PartBox.vue 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. <!-- 细节 -->
  2. <template>
  3. <div class="part-box-wrapper h-full w-full flex flex-col gap-6px">
  4. <div
  5. :class="['part-box', partActive === i ? 'active' : '']"
  6. v-for="(item, i) in partUrl"
  7. :key="item.UUID"
  8. cursor-pointer
  9. relative
  10. @click="partActive = i"
  11. >
  12. <div :class="[`screen-full-target${i}`, 'w-full aspect-ratio-video']">
  13. <template v-if="item.UUID">
  14. <pub-video :new-class="'playback-video' + item.UUID" :is-scale="false" />
  15. <!-- 加载 -->
  16. <div
  17. v-if="item.loading"
  18. absolute
  19. top-0
  20. right-0
  21. left-0
  22. bottom-0
  23. flex
  24. flex-justify-center
  25. flex-items-center
  26. >
  27. <Icon icon="loading" color="#007bff" width="60" />
  28. </div>
  29. </template>
  30. <!-- 操作按钮 -->
  31. <div
  32. class="panoramic-situation-main-btn-box"
  33. absolute
  34. top-1vw
  35. right-1vw
  36. flex
  37. flex-row-reverse
  38. >
  39. <OperatingButton @closeWorker="closeWorkerBtn" :i="i" :part="item" />
  40. </div>
  41. </div>
  42. </div>
  43. </div>
  44. </template>
  45. <script setup>
  46. import useWorker from '@/hooks/useWorker'
  47. import { useOutsideSystemStore } from '@/stores/modules/system.js'
  48. import { useDebounceFn } from '@vueuse/core'
  49. import OperatingButton from './OperatingButton.vue'
  50. import useWheel from '@/hooks/useWheel'
  51. const route = useRoute()
  52. const useSystem = useOutsideSystemStore()
  53. const { API_VIEW_PLAYBACK_POST } = useRequest()
  54. const props = defineProps({
  55. timeSegmentsObj: {
  56. type: Object
  57. },
  58. currentTime: {
  59. type: Number
  60. }
  61. })
  62. const emits = defineEmits(['successOpen'])
  63. const partActive = ref(0)
  64. const partUrl = reactive([
  65. {
  66. UUID: '',
  67. PartCenterX: '',
  68. PartCenterY: '',
  69. loading: false
  70. },
  71. {
  72. UUID: '',
  73. PartCenterX: '',
  74. PartCenterY: '',
  75. loading: false
  76. }
  77. // {
  78. // UUID: '',
  79. // PartCenterX: '',
  80. // PartCenterY: ''
  81. // },
  82. // {
  83. // UUID: '',
  84. // PartCenterX: '',
  85. // PartCenterY: ''
  86. // }
  87. ])
  88. const workerObj = {}
  89. let cloneParts = null
  90. const getWss = (type, timestamp, x = 0, y = 0, index) => {
  91. return new Promise((resolve, reject) => {
  92. API_VIEW_PLAYBACK_POST({
  93. NvrId: useSystem.nvrId,
  94. Type: type,
  95. UTCDiff: new Date().getTimezoneOffset(),
  96. Timestamp: timestamp,
  97. X: x,
  98. Y: y
  99. }).then((res) => {
  100. if (type) {
  101. partUrl[index].UUID = res.wss
  102. partUrl[index].PartCenterX = x
  103. partUrl[index].PartCenterY = y
  104. partUrl[index].loading = true
  105. }
  106. resolve(res)
  107. })
  108. })
  109. }
  110. const openWork = (res, index, cName = '.rpb-video') => {
  111. return new Promise((resolve) => {
  112. const url = `wss://${useSystem.ip.split('//')[1]}/VideoShow/Common?UUID=${res.wss}`
  113. if (!cName) {
  114. nextTick(() => {
  115. workerObj[index] = useWorker(url, '.playback-video' + res.wss, () => {
  116. const partV = document.querySelector('.playback-video' + res.wss)
  117. partV.parentElement.style.height = '100%'
  118. partUrl[index].loading = false
  119. })
  120. useWheel(
  121. document.querySelector('.playback-video' + res.wss),
  122. partUrl[index],
  123. '.playback-video'
  124. )
  125. cloneParts = JSON.parse(JSON.stringify(partUrl))
  126. resolve(true)
  127. })
  128. } else {
  129. nextTick(() => {
  130. workerObj['qj'] = useWorker(url, cName)
  131. emits('successOpen')
  132. })
  133. }
  134. })
  135. }
  136. const closeWorkerBtn = (index) => {
  137. closeWorker(index)
  138. cloneParts = JSON.parse(JSON.stringify(partUrl))
  139. }
  140. // 关闭线程
  141. const closeWorker = async (index) => {
  142. if (!workerObj[index]) return
  143. workerObj[index].WebSocketWork.terminate()
  144. workerObj[index].worker.terminate()
  145. Object.assign(partUrl[index], {
  146. UUID: '',
  147. PartCenterX: '',
  148. PartCenterY: '',
  149. loading: false
  150. })
  151. }
  152. const init = async (beginTime) => {
  153. await useSystem.resetQj(workerObj['qj'])
  154. getWss(0, beginTime).then((res) => {
  155. openWork(res)
  156. })
  157. partUrl.forEach((_, i) => closeWorker(i))
  158. let promises = []
  159. await new Promise((resolve) => {
  160. nextTick(() => {
  161. cloneParts?.forEach((item, i) => {
  162. if (item.UUID) {
  163. promises.push(getWss(1, beginTime, item.PartCenterX, item.PartCenterY, i))
  164. }
  165. })
  166. Promise.all(promises).then((resArr) => {
  167. resArr.forEach((res, i) => {
  168. openWork(res, i, null, cloneParts[i].PartCenterX, cloneParts[i].PartCenterY)
  169. })
  170. })
  171. })
  172. })
  173. }
  174. const remoteLayout = () => {
  175. const remoteBox = document.querySelector('.remote-playback-layout')
  176. if (innerWidth <= 1024) {
  177. remoteBox && (remoteBox.style.minHeight = 'auto')
  178. } else {
  179. const h = innerHeight - document.querySelector('.main-box').offsetTop - 139 + 'px'
  180. remoteBox && (remoteBox.style.minHeight = h), (remoteBox.style.maxHeight = h)
  181. }
  182. }
  183. const resize = useDebounceFn(remoteLayout, 500)
  184. watch(
  185. () => useSystem.partObj,
  186. (newV) => {
  187. if (route.name != 'RemotePlayback' || !workerObj['qj']) return
  188. closeWorker(partActive.value)
  189. getWss(
  190. 1,
  191. +`${props.currentTime}`.slice(0, 10),
  192. +newV.PartCenterX,
  193. +newV.PartCenterY,
  194. partActive.value
  195. ).then((res) => {
  196. openWork(res, partActive.value, null)
  197. })
  198. },
  199. { deep: true }
  200. )
  201. // 防抖防止接口多次调用问题
  202. watch(
  203. () => props.timeSegmentsObj,
  204. useDebounceFn((newV) => {
  205. init(+newV.beginTime.slice(0, 10))
  206. }, 800),
  207. { deep: true }
  208. )
  209. onMounted(() => {
  210. useSystem.getVideoArrange().then(() => {
  211. remoteLayout()
  212. addEventListener('resize', resize)
  213. })
  214. })
  215. onUnmounted(() => {
  216. partUrl.forEach((item, i) => closeWorker(i))
  217. workerObj['qj']?.WebSocketWork.terminate()
  218. workerObj['qj']?.worker.terminate()
  219. // removeEventListener('resize', resize)
  220. })
  221. // onDeactivated(() => {
  222. // partUrl.forEach((item, i) => closeWorker(i))
  223. // workerObj['qj']?.terminate()
  224. // })
  225. </script>
  226. <style scoped lang="scss">
  227. .part-box {
  228. width: 100%;
  229. border: 1px solid;
  230. border-color: #007bff;
  231. &.active {
  232. border-color: red;
  233. }
  234. }
  235. </style>