설명 없음
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

GB28181Session.js 40KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104
  1. const xml2js = require('xml2js');
  2. const SIP = require('./sip/sip');
  3. const SDP = require('./sdp/parser');
  4. const Logger = require('./core/logger');
  5. const context = require('./core/ctx');
  6. const NtvAuthModule = require('./NtvAuthModule');
  7. class NodeSipSession {
  8. constructor(config, userid, remote, uas) {
  9. //ntv add
  10. this.config = config;
  11. this.request = remote.request;
  12. this.protocol = remote.info.protocol;
  13. this.id = userid;
  14. //注册请求
  15. if (this.request && this.request.headers) {
  16. if (this.request.headers.via[0])
  17. this.via = this.request.headers.via[0];
  18. //过期时间
  19. if (this.request.headers.expires)
  20. this.expires = this.request.headers.expires;
  21. }
  22. //主机通讯地址&端口
  23. this.host = remote.info.address;
  24. this.port = remote.info.port;
  25. //设备目录
  26. this.catalog = { devicelist: [] };
  27. this.sn = 0;
  28. this.callbacks = {};
  29. this.dialogs = {};
  30. //当前域
  31. this.GBDomain = config.GB28181.sipServer.realm || '3402000000';
  32. //SIP服务通讯端口
  33. this.GBServerPort = config.GB28181.sipServer.mapPort || config.GB28181.sipServer.listen;
  34. //SIP服务主机地址
  35. this.GBserverHost = config.GB28181.sipServer.mapHost || config.GB28181.sipServer.host;
  36. //SIP服务编号
  37. this.GBServerId = config.GB28181.sipServer.serial || config.GB28181.streamServer.serial;
  38. //超时
  39. this.pingTime = config.GB28181.sipServer.ping ? config.GB28181.sipServer.ping * 1000 : 60000;
  40. //重试次数
  41. this.pingTimeout = config.GB28181.sipServer.ping_timeout || 3;
  42. //最后一个保活包接收时间
  43. this.startTimestamp = Date.now();
  44. //丢包统计,连接3次丢包,表示对象下线
  45. this.lostPacketCount = 0;
  46. this.pingInterval = null;
  47. this.uas = uas;
  48. this.TAG = 'sip';
  49. //ntv add play mode prop
  50. this.playmode='';
  51. context.sessions.set(this.id, this);
  52. }
  53. //启动
  54. async run(authModule,auto_play) {
  55. this.pingInterval = setInterval(() => {
  56. let timevalue = Date.now() - this.startTimestamp;
  57. if (timevalue > this.pingTime) {
  58. this.lostPacketCount++;
  59. if (this.lostPacketCount > this.pingTimeout) {
  60. this.stop();
  61. context.nodeEvent.emit('offline', this.id);
  62. }
  63. }
  64. }, this.pingTime);
  65. this.isStarting = true;
  66. Logger.log(`[${this.id}] New Device Connected ip=${this.via.host} port=${this.via.port} `);
  67. context.nodeEvent.emit('online', this.id);
  68. //获取设备基本信息
  69. this.deviceinfo = await this.getDeviceInfo();
  70. //获取设备状态
  71. this.devicestatus = await this.getDeviceStatus();
  72. //获取设备目录
  73. this.catalog = await this.getCatalog();
  74. //ntv 提交状态,自动开启视频...
  75. this.authModule = authModule;
  76. this.auto_play = auto_play;
  77. authModule.status(this.id,1,(data)=>{
  78. if(data.code==0){
  79. Logger.log("设备在线状态已上报!");
  80. }else{
  81. Logger.log("设备在线状态已上报失败!" + data.err_desc);
  82. }
  83. });
  84. await this.startRealPlay(this.id,auto_play);
  85. }
  86. //ntv add 自动发realplay消息 TODO streamServer配置项增加服务器ip选项,使摄像头可以推送到其他服务器。
  87. async startRealPlay(channelId,auto_play) {
  88. let config = this.config;
  89. if(config.GB28181.streamServer.enable && (auto_play || config.GB28181.streamServer.auto_play)){
  90. let host = config.GB28181.sipServer.host;
  91. let port = config.GB28181.streamServer.listen;
  92. let mode = 1;
  93. Logger.log(`[${channelId}] auto send real play message`);
  94. this.sendRealPlayMessage(channelId,host,port,mode);
  95. }
  96. }
  97. // 停止
  98. stop() {
  99. if (this.isStarting) {
  100. if (this.pingInterval != null) {
  101. clearInterval(this.pingInterval);
  102. this.pingInterval = null;
  103. }
  104. this.isStarting = false;
  105. context.sessions.delete(this.id);
  106. this.authModule.status(this.id,0,(data)=>{
  107. if(data.code==0){
  108. Logger.log("设备离线状态已上报!");
  109. }else{
  110. Logger.log("设备离线状态已上报失败!" + data.err_desc);
  111. }
  112. });
  113. }
  114. }
  115. //将XML转JSON
  116. parseXml(xml) {
  117. let json = {};
  118. xml2js.parseString(xml, { explicitArray: false, ignoreAttrs: true }, (err, result) => {
  119. json = result;
  120. });
  121. return json;
  122. }
  123. //获取设备基础信息
  124. async getDeviceInfo() {
  125. return await this.QueryDeviceInfo();
  126. }
  127. //获取设备目录
  128. async getCatalog() {
  129. return await this.QueryDeviceCatalog();
  130. }
  131. //获取设备状态信息
  132. async getDeviceStatus() {
  133. return await this.QueryDeviceStatus();
  134. }
  135. //录像文件查询
  136. async getRecordInfos(channelId, begin, end) {
  137. return await this.QueryRecordInfo(channelId, begin, end);
  138. }
  139. //云台控制
  140. ControlPTZ(channelId, ptzvalue) {
  141. this.Control(channelId, 'PTZCmd', ptzvalue);
  142. }
  143. //重启
  144. ControlBoot() {
  145. this.Control(this.id, 'TeleBoot', 'Boot',);
  146. }
  147. //设备信息
  148. QueryDeviceInfo() {
  149. return new Promise((resolve, reject) => {
  150. let deviceinfo = {};
  151. this.Query('DeviceInfo', (content) => {
  152. if (content.Result === 'OK') {
  153. switch (content.CmdType) {
  154. case 'DeviceInfo':
  155. deviceinfo = { manufacturer: content.Manufacturer, model: content.Model, firmware: content.Firmware, name: content.DeviceName };
  156. break;
  157. }
  158. }
  159. else {
  160. deviceinfo = { result: false, message: content.Result, errorcode: content.ErrorCode };
  161. }
  162. resolve(deviceinfo);
  163. return true;
  164. });
  165. });
  166. }
  167. //设备目录
  168. QueryDeviceCatalog() {
  169. return new Promise((resolve, reject) => {
  170. let catalog = { total: 0, devicelist: [] };
  171. this.Query('Catalog', (content) => {
  172. if (content.Result) {
  173. catalog = { result: false, message: content.Result, errorcode: content.ErrorCode };
  174. }
  175. else {
  176. switch (content.CmdType) {
  177. case 'Catalog':
  178. {
  179. if (content.SumNum)
  180. catalog.total = Number(content.SumNum);
  181. if (content.DeviceList) {
  182. if (catalog.total > 1) {
  183. content.DeviceList.Item.forEach(device => {
  184. catalog.devicelist.push(device);
  185. });
  186. }
  187. else {
  188. catalog.devicelist.push(content.DeviceList.Item);
  189. }
  190. }
  191. }
  192. break;
  193. }
  194. }
  195. if (catalog.total != catalog.devicelist.length)
  196. return false;
  197. resolve(catalog);
  198. return true;
  199. });
  200. });
  201. }
  202. //设备状态
  203. QueryDeviceStatus() {
  204. return new Promise((resolve, reject) => {
  205. let devicestatus = {};
  206. this.Query('DeviceStatus', (content) => {
  207. if (content.Result === 'OK') {
  208. switch (content.CmdType) {
  209. case 'DeviceStatus'://设备状态
  210. devicestatus = { online: content.Online, status: content.Status, encode: content.Encode, record: content.Record, devicetime: content.DeviceTime };
  211. break;
  212. }
  213. }
  214. else {
  215. devicestatus = { result: false, message: content.Result, errorcode: content.ErrorCode };
  216. }
  217. resolve(devicestatus);
  218. return true;
  219. });
  220. });
  221. }
  222. //录像文件查询
  223. QueryRecordInfo(channelId, startTime, endTime) {
  224. return new Promise((resolve, reject) => {
  225. let recordinfos = { total: 0, recordlist: [] };
  226. this.sendQueryRecordInfoMessage(channelId, startTime, endTime, (content) => {
  227. switch (content.CmdType) {
  228. case 'RecordInfo'://设备状态
  229. {
  230. if (content.SumNum)
  231. recordinfos.total = Number(content.SumNum);
  232. if (content.RecordList) {
  233. if (recordinfos.total > 0) {
  234. content.RecordList.Item.forEach(record => {
  235. recordinfos.recordlist.push(record);
  236. });
  237. }
  238. }
  239. }
  240. break;
  241. }
  242. if (recordinfos.total != recordinfos.recordlist.length) {
  243. return false;
  244. }
  245. resolve(recordinfos);
  246. return true;
  247. });
  248. });
  249. }
  250. //控制 channelId 设备通道国标编码
  251. Control(channelId, cmdtype, cmdvalue, callback) {
  252. //PTZCmd/TeleBoot
  253. let json = {
  254. Control: {
  255. CmdType: 'DeviceControl',
  256. SN: this.sn++,
  257. DeviceID: channelId
  258. }
  259. };
  260. switch (cmdtype) {
  261. case 'PTZCmd':
  262. {
  263. let cmd = Buffer.alloc(8);
  264. cmd[0] = 0xA5;//首字节以05H开头
  265. cmd[1] = 0x0F;//组合码,高4位为版本信息v1.0,版本信息0H,低四位为校验码
  266. // 校验码 = (cmd[0]的高4位+cmd[0]的低4位+cmd[1]的高4位)%16
  267. cmd[2] = 0x01;
  268. let ptzSpeed = 0x5f; //默认速度
  269. switch (Number(cmdvalue)) {
  270. //停止
  271. case 0:
  272. cmd[3] = 0x00;
  273. break;
  274. //向右
  275. case 1:
  276. cmd[3] = 0x01;
  277. cmd[4] = ptzSpeed;
  278. break;
  279. //向左
  280. case 2:
  281. cmd[3] = 0x02;
  282. cmd[4] = ptzSpeed;
  283. break;
  284. //向下
  285. case 3:
  286. cmd[3] = 0x04;
  287. cmd[5] = ptzSpeed;
  288. break;
  289. //向上
  290. case 4:
  291. cmd[3] = 0x08;
  292. cmd[5] = ptzSpeed;
  293. break;
  294. //放大
  295. case 5:
  296. cmd[3] = 0x10;
  297. cmd[6] = 0x10;
  298. break;
  299. //缩小
  300. case 6:
  301. cmd[3] = 0x20;
  302. cmd[6] = 0x10;
  303. break;
  304. //组合
  305. case 7:
  306. cmd[3] = 0x29;
  307. break;
  308. }
  309. cmd[7] = (cmd[0] + cmd[1] + cmd[2] + cmd[3] + cmd[4] + cmd[5] + cmd[6]) % 256;
  310. json.Control.PTZCmd = this.Bytes2HexString(cmd);
  311. }
  312. break;
  313. case 'TeleBoot':
  314. json.Control.TeleBoot = cmdvalue;
  315. break;
  316. }
  317. let id = [json.Control.CmdType, json.Control.SN].join(':');
  318. if (!this.callbacks[id])
  319. this.callbacks[id] = callback;
  320. //JSON 转XML
  321. let builder = new xml2js.Builder();
  322. let content = builder.buildObject(json);
  323. let options = {
  324. method: 'MESSAGE',
  325. contentType: 'application/MANSCDP+xml',
  326. content: content
  327. };
  328. this.send(options);
  329. }
  330. //字节转字符串
  331. Bytes2HexString(b) {
  332. let hexs = "";
  333. for (let i = 0; i < b.length; i++) {
  334. let hex = (b[i]).toString(16);
  335. if (hex.length === 1) {
  336. hex = '0' + hex;
  337. }
  338. hexs += hex.toUpperCase();
  339. }
  340. return hexs;
  341. }
  342. //查询
  343. Query(cmdtype, callback) {
  344. //DeviceInfo/Catalog/DeviceStatus
  345. let json = {
  346. Query: {
  347. CmdType: cmdtype,
  348. SN: this.sn++,
  349. DeviceID: this.id
  350. }
  351. };
  352. let id = [json.Query.CmdType, json.Query.SN].join(':');
  353. if (!this.callbacks[id])
  354. this.callbacks[id] = callback;
  355. //JSON 转XML
  356. let builder = new xml2js.Builder();
  357. let content = builder.buildObject(json);
  358. let options = {
  359. method: 'MESSAGE',
  360. contentType: 'application/MANSCDP+xml',
  361. content: content
  362. };
  363. this.send(options);
  364. }
  365. //发送查询通道录像文件请求
  366. sendQueryRecordInfoMessage(channelId, startTime, endTime, callback) {
  367. let json = {
  368. Query: {
  369. CmdType: 'RecordInfo',
  370. SN: this.sn++,
  371. DeviceID: channelId,
  372. StartTime: startTime,
  373. EndTime: endTime,
  374. Secrecy: 0, //保密属性 0:不保密 1:涉密
  375. Type: 'all', //录像产生类型 time/alarm/manual/all
  376. IndistinctQuery: 0//字段代表模糊查询,缺省为 0。 值为 0 时:不进行模糊查询。此时根据 SIP 消息中 To 头域 URI 中的 ID 值确定查询录像位置,若 ID 值为本域系统 ID 则进行中心历史记录检索,若为前端设备 ID 则进行前端设备历史记录检
  377. }
  378. };
  379. let id = [json.Query.CmdType, json.Query.SN].join(':');
  380. if (!this.callbacks[id])
  381. this.callbacks[id] = callback;
  382. //JSON 转XML
  383. let builder = new xml2js.Builder();
  384. let content = builder.buildObject(json);
  385. let options = {
  386. id: channelId,
  387. method: 'MESSAGE',
  388. contentType: 'application/MANSCDP+xml',
  389. content: content
  390. };
  391. this.send(options);
  392. }
  393. //下载
  394. Download() {
  395. }
  396. //补位0
  397. _prefixInteger(num, m) {
  398. return (Array(m).join(0) + num).slice(-m);
  399. }
  400. //回放 begin-开始时间 end-结束时间 channelId-设备通道国标编码
  401. sendPlaybackMessage(channelId, begin, end, nhost, nport, mode) {
  402. return new Promise((resolve, reject) => {
  403. let isFinded = false;
  404. let findssrc = '';
  405. let result = { result: true, message: 'OK' };
  406. for (var key in this.dialogs) {
  407. let session = this.dialogs[key];
  408. if (session.bye && session.port === rport && session.host === rhost && session.channelId === channelId && session.play === 'playback' && session.begin == begin && session.end == end) {
  409. isFinded = true;
  410. findssrc = session.ssrc;
  411. break;
  412. }
  413. }
  414. if (isFinded) {
  415. result.data = { ssrc: findssrc };
  416. resolve(result);
  417. return;
  418. }
  419. //0: udp,1:tcp/passive ,2:tcp/active
  420. let selectMode = mode || 0;
  421. //产生1-9999随机数
  422. let random = Math.floor(Math.random() * 9999);
  423. let streamId = this._prefixInteger(random, 4);
  424. //回看以1开头,同一个通道编码可能存在许多不同时间段的请求,所以ssrc后四位要处理一下,不能用通道编码了
  425. let ssrc = "1" + channelId.substring(3, 8) + streamId;
  426. let host = nhost || "127.0.0.1";
  427. let port = nport || 9200;
  428. let sdpV = "";
  429. let mValue = "RTP/AVP"
  430. switch (Number(selectMode)) {
  431. default:
  432. break;
  433. case 1:
  434. sdpV = `a=setup:passive\r\n` +
  435. `a=connection:new\r\n`;
  436. mValue = "TCP/RTP/AVP";
  437. break;
  438. case 2:
  439. sdpV = `a=setup:active\r\n` +
  440. `a=connection:new\r\n`;
  441. mValue = "TCP/RTP/AVP";
  442. break;
  443. }
  444. let content = `v=0\r\n` +
  445. `o=${this.GBServerId} 0 0 IN IP4 ${host}\r\n` +
  446. `s=Playback\r\n` +
  447. `u=${channelId}:0\r\n` +
  448. `c=IN IP4 ${host}\r\n` +
  449. `t=${begin} ${end}\r\n` +
  450. `m=video ${port} ${mValue} 96\r\n` +
  451. `a=rtpmap:96 PS/90000\r\n` +
  452. `a=recvonly\r\n` +
  453. sdpV +
  454. `y=${ssrc}\r\n` +
  455. `f=v/2/4///a///\r\n`;
  456. let that = this;
  457. let options = {
  458. id: channelId,
  459. subject: `${channelId}:${ssrc},${this.GBServerId}:0`,
  460. method: 'INVITE',
  461. contentType: 'application/sdp',
  462. content: content,
  463. callback: function (response) {
  464. if (response.status >= 300) {
  465. //错误信息
  466. Logger.error(`[${that.TAG}] id=${that.id} ssrc=${ssrc} status=${response.status}`);
  467. result.result = false;
  468. result.data = { status: response.status };
  469. result.message = `ErrorCode=${response.status}`;
  470. resolve(result);
  471. }
  472. else if (response.status < 200) {
  473. Logger.log(`[${that.TAG}] id=${that.id} ssrc=${ssrc} status=${response.status}`);
  474. }
  475. else {
  476. //判断消息类型
  477. switch (options.method) {
  478. case 'INVITE':
  479. //SDP
  480. if (response.content) {
  481. // 响应消息体
  482. let sdp = SDP.parse(response.content);
  483. Logger.log(`[${that.TAG}] id=${that.id} ssrc=${ssrc} sdp=${sdp}`);
  484. //Step 6 SIP服务器收到媒体流发送者返回的200OK响应后,向 媒体服务器 发送 ACK请求,请求中携带 消息5中媒体流发送者回复的200 ok响应消息体,完成与媒体服务器的invite会话建立过程
  485. context.nodeEvent.emit('sdpReceived', sdp);
  486. //Step 7 SIP服务器收到媒体流发送者返回200 OK响应后,向 媒体流发送者 发送 ACK请求,请求中不携带消息体,完成与媒体流发送者的invite会话建立过程
  487. that.uas.send({
  488. method: 'ACK',
  489. uri: response.headers.contact[0].uri,
  490. headers: {
  491. to: response.headers.to,
  492. from: response.headers.from,
  493. 'call-id': response.headers['call-id'],
  494. cseq: { method: 'ACK', seq: response.headers.cseq.seq }
  495. }
  496. });
  497. //会话标识
  498. let key = [response.headers['call-id'], response.headers.from.params.tag, response.headers.to.params.tag].join(':');
  499. //创建会话
  500. if (!that.dialogs[key]) {
  501. // 断开会话请求
  502. let byeRequest = {
  503. method: 'BYE',
  504. uri: response.headers.contact[0].uri,
  505. headers: {
  506. to: response.headers.to,
  507. from: response.headers.from,
  508. 'call-id': response.headers['call-id'],
  509. cseq: { method: 'BYE', seq: response.headers.cseq.seq++ }//需额外加1
  510. }
  511. }
  512. that.dialogs[key] = { channelId: channelId, ssrc: ssrc, host: host, port: port, begin: begin, end: end, bye: byeRequest, play: 'playback' };
  513. }
  514. result.data = { ssrc: ssrc };
  515. resolve(result);
  516. }
  517. break;
  518. }
  519. }
  520. }
  521. };
  522. this.send(options);
  523. });
  524. }
  525. //回看播放控制
  526. sendPlayControlMessage(channelId, begin, end, cmd, value) {
  527. return new Promise((resolve, reject) => {
  528. let result = { result: false, message: 'OK' };
  529. //PLAY/PAUSE/TEARDOWN
  530. //播放速度,其中 1 为正常
  531. let scale = ['0.25', '0.5', '1.0', '2.0', '4.0', '-0.25', '-0.5', '-1.0', '-2.0', '-4.0'];
  532. //播放/倍速播放/暂停/停止
  533. let method = ['PLAY', 'PLAY', 'PAUSE', 'TEARDOWN'];
  534. let findSession = null;
  535. for (var key in this.dialogs) {
  536. let session = this.dialogs[key];
  537. if (session.bye && session.channelId === channelId && session.play === 'playback' && session.begin == begin && session.end == end) {
  538. findSession = session;
  539. break;
  540. }
  541. }
  542. if (findSession == null) {
  543. result.message = 'dialog not found.';
  544. resolve(result);
  545. return;
  546. }
  547. //引用参数
  548. var request = SIP.copyMessage(findSession.bye);
  549. let findssrc = findSession.ssrc;
  550. if (!findSession.cseq) {
  551. findSession.cseq = 1;
  552. }
  553. else {
  554. findSession.cseq++;
  555. }
  556. if (request && findssrc) {
  557. request.method = 'INFO';
  558. request['content-type'] = 'application/MANSRTSP';
  559. request.headers.cseq.method = 'Info';
  560. request.headers.cseq.seq = Math.floor(Math.random() * 100);
  561. request.headers.contact = [{ uri: 'sip:' + this.GBServerId + '@' + this.GBserverHost + ':' + this.GBServerPort }];
  562. switch (Number(cmd)) {
  563. //播放/随机播放
  564. case 0:
  565. {
  566. request.content = "PLAY MANSRTSP/1.0\r\n" +
  567. `CSeq:${findSession.cseq}\r\n` +
  568. `Range:npt=${(value || 'now-')}\r\n`;
  569. }
  570. break;
  571. //快进/慢退
  572. case 1:
  573. {
  574. //传参数如果不符合条件,用原速播放
  575. let speed = Number(value);
  576. if (speed < 0 || speed > 9) {
  577. speed = 2;
  578. }
  579. request.content = "PLAY MANSRTSP/1.0\r\n" +
  580. `CSeq:${findSession.cseq}\r\n` +
  581. `Scale:${scale[value]}\r\n`;
  582. }
  583. break;
  584. //暂停(当前位置)
  585. case 2:
  586. {
  587. request.content = "PAUSE MANSRTSP/1.0\r\n" +
  588. `CSeq:${findSession.cseq}\r\n` +
  589. `PauseTime:now\r\n`;
  590. }
  591. break;
  592. //停止
  593. case 3:
  594. {
  595. request.content = "TEARDOWN MANSRTSP/1.0\r\n" +
  596. `CSeq:${findSession.cseq}\r\n`;
  597. }
  598. break;
  599. }
  600. //发送请求
  601. this.uas.send(request, (response) => {
  602. //响应
  603. Logger.log(`[${this.id}] INFO ${method[cmd]} result=${response.status}`);
  604. if (response.status == 200) {
  605. result.result = true;
  606. }
  607. else {
  608. result.message = `ErrorCode=${response.status}`;
  609. result.data = { status: response.status };
  610. }
  611. resolve(result);
  612. });
  613. }
  614. });
  615. }
  616. //预览 channelId 通道国标编码
  617. sendRealPlayMessage(channelId, rhost, rport, mode) {
  618. return new Promise((resolve, reject) => {
  619. let result = { result: true, message: 'OK' };
  620. let isFinded = false;
  621. let findssrc = "";
  622. for (var key in this.dialogs) {
  623. let session = this.dialogs[key];
  624. if (session.bye && session.port === rport && session.host === rhost && session.channelId === channelId && session.play === 'realplay') {
  625. isFinded = true;
  626. findssrc = session.ssrc;
  627. break;
  628. }
  629. }
  630. //己存在会话,同一个流媒体不需要重复请求
  631. if (isFinded) {
  632. result.data = { ssrc: findssrc };
  633. resolve(result);
  634. return;
  635. }
  636. //0: udp,1:tcp/passive ,2:tcp/active
  637. let selectMode = mode || 0;
  638. //ssrc is here -- by ntv wangjian
  639. //let ssrc = "0" + channelId.substring(16, 20) + channelId.substring(3, 8);
  640. let ssed = channelId.substring(13);
  641. let ised = '' + (parseInt(ssed)*13+10000);
  642. let ssrc = "1" + ssed + ised.substring(ised.length-2);
  643. let host = rhost || "127.0.0.1";
  644. let port = rport || 9200;
  645. let sdpV = "";
  646. let mValue = "RTP/AVP"
  647. switch (Number(selectMode)) {
  648. default:
  649. break;
  650. case 1:
  651. sdpV = `a=setup:passive\r\n` +
  652. `a=connection:new\r\n`;
  653. mValue = "TCP/RTP/AVP";
  654. break;
  655. case 2:
  656. sdpV = `a=setup:active\r\n` +
  657. `a=connection:new\r\n`;
  658. mValue = "TCP/RTP/AVP";
  659. break;
  660. }
  661. //s=Play/Playback/Download/Talk
  662. let content = `v=0\r\n` +
  663. `o=${this.GBServerId} 0 0 IN IP4 ${host}\r\n` +
  664. `s=Play\r\n` +
  665. `c=IN IP4 ${host}\r\n` +
  666. `t=0 0\r\n` +
  667. `m=video ${port} ${mValue} 96\r\n` +
  668. `a=rtpmap:96 PS/90000\r\n` +
  669. `a=recvonly\r\n` +
  670. sdpV +
  671. `y=${ssrc}\r\n` +
  672. `f=v/2/4///a///\r\n`;
  673. let that = this;
  674. let options = {
  675. id: channelId,
  676. subject: `${channelId}:${ssrc},${this.GBServerId}:0`,
  677. method: 'INVITE',
  678. contentType: 'application/sdp',
  679. content: content,
  680. callback: function (response) {
  681. if (response.status >= 300) {
  682. //错误信息
  683. Logger.error(`[${that.id}] ssrc=${ssrc} status=${response.status}`);
  684. result.result = false;
  685. result.data = { status: response.status };
  686. result.message = `ErrorCode=${response.status}`;
  687. resolve(result);
  688. }
  689. else if (response.status < 200) {
  690. Logger.log(`[${that.id} ] ssrc=${ssrc} status=${response.status}`);
  691. }
  692. else {
  693. //判断消息类型
  694. switch (options.method) {
  695. case 'INVITE':
  696. //SDP
  697. if (response.content) {
  698. //响应消息体
  699. let sdp = SDP.parse(response.content);
  700. Logger.log(`[${that.id}] ssrc=${ssrc} sdp=${sdp}`);
  701. //Step 6 SIP服务器收到媒体流发送者返回的200OK响应后,向 媒体服务器 发送 ACK请求,请求中携带 消息5中媒体流发送者回复的200 ok响应消息体,完成与媒体服务器的invite会话建立过程
  702. context.nodeEvent.emit('sdpReceived', sdp);
  703. //Step 7 SIP服务器收到媒体流发送者返回200 OK响应后,向 媒体流发送者 发送 ACK请求,请求中不携带消息体,完成与媒体流发送者的invite会话建立过程
  704. that.uas.send({
  705. method: 'ACK',
  706. uri: response.headers.contact[0].uri,
  707. headers: {
  708. to: response.headers.to,
  709. from: response.headers.from,
  710. 'call-id': response.headers['call-id'],
  711. cseq: { method: 'ACK', seq: response.headers.cseq.seq }
  712. }
  713. });
  714. //会话标识
  715. let key = [response.headers['call-id'], response.headers.from.params.tag, response.headers.to.params.tag].join(':');
  716. //创建会话
  717. if (!that.dialogs[key]) {
  718. // 断开会话请求
  719. let byeRequest = {
  720. method: 'BYE',
  721. uri: response.headers.contact[0].uri,
  722. headers: {
  723. to: response.headers.to,
  724. from: response.headers.from,
  725. 'call-id': response.headers['call-id'],
  726. //cseq: { method: 'BYE', seq: response.headers.cseq.seq++ }//需额外加1
  727. cseq: { method: 'BYE', seq: response.headers.cseq.seq+1 } // ntv modify, 上一语句是基本语法不熟悉,实际没有加1
  728. }
  729. }
  730. that.dialogs[key] = { channelId: channelId, ssrc: ssrc, host: host, port: port, bye: byeRequest, play: 'realplay' };
  731. }
  732. result.data = { ssrc: ssrc };
  733. //ntv
  734. that.playmode='realplay';
  735. this.authModule.play_status(this.id,1,(data)=>{
  736. if(data.code==0){
  737. Logger.log("视频开启状态已上报!");
  738. }else{
  739. Logger.log("视频开启状态已上报失败!" + data.err_desc);
  740. }
  741. });
  742. resolve(result);
  743. }
  744. break;
  745. }
  746. }
  747. }
  748. };
  749. this.send(options);
  750. });
  751. }
  752. //停止实时预览
  753. async sendStopRealPlayMessage(channelId, rhost, rport) {
  754. return new Promise((resolve, reject) => {
  755. let result = { result: false, message: 'not find dialog.' };
  756. for (var key in this.dialogs) {
  757. //搜索满足条件的会话
  758. let session = this.dialogs[key];
  759. if (session.bye && session.port === rport && session.host === rhost && session.channelId === channelId && session.play === 'realplay') {
  760. //ntv 挪到回调中,TODO 这个指令发不成功!
  761. //context.nodeEvent.emit('stopPlayed', session.ssrc);
  762. this.uas.send(session.bye, (response) => {
  763. Logger.log(`[${this.id}] StopRealPlay status=${response.status}`);
  764. if(response.status==200){
  765. /**
  766. ntv remove rtp连接断开时再emit这条消息,放到了stream/session中执行
  767. */
  768. //context.nodeEvent.emit('stopPlayed', session.ssrc);
  769. delete this.dialogs[key];
  770. this.authModule.play_status(this.id,0,(data)=>{
  771. if(data.code==0){
  772. Logger.log("视频关闭状态已上报!");
  773. }else{
  774. Logger.log("视频关闭状态已上报失败!" + data.err_desc);
  775. }
  776. });
  777. }
  778. });
  779. //ntv
  780. this.playmode='';
  781. result.result = true;
  782. result.message = 'OK';
  783. }
  784. }
  785. resolve(result);
  786. });
  787. }
  788. //停止录像回看
  789. async sendStopPlayBackMessage(channelId, begin, end, nhost, nport) {
  790. let result = { result: false, message: 'not find dialog.' };
  791. for (var key in this.dialogs) {
  792. //搜索满足条件的会话
  793. let session = this.dialogs[key];
  794. if (session.bye && session.begin == begin && session.end == end && session.port === nport && session.host === nhost && session.channelId === channelId && session.play === 'playback') {
  795. //先发送停止回看命令
  796. result = await this.sendPlayControlMessage(session.channelId, session.begin, session.end, 3);
  797. context.nodeEvent.emit('stopPlayed', session.ssrc);
  798. //发送BYE
  799. this.uas.send(session.bye, (response) => {
  800. Logger.log(`[${this.id}] StopPlayback status=${response.status}`);
  801. delete this.dialogs[key];
  802. });
  803. break;
  804. }
  805. }
  806. return result;
  807. }
  808. //处理 MESSAGE
  809. onMessage(request) {
  810. let content = this.parseXml(request.content);
  811. // 回复
  812. if (content.hasOwnProperty('Response')) {
  813. let id = [content.Response.CmdType, content.Response.SN].join(':');
  814. if (this.callbacks[id]) {
  815. //如果是查询有返回多条消息,还需等待。
  816. let result = this.callbacks[id](content.Response);
  817. if (result)
  818. delete this.callbacks[id];
  819. }
  820. }
  821. // 通知
  822. if (content.hasOwnProperty('Notify')) {
  823. //ntv remove notify log
  824. //Logger.log(`[${this.id}] Notify CmdType=${content.Notify.CmdType} SN=${content.Notify.SN} length=${request.content.length}`);
  825. switch (content.Notify.CmdType) {
  826. //保活消息
  827. case 'Keepalive':
  828. {
  829. //更新时间
  830. this.startTimestamp = Date.now();
  831. this.lostPacketCount = 0;
  832. }
  833. break;
  834. //媒体播放完成消息
  835. case 'MediaStatus':
  836. {
  837. switch (content.Notify.NotifyType) {
  838. //录像发送完毕
  839. case 121:
  840. {
  841. let key = [request.headers['call-id'], request.headers.from.params.tag, request.headers.to.params.tag].join(':');
  842. if (this.dialogs[key]) {
  843. let session = this.dialogs[key];
  844. if (session.bye && content.Notify.DeviceID == session.channelId) {
  845. this.uas.send(session.bye, (reqponse) => {
  846. if (reqponse.status == 200 || reqponse.status == 481)
  847. delete this.dialogs[key];
  848. });
  849. }
  850. }
  851. }
  852. break;
  853. }
  854. }
  855. break;
  856. }
  857. }
  858. }
  859. //发送SIP消息
  860. send(options) {
  861. //设备国标编码+设备主机地址+通讯端口
  862. let uri = 'sip:' + (options.id || this.id) + '@' + this.host + ':' + this.port;
  863. let request = {
  864. method: options.method,
  865. uri: uri,
  866. headers: {
  867. to: { uri: 'sip:' + (options.id || this.id) + '@' + this.GBDomain },
  868. from: { uri: 'sip:' + this.GBServerId + '@' + this.GBDomain, params: { tag: options.tag || this.getTagRandom(8) } },
  869. 'call-id': this.getCallId(),
  870. cseq: { method: options.method, seq: Math.floor(Math.random() * 1e5) },
  871. 'content-type': options.contentType,
  872. subject: options.subject,
  873. contact: [{ uri: 'sip:' + this.GBServerId + '@' + this.GBserverHost + ':' + this.GBServerPort }]
  874. },
  875. content: options.content
  876. }
  877. this.uas.send(request, options.callback);
  878. }
  879. //
  880. getSN() {
  881. this.sn++;
  882. return this.sn;
  883. }
  884. //
  885. getCallId() {
  886. return Math.floor(Math.random() * 1e6).toString() + '@' + this.GBserverHost;
  887. }
  888. //
  889. getTagRandom(size) {
  890. let seed = new Array('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
  891. 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'm', 'n', 'p', 'Q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
  892. '2', '3', '4', '5', '6', '7', '8', '9'
  893. );//数组
  894. let seedlength = seed.length;//数组长度
  895. let num = '';
  896. for (let i = 0; i < size; i++) {
  897. let j = Math.floor(Math.random() * seedlength);
  898. num += seed[j];
  899. }
  900. return num;
  901. }
  902. }
  903. module.exports = NodeSipSession;