Ei kuvausta
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 36KB

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