useCamera.ts 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. import { defHttp } from '/@/utils/http/axios';
  2. // import { render, h, nextTick } from 'vue';
  3. // import LivePlayer from '@liveqing/liveplayer-v3';
  4. import { useDrag } from '../event/useDrag';
  5. import Player, { I18N } from 'xgplayer';
  6. import HlsPlugin from 'xgplayer-hls';
  7. import FlvPlugin from 'xgplayer-flv';
  8. import 'xgplayer/dist/index.min.css';
  9. import ZH from 'xgplayer/es/lang/zh-cn';
  10. I18N.use(ZH);
  11. export function useCamera() {
  12. const cameraList = (params) => defHttp.get({ url: '/safety/ventanalyCamera/list', params });
  13. const cameraAddrList = (params) => defHttp.post({ url: '/monitor/camera/info', params });
  14. const cameraAddr = (params) => defHttp.get({ url: '/monitor/camera/queryByCameraCode', params });
  15. let webRtcServer = <any[]>[];
  16. const playerList = <any[]>[];
  17. const playerDoms = <(HTMLVideoElement | undefined | null)[]>[];
  18. const videoParentDomList: (HTMLElement | [string, { name: string; addr: string; cameraRate: number; devicekind: string }])[] = [];
  19. async function getCamera(deviceid, parentPlayerDom?, devKind?, isCustom = false) {
  20. removeCamera();
  21. if (!parentPlayerDom) {
  22. parentPlayerDom = document.createElement('div');
  23. parentPlayerDom.setAttribute('style', `top:0px; left: 0px; width: 100%; height: 100%; position: fixed; z-index: 999;`);
  24. }
  25. let res;
  26. if (!devKind) {
  27. res = await cameraList({ deviceid });
  28. } else {
  29. res = await cameraList({ sysId: deviceid, devKind });
  30. }
  31. const cameras: [] = res.records || [];
  32. const cameraAddrs: any[] = [],
  33. cameraNames: any[] = [];
  34. if (cameras.length > 0) {
  35. for (let i = 0; i < cameras.length; i++) {
  36. const item = cameras[i];
  37. if (
  38. item['devicekind'] === 'toHKRtsp' ||
  39. item['devicekind'] === 'toHKHLs' ||
  40. item['devicekind'] === 'HLL' ||
  41. item['devicekind'] === 'YZG_URL'
  42. ) {
  43. // 从海康平台接口获取视频流
  44. const videoType = item['devicekind'] === 'toHKRtsp' ? 'rtsp' : '';
  45. try {
  46. const data = await cameraAddr({ cameraCode: item['addr'], videoType });
  47. if (data && data['url']) {
  48. cameraAddrs.push({ name: item['name'], addr: data['url'], cameraRate: item['cameraRate'], devicekind: item['devicekind'] });
  49. }
  50. // // 从海康平台接口获取视频流测试
  51. // cameraAddrs.push({
  52. // name: item['name'],
  53. // addr: 'https://demo.unified-streaming.com/k8s/features/stable/video/tears-of-steel/tears-of-steel.mp4/.m3u8',
  54. // });
  55. } catch (error) {}
  56. } else if (item['devicekind'] == 'toHKR') {
  57. cameraNames.push({ name: item['name'], cameraRate: item['cameraRate'] });
  58. } else {
  59. cameraAddrs.push({ name: item['name'], addr: item['addr'], cameraRate: item['cameraRate'], devicekind: item['devicekind'] });
  60. }
  61. }
  62. }
  63. if (cameraNames.length > 0) {
  64. // 请求接口从装备院拿数据
  65. const cameraNameList = cameraNames.map((item) => item.name);
  66. const addrs: string[] = await cameraAddrList({ cameraNameList: cameraNameList });
  67. for (let i = 0; i < addrs.length; i++) {
  68. cameraAddrs.push({ name: '摄像头' + i, addr: addrs[i] });
  69. }
  70. }
  71. if (isCustom) {
  72. return cameraAddrs;
  73. } else {
  74. await deviceCameraInit(cameraAddrs, parentPlayerDom);
  75. }
  76. }
  77. function deviceCameraInit(cameraAddrs, player: HTMLElement) {
  78. const newWebRtcServer = [];
  79. let livePlayerDiv: HTMLElement | null = document.getElementById('LivePlayerBox');
  80. if (livePlayerDiv) {
  81. livePlayerDiv.remove();
  82. livePlayerDiv = null;
  83. }
  84. if (!livePlayerDiv) {
  85. const dom = document.createElement('div');
  86. dom.setAttribute('id', 'LivePlayerBox');
  87. dom.setAttribute('class', 'live-player-box');
  88. livePlayerDiv = dom;
  89. player.appendChild(livePlayerDiv);
  90. }
  91. return new Promise((resolve) => {
  92. const playCamrea = () => {
  93. if (cameraAddrs.length > 0) {
  94. const promiseList: Promise<any>[] = [];
  95. // debugger;
  96. cameraAddrs.forEach(async (cameraUrl: { name: string; addr: string; cameraRate: number; devicekind: string }, index) => {
  97. const promise = new Promise(async (childResolve) => {
  98. let cameraNameDom: null | HTMLElement = null;
  99. console.log('摄像头地址--------->', cameraUrl, cameraUrl.addr.startsWith('rtsp://'), livePlayerDiv);
  100. if (cameraUrl.addr.includes('0.0.0.0')) {
  101. cameraUrl.addr = cameraUrl.addr.replace('0.0.0.0', window.location.hostname);
  102. }
  103. if (cameraUrl.addr && cameraUrl.addr.startsWith('rtsp://')) {
  104. const server = webRtcServer?.shift();
  105. let videoDom: HTMLVideoElement | null = null;
  106. if (server) {
  107. try {
  108. videoDom = server.videoElement as HTMLVideoElement;
  109. videoDom.volume = 0;
  110. const cameraNameDom = videoDom.parentElement?.getElementsByClassName('video-name')[0];
  111. if (cameraNameDom) cameraNameDom.innerText = cameraUrl.name;
  112. playerDoms.unshift(videoDom);
  113. newWebRtcServer.unshift(server);
  114. // videoParentDomList.unshift()
  115. await server.connect(cameraUrl['addr']);
  116. videoDom.play();
  117. childResolve(null);
  118. } catch (error) {
  119. playerDoms.unshift(undefined);
  120. childResolve(null);
  121. }
  122. } else {
  123. videoDom = document.createElement('video');
  124. videoDom.volume = 0;
  125. videoDom.setAttribute('class', 'rtspVideo');
  126. videoDom.setAttribute('muted', 'muted');
  127. videoDom.setAttribute('poster', '/src/assets/images/vent/noSinge.png');
  128. videoDom.autoplay = true;
  129. try {
  130. const server = new window['WebRtcStreamer'](
  131. videoDom,
  132. VUE_APP_URL.webRtcUrl.startsWith('/') ? location.protocol + VUE_APP_URL.webRtcUrl : VUE_APP_URL.webRtcUrl
  133. );
  134. newWebRtcServer.unshift(server);
  135. await server.connect(cameraUrl.addr);
  136. videoDom.play();
  137. playerDoms.unshift(videoDom);
  138. childResolve(null);
  139. } catch (error) {
  140. console.log('WebRtcStreamer 抛出异常!!!!!!');
  141. playerDoms.unshift(null);
  142. childResolve(null);
  143. }
  144. }
  145. if (videoDom) {
  146. const videoParentDom: HTMLElement = document.createElement('div');
  147. videoParentDom.setAttribute('class', 'video-parent');
  148. videoParentDom.appendChild(videoDom);
  149. cameraNameDom = document.createElement('div');
  150. cameraNameDom.setAttribute('class', 'video-name');
  151. cameraNameDom.innerText = cameraUrl.name;
  152. videoParentDom.appendChild(cameraNameDom);
  153. videoParentDom.addEventListener('dblclick', () => {
  154. if (videoDom?.requestFullscreen) {
  155. videoDom.requestFullscreen();
  156. videoDom.play();
  157. }
  158. });
  159. videoParentDomList.push(videoParentDom);
  160. }
  161. } else {
  162. videoParentDomList.push(['player' + index, cameraUrl]);
  163. childResolve(null);
  164. }
  165. });
  166. promiseList.push(promise);
  167. });
  168. Promise.all(promiseList).then(() => {
  169. resolve(null);
  170. });
  171. } else {
  172. resolve(null);
  173. }
  174. };
  175. playCamrea();
  176. }).then(() => {
  177. videoParentDomList.forEach((videoParent) => {
  178. if (typeof videoParent[0] === 'string' && livePlayerDiv) {
  179. const videoParentDom: HTMLElement = document.createElement('div');
  180. videoParentDom.setAttribute('class', 'liveVideo');
  181. livePlayerDiv?.appendChild(videoParentDom);
  182. useDrag(videoParentDom);
  183. const videoDom: HTMLElement = document.createElement('div');
  184. videoDom.setAttribute('id', videoParent[0]);
  185. videoParentDom.appendChild(videoDom);
  186. if (videoParent[1] && videoParent[1].addr) {
  187. const fileExtension = videoParent[1].addr.split('.').pop();
  188. const player = getPlayer(fileExtension, videoParent[0], videoParent[1].devicekind, videoParent[1].addr, videoParent[1].cameraRate);
  189. // let player;
  190. // if (fileExtension === 'flv' || videoParent[1].devicekind == 'flv') {
  191. // player = new Player({
  192. // lang: 'zh',
  193. // id: videoParent[0],
  194. // url: videoParent[1].addr,
  195. // width: 294,
  196. // height: 188,
  197. // poster: '/src/assets/images/vent/noSinge.png',
  198. // plugins: [FlvPlugin],
  199. // fluid: true,
  200. // autoplay: true,
  201. // isLive: true,
  202. // playsinline: true,
  203. // screenShot: true,
  204. // whitelist: [''],
  205. // ignores: ['time', 'progress', 'play', 'i18n', 'volume', 'fullscreen', 'screenShot', 'playbackRate'],
  206. // closeVideoClick: true,
  207. // customConfig: {
  208. // isClickPlayBack: false,
  209. // },
  210. // controlsList: ['nodownload', 'nofullscreen', 'noremoteplayback'],
  211. // defaultPlaybackRate: videoParent[1].cameraRate || 1,
  212. // flv: {
  213. // retryCount: 3, // 重试 3 次,默认值
  214. // retryDelay: 1000, // 每次重试间隔 1 秒,默认值
  215. // loadTimeout: 10000, // 请求超时时间为 10 秒,默认值
  216. // fetchOptions: {
  217. // // 该参数会透传给 fetch,默认值为 undefined
  218. // mode: 'cors',
  219. // },
  220. // targetLatency: 10, // 直播目标延迟,默认 10 秒
  221. // maxLatency: 20, // 直播允许的最大延迟,默认 20 秒
  222. // disconnectTime: 10, // 直播断流时间,默认 0 秒,(独立使用时等于 maxLatency)
  223. // maxJumpDistance: 10,
  224. // },
  225. // });
  226. // playerList.push(player);
  227. // }
  228. // if (fileExtension === 'm3u8' || videoParent[1].devicekind == 'm3u8') {
  229. // let player;
  230. // if (document.createElement('video').canPlayType('application/vnd.apple.mpegurl')) {
  231. // // 原生支持 hls 播放
  232. // player = new Player({
  233. // lang: 'zh',
  234. // id: videoParent[0],
  235. // url: videoParent[1].addr,
  236. // width: 294,
  237. // height: 188,
  238. // isLive: true,
  239. // autoplay: true,
  240. // autoplayMuted: true,
  241. // cors: true,
  242. // ignores: ['time', 'progress', 'play', 'i18n', 'volume', 'fullscreen', 'screenShot', 'playbackRate'],
  243. // poster: '/src/assets/images/vent/noSinge.png',
  244. // defaultPlaybackRate: videoParent[1].cameraRate || 1,
  245. // controlsList: ['nodownload', 'nofullscreen', 'noremoteplayback'],
  246. // hls: {
  247. // retryCount: 10, // 重试 3 次,默认值
  248. // retryDelay: 30000, // 每次重试间隔 1 秒,默认值
  249. // loadTimeout: 10000, // 请求超时时间为 10 秒,默认值
  250. // disconnectTime: 30, //直播断流时间,
  251. // fetchOptions: {
  252. // // 该参数会透传给 fetch,默认值为 undefined
  253. // mode: 'cors',
  254. // },
  255. // targetLatency: 10, // 直播目标延迟,默认 10 秒
  256. // maxLatency: 20, // 直播允许的最大延迟,默认 20 秒
  257. // maxJumpDistance: 10,
  258. // },
  259. // });
  260. // } else if (HlsPlugin.isSupported()) {
  261. // // 第一步
  262. // player = new Player({
  263. // lang: 'zh',
  264. // id: videoParent[0],
  265. // url: videoParent[1].addr,
  266. // width: 294,
  267. // height: 188,
  268. // isLive: true,
  269. // autoplay: true,
  270. // autoplayMuted: true,
  271. // ignores: ['time', 'progress', 'play', 'i18n', 'volume', 'fullscreen', 'screenShot', 'playbackRate'],
  272. // plugins: [HlsPlugin], // 第二步
  273. // poster: '/src/assets/images/vent/noSinge.png',
  274. // defaultPlaybackRate: videoParent[1].cameraRate || 1,
  275. // controlsList: ['nodownload', 'nofullscreen', 'noremoteplayback'],
  276. // hls: {
  277. // retryCount: 10, // 重试 3 次,默认值
  278. // retryDelay: 30000, // 每次重试间隔 1 秒,默认值
  279. // loadTimeout: 10000, // 请求超时时间为 10 秒,默认值
  280. // disconnectTime: 30,
  281. // fetchOptions: {
  282. // // 该参数会透传给 fetch,默认值为 undefined
  283. // mode: 'cors',
  284. // },
  285. // targetLatency: 10, // 直播目标延迟,默认 10 秒
  286. // maxLatency: 20, // 直播允许的最大延迟,默认 20 秒
  287. // maxJumpDistance: 10,
  288. // },
  289. // });
  290. // }
  291. // playerList.push(player);
  292. // }
  293. playerList.push(player);
  294. }
  295. } else {
  296. useDrag(videoParent as HTMLElement);
  297. livePlayerDiv?.appendChild(videoParent as Node);
  298. }
  299. });
  300. const players = livePlayerDiv?.getElementsByClassName('player');
  301. if (players && players.length) {
  302. for (let i = 0; i < players.length; i++) {
  303. try {
  304. const isCanPlayer = !players[i].getAttribute('id')?.startsWith('onPlayer');
  305. const dom = players[i].getElementsByTagName('video')[0];
  306. if (dom && isCanPlayer) {
  307. playerDoms.unshift(dom);
  308. } else {
  309. playerDoms.unshift(null);
  310. }
  311. } catch (error) {
  312. console.log('可以捕获到异常吗?????');
  313. playerDoms.unshift(null);
  314. }
  315. }
  316. }
  317. if (webRtcServer.length > 0) {
  318. for (let i = 0; i < webRtcServer.length; i++) {
  319. webRtcServer[i].videoElement.parentElement.remove();
  320. webRtcServer[i].disconnect();
  321. webRtcServer[i] = undefined;
  322. }
  323. }
  324. webRtcServer.length = 0;
  325. webRtcServer = newWebRtcServer;
  326. });
  327. }
  328. function getPlayer(fileExtension, playerDomId, camerakind, cameraUrl, cameraRate, option = { width: 294, height: 188 }) {
  329. let player;
  330. if (fileExtension === 'flv' || camerakind == 'flv') {
  331. player = new Player({
  332. lang: 'zh',
  333. id: playerDomId,
  334. url: cameraUrl,
  335. width: option.width,
  336. height: option.height,
  337. poster: '/src/assets/images/vent/noSinge.png',
  338. plugins: [FlvPlugin],
  339. fluid: true,
  340. autoplay: true,
  341. isLive: true,
  342. playsinline: true,
  343. screenShot: true,
  344. whitelist: [''],
  345. ignores: ['time', 'progress', 'play', 'i18n', 'volume', 'fullscreen', 'screenShot', 'playbackRate'],
  346. closeVideoClick: true,
  347. customConfig: {
  348. isClickPlayBack: false,
  349. },
  350. controlsList: ['nodownload', 'nofullscreen', 'noremoteplayback'],
  351. defaultPlaybackRate: cameraRate || 1,
  352. flv: {
  353. retryCount: 3, // 重试 3 次,默认值
  354. retryDelay: 1000, // 每次重试间隔 1 秒,默认值
  355. loadTimeout: 10000, // 请求超时时间为 10 秒,默认值
  356. fetchOptions: {
  357. // 该参数会透传给 fetch,默认值为 undefined
  358. mode: 'cors',
  359. },
  360. targetLatency: 10, // 直播目标延迟,默认 10 秒
  361. maxLatency: 20, // 直播允许的最大延迟,默认 20 秒
  362. disconnectTime: 10, // 直播断流时间,默认 0 秒,(独立使用时等于 maxLatency)
  363. maxJumpDistance: 10,
  364. },
  365. });
  366. // playerList.push(player);
  367. }
  368. if (fileExtension === 'm3u8' || camerakind == 'm3u8') {
  369. if (document.createElement('video').canPlayType('application/vnd.apple.mpegurl')) {
  370. // 原生支持 hls 播放
  371. player = new Player({
  372. lang: 'zh',
  373. id: playerDomId,
  374. url: cameraUrl,
  375. width: option.width,
  376. height: option.height,
  377. isLive: true,
  378. autoplay: true,
  379. autoplayMuted: true,
  380. cors: true,
  381. ignores: ['time', 'progress', 'play', 'i18n', 'volume', 'fullscreen', 'screenShot', 'playbackRate'],
  382. poster: '/src/assets/images/vent/noSinge.png',
  383. defaultPlaybackRate: cameraRate || 1,
  384. controlsList: ['nodownload', 'nofullscreen', 'noremoteplayback'],
  385. hls: {
  386. retryCount: 10, // 重试 3 次,默认值
  387. retryDelay: 30000, // 每次重试间隔 1 秒,默认值
  388. loadTimeout: 10000, // 请求超时时间为 10 秒,默认值
  389. disconnectTime: 30, //直播断流时间,
  390. fetchOptions: {
  391. // 该参数会透传给 fetch,默认值为 undefined
  392. mode: 'cors',
  393. },
  394. targetLatency: 10, // 直播目标延迟,默认 10 秒
  395. maxLatency: 20, // 直播允许的最大延迟,默认 20 秒
  396. maxJumpDistance: 10,
  397. },
  398. });
  399. } else if (HlsPlugin.isSupported()) {
  400. // 第一步
  401. player = new Player({
  402. lang: 'zh',
  403. id: playerDomId,
  404. url: cameraUrl,
  405. width: option.width,
  406. height: option.height,
  407. isLive: true,
  408. autoplay: true,
  409. autoplayMuted: true,
  410. ignores: ['time', 'progress', 'play', 'i18n', 'volume', 'fullscreen', 'screenShot', 'playbackRate'],
  411. plugins: [HlsPlugin], // 第二步
  412. poster: '/src/assets/images/vent/noSinge.png',
  413. defaultPlaybackRate: cameraRate || 1,
  414. controlsList: ['nodownload', 'nofullscreen', 'noremoteplayback'],
  415. hls: {
  416. retryCount: 10, // 重试 3 次,默认值
  417. retryDelay: 30000, // 每次重试间隔 1 秒,默认值
  418. loadTimeout: 10000, // 请求超时时间为 10 秒,默认值
  419. disconnectTime: 30,
  420. fetchOptions: {
  421. // 该参数会透传给 fetch,默认值为 undefined
  422. mode: 'cors',
  423. },
  424. targetLatency: 10, // 直播目标延迟,默认 10 秒
  425. maxLatency: 20, // 直播允许的最大延迟,默认 20 秒
  426. maxJumpDistance: 10,
  427. },
  428. });
  429. }
  430. // playerList.push(player);
  431. }
  432. return player;
  433. }
  434. function removeCamera() {
  435. if (webRtcServer.length > 0) {
  436. webRtcServer.forEach((item) => {
  437. item.disconnect();
  438. });
  439. webRtcServer = [];
  440. }
  441. playerDoms.forEach((dom) => {
  442. dom?.remove();
  443. });
  444. playerList.forEach((player) => {
  445. if (player && player.destroy) player.destroy();
  446. player = null;
  447. });
  448. videoParentDomList.length = 0;
  449. playerDoms.length = 0;
  450. playerList.length = 0;
  451. }
  452. return {
  453. getCamera,
  454. webRtcServer,
  455. playerDoms,
  456. deviceCameraInit,
  457. removeCamera,
  458. getPlayer,
  459. };
  460. }