videoStore.js 2.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546
  1. import { shallowRef } from 'vue'
  2. import { defineStore } from 'pinia'
  3. import { createVideoApi, VideoRequestError } from '../services/videoApi.js'
  4. import { createVideoLiveSession } from '../services/videoLiveSession.js'
  5. import { useDetailStore } from './detailStore.js'
  6. import { useEnhancementDevicesStore } from './enhancementDevicesStore.js'
  7. export const useVideoStore = defineStore('cockpitVideo', () => {
  8. const state = shallowRef({ open: false, device: null, ownerSource: 'detail', phase: 'idle', media: null, error: '', cleanupError: '' })
  9. const playerOptions = shallowRef({})
  10. let options = {}, controller
  11. const publish = (patch) => { state.value = { ...state.value, ...patch } }
  12. function configure(next = {}) {
  13. if (controller) throw new Error('请在建立视频会话前配置传输')
  14. options = next
  15. playerOptions.value = next.playerOptions || {}
  16. }
  17. const session = () => controller || (controller = createVideoLiveSession(createVideoApi(options), publish, options.timers))
  18. async function loadDevice(deviceCode) {
  19. const data = await createVideoApi(options).device(deviceCode)
  20. if (data.deviceCode !== deviceCode || typeof data.typeCode !== 'string' || typeof data.capabilities?.live !== 'boolean') {
  21. throw new VideoRequestError('INVALID_RESPONSE', '视频能力载荷与当前设备不匹配')
  22. }
  23. return data
  24. }
  25. async function open(device, ownerSource = 'detail') {
  26. if (!['detail', 'enhancement'].includes(ownerSource)) throw new Error('监控入口来源不合法')
  27. const selection = useDetailStore().selection
  28. const selectedCode = ownerSource === 'enhancement' ? useEnhancementDevicesStore().selected?.code
  29. : selection.level === 'equipment' ? selection.deviceCode : ''
  30. // A detached/late entry cannot take ownership from a still-valid viewer.
  31. if (!selectedCode || selectedCode !== device?.deviceCode) return
  32. if (state.value.open && state.value.device?.deviceCode === device?.deviceCode) { publish({ ownerSource }); return }
  33. publish({ open: true, device, ownerSource, error: '' })
  34. if (device?.typeCode !== '20' || device.bound !== true || device.capabilities?.live !== true) {
  35. publish({ phase: 'failed', error: '当前设备未确认实时视频能力' }); return
  36. }
  37. await session().start(device.deviceCode)
  38. }
  39. async function close() { publish({ open: false }); if (controller) await controller.stop() }
  40. async function retry() { if (state.value.device) await session().start(state.value.device.deviceCode) }
  41. async function retryCleanup() { if (controller) await controller.stop() }
  42. const ready = () => controller?.ready()
  43. const fail = (error) => controller?.fail(typeof error === 'string' ? error : error?.message || '视频播放失败')
  44. return { state, playerOptions, configure, loadDevice, open, close, retry, retryCleanup, ready, fail }
  45. })