index.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. <template>
  2. <div v-if="addrList.length > 0">
  3. <div class="vent-flex-row-wrap camera-box" >
  4. <div v-for="(item, index) in addrList" :key="index" class="player-box">
  5. <div class="player-name">{{ item.name }}</div>
  6. <div>
  7. <template v-if="item.addr.startsWith('rtsp://')">
  8. <video :id="`video${index}`" muted autoplay></video>
  9. <div class="click-box" @dblclick="goFullScreen(`video${index}`)"></div>
  10. </template>
  11. <template v-else>
  12. <div :id="'player'+index" ></div>
  13. </template>
  14. </div>
  15. </div>
  16. </div>
  17. <div class="pagination">
  18. <Pagination v-model:current="current" :total="total" show-less-items @change="onChange"/>
  19. </div>
  20. </div>
  21. <div class="camera-box" v-else>
  22. <Empty />
  23. </div>
  24. </template>
  25. <script lang="ts" setup>
  26. import {onMounted, onUnmounted, ref } from 'vue';
  27. import { Pagination, Empty } from 'ant-design-vue';
  28. import { list, cameraAddr } from './camera.api'
  29. import Player from 'xgplayer';
  30. import HlsPlugin from 'xgplayer-hls';
  31. import FlvPlugin from 'xgplayer-flv';
  32. import 'xgplayer/dist/index.min.css';
  33. const pageSize = 8
  34. const current = ref(1)
  35. const total = ref(0)
  36. const playerList = ref([])
  37. const webRtcServerList = <any[]>[]
  38. let addrList = ref<{ name: string, addr: string }[]>([])
  39. async function getVideoAddrs(){
  40. clearCamera();
  41. playerList.value = []
  42. const result = await list({ pageSize: pageSize, pageNo: current.value })
  43. if(result && result.records && result.records.length > 0){
  44. total.value = result['total']
  45. const cameraList = <{ name: string, addr: string }[]>[]
  46. const cameras = result.records
  47. // const camerasArr: [] = [
  48. // {
  49. // name: '1111',
  50. // devicekind: 'toHKRtsp',
  51. // },
  52. // {
  53. // name: '2222',
  54. // devicekind: 'toHKRtsp',
  55. // },
  56. // {
  57. // name: '3333',
  58. // devicekind: 'toHKRtsp',
  59. // },
  60. // {
  61. // name: '4444',
  62. // devicekind: 'toHKRtsp',
  63. // },
  64. // {
  65. // name: '5555',
  66. // devicekind: 'toHKRtsp',
  67. // },
  68. // {
  69. // name: '6666',
  70. // devicekind: 'toHKRtsp',
  71. // },
  72. // {
  73. // name: '7777',
  74. // devicekind: 'toHKRtsp',
  75. // },
  76. // {
  77. // name: '8888',
  78. // devicekind: 'toHKRtsp',
  79. // },
  80. // {
  81. // name: '9999',
  82. // devicekind: 'toHKRtsp',
  83. // },
  84. // {
  85. // name: 'aaaa',
  86. // devicekind: 'toHKRtsp',
  87. // },
  88. // {
  89. // name: 'bbbb',
  90. // devicekind: 'toHKRtsp',
  91. // },
  92. // {
  93. // name: 'cccc',
  94. // devicekind: 'toHKRtsp',
  95. // },
  96. // ];
  97. // const cameras = []
  98. // for (let index = (current.value - 1)*pageSize; index < current.value * pageSize && index < camerasArr.length ; index++) {
  99. // cameras.push(camerasArr[index]) ;
  100. // }
  101. for (let i = 0; i < cameras.length; i++) {
  102. const item = cameras[i];
  103. if (item['devicekind'] === 'toHKRtsp') {
  104. // 从海康平台接口获取视频流
  105. const data = await cameraAddr({ cameraCode: item['addr'] });
  106. if (data && data['url']) {
  107. cameraList.push({ name: item['name'], addr: data['url'] });
  108. }
  109. // cameraList.push({
  110. // name: item['name'],
  111. // // addr: 'http://219.151.31.38/liveplay-kk.rtxapp.com/live/program/live/hnwshd/4000000/mnf.m3u8'
  112. // addr: 'https://demo.unified-streaming.com/k8s/features/stable/video/tears-of-steel/tears-of-steel.mp4/.m3u8',
  113. // });
  114. } else {
  115. if(item['addr'].includes('0.0.0.0')){
  116. item['addr'].replace('0.0.0.0', window.location.hostname)
  117. }
  118. cameraList.push({ name: item['name'], addr: item['addr'] });
  119. }
  120. }
  121. addrList.value = cameraList
  122. }
  123. }
  124. function onChange(page) {
  125. current.value = page;
  126. getVideoAddrs().then(() => {
  127. getVideo()
  128. })
  129. }
  130. function getVideo() {
  131. const ip = VUE_APP_URL.webRtcUrl;
  132. for(let i = 0; i < addrList.value.length; i++){
  133. const item = addrList.value[i]
  134. if(item.addr.startsWith('rtsp://')){
  135. const dom = document.getElementById('video' + i) as HTMLVideoElement
  136. dom.muted = true;
  137. dom.volume = 0
  138. const webRtcServer = new window['WebRtcStreamer'](dom, location.protocol + ip)
  139. webRtcServerList.push(webRtcServer)
  140. webRtcServer.connect(item.addr)
  141. }else{
  142. setNoRtspVideo('player'+i,item.addr )
  143. }
  144. }
  145. }
  146. function setNoRtspVideo(id,videoAddr) {
  147. const fileExtension = videoAddr.split('.').pop();
  148. // const plugins = fileExtension === 'flv' ? [ FlvPlugin ] : [ HlsPlugin ];
  149. if(fileExtension === 'flv'){
  150. const player = new Player({
  151. id: id,
  152. url: videoAddr,
  153. width: 421,
  154. height: 292,
  155. poster: '/src/assets/images/vent/noSinge.png',
  156. plugins: [ FlvPlugin ] ,
  157. fluid: true,
  158. autoplay: true,
  159. isLive: true,
  160. playsinline: false,
  161. screenShot: true,
  162. whitelist: [''],
  163. ignores: ['time'],
  164. closeVideoClick: true,
  165. customConfig: {
  166. isClickPlayBack: false
  167. },
  168. flv: {
  169. retryCount: 3, // 重试 3 次,默认值
  170. retryDelay: 1000, // 每次重试间隔 1 秒,默认值
  171. loadTimeout: 10000, // 请求超时时间为 10 秒,默认值
  172. fetchOptions: {
  173. // 该参数会透传给 fetch,默认值为 undefined
  174. mode: 'cors'
  175. },
  176. targetLatency: 10, // 直播目标延迟,默认 10 秒
  177. maxLatency: 20, // 直播允许的最大延迟,默认 20 秒
  178. disconnectTime: 10, // 直播断流时间,默认 0 秒,(独立使用时等于 maxLatency)
  179. maxJumpDistance: 10,
  180. }
  181. });
  182. playerList.value.push(player)
  183. }
  184. if(fileExtension === 'm3u8'){
  185. let player
  186. if (document.createElement('video').canPlayType('application/vnd.apple.mpegurl')) {
  187. // 原生支持 hls 播放
  188. player = new Player({
  189. id: id,
  190. url: videoAddr,
  191. width: 421,
  192. height: 292,
  193. isLive: true,
  194. autoplay: true,
  195. autoplayMuted: true,
  196. cors: true,
  197. poster: '/src/assets/images/vent/noSinge.png',
  198. hls: {
  199. retryCount: 3, // 重试 3 次,默认值
  200. retryDelay: 1000, // 每次重试间隔 1 秒,默认值
  201. loadTimeout: 10000, // 请求超时时间为 10 秒,默认值
  202. fetchOptions: {
  203. // 该参数会透传给 fetch,默认值为 undefined
  204. mode: 'cors'
  205. },
  206. targetLatency: 10, // 直播目标延迟,默认 10 秒
  207. maxLatency: 20, // 直播允许的最大延迟,默认 20 秒
  208. disconnectTime: 10, // 直播断流时间,默认 0 秒,(独立使用时等于 maxLatency)
  209. maxJumpDistance: 10,
  210. }
  211. })
  212. } else if (HlsPlugin.isSupported()) { // 第一步
  213. player = new Player({
  214. id: id,
  215. url: videoAddr,
  216. width: 421,
  217. height: 292,
  218. isLive: true,
  219. autoplay: true,
  220. autoplayMuted: true,
  221. plugins: [HlsPlugin], // 第二步
  222. poster: '/src/assets/images/vent/noSinge.png',
  223. hls: {
  224. retryCount: 3, // 重试 3 次,默认值
  225. retryDelay: 1000, // 每次重试间隔 1 秒,默认值
  226. loadTimeout: 10000, // 请求超时时间为 10 秒,默认值
  227. fetchOptions: {
  228. // 该参数会透传给 fetch,默认值为 undefined
  229. mode: 'cors'
  230. },
  231. targetLatency: 10, // 直播目标延迟,默认 10 秒
  232. maxLatency: 20, // 直播允许的最大延迟,默认 20 秒
  233. disconnectTime: 10, // 直播断流时间,默认 0 秒,(独立使用时等于 maxLatency)
  234. maxJumpDistance: 10,
  235. }
  236. })
  237. }
  238. playerList.value.push(player)
  239. }
  240. }
  241. function goFullScreen(domId) {
  242. const videoDom = document.getElementById(domId) as HTMLVideoElement
  243. if(videoDom.requestFullscreen){
  244. videoDom.requestFullscreen()
  245. videoDom.play()
  246. } else if(videoDom.mozRequestFullscreen){
  247. videoDom.mozRequestFullscreen()
  248. videoDom.play()
  249. } else if (videoDom.webkitRequestFullscreen) {
  250. videoDom.webkitRequestFullscreen()
  251. videoDom.play()
  252. } else if (videoDom.msRequestFullscreen) {
  253. videoDom.msRequestFullscreen()
  254. videoDom.play()
  255. }
  256. }
  257. function clearCamera() {
  258. const num = webRtcServerList.length
  259. for (let i = 0; i < num; i++) {
  260. webRtcServerList[i].disconnect()
  261. webRtcServerList[i] = null
  262. }
  263. for(let i = 0; i < playerList.value.length; i++){
  264. const player = playerList.value[i]
  265. if(player.destroy)player.destroy()
  266. }
  267. }
  268. onMounted(async() => {
  269. await getVideoAddrs()
  270. getVideo()
  271. })
  272. onUnmounted(() => {
  273. clearCamera()
  274. })
  275. </script>
  276. <style lang="less">
  277. .camera-box{
  278. height: 700px;
  279. overflow-y: auto;
  280. display: flex;
  281. justify-content: center;
  282. align-items: center;
  283. .player-box{
  284. width: 451px;
  285. height: 312px;
  286. padding: 10px;
  287. background: url('/@/assets/images/vent/camera_bg.png');
  288. background-size: cover;
  289. position: relative;
  290. margin: 10px;
  291. .player-name{
  292. font-size: 14px;
  293. position: absolute;
  294. top: 15px;
  295. right: 15px;
  296. color: #fff;
  297. background-color: hsla(0, 0%, 50%, .5);
  298. border-radius: 2px;
  299. padding: 1px 5px;
  300. max-width: 120px;
  301. overflow: hidden;
  302. white-space: nowrap;
  303. text-overflow: ellipsis;
  304. z-index: 999;
  305. }
  306. .click-box{
  307. position: absolute;
  308. width: 100%;
  309. height: 100%;
  310. top: 0;
  311. left: 0;
  312. }
  313. }
  314. }
  315. .pagination{
  316. width: 100%;
  317. height: 200px;
  318. display: flex;
  319. justify-content: center;
  320. align-items: center;
  321. }
  322. // :deep(video){
  323. // width: 100% !important;
  324. // height: 100% !important;
  325. // object-fit: cover !important;
  326. // }
  327. </style>