@havue/solutions 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +0 -0
  2. package/bc-connect/README.md +3 -0
  3. package/bc-connect/dist/bc-connect.mjs +342 -0
  4. package/bc-connect/dist/bc-connect.umd.js +346 -0
  5. package/bc-connect/dist/types/src/index.d.ts +1 -0
  6. package/bc-connect/dist/types/src/manager.d.ts +135 -0
  7. package/bc-connect/package.json +38 -0
  8. package/bc-connect/src/index.ts +1 -0
  9. package/bc-connect/src/manager.ts +388 -0
  10. package/bc-connect/vite.config.ts +4 -0
  11. package/dist/solutions.full.js +8504 -0
  12. package/dist/solutions.full.min.js +18 -0
  13. package/dist/solutions.full.min.js.map +1 -0
  14. package/dist/solutions.mjs +2 -0
  15. package/dist/solutions.umd.js +18 -0
  16. package/dist/types/bc-connect/src/index.d.ts +1 -0
  17. package/dist/types/bc-connect/src/manager.d.ts +135 -0
  18. package/dist/types/src/index.d.ts +2 -0
  19. package/dist/types/ws-video-manager/src/hooks/useVideoPlay.d.ts +73 -0
  20. package/dist/types/ws-video-manager/src/index.d.ts +5 -0
  21. package/dist/types/ws-video-manager/src/loader/index.d.ts +2 -0
  22. package/dist/types/ws-video-manager/src/loader/websocket-loader.d.ts +62 -0
  23. package/dist/types/ws-video-manager/src/manager/index.d.ts +144 -0
  24. package/dist/types/ws-video-manager/src/render/drawer.d.ts +44 -0
  25. package/dist/types/ws-video-manager/src/render/index.d.ts +105 -0
  26. package/package.json +44 -0
  27. package/src/index.ts +2 -0
  28. package/vite.config.ts +4 -0
  29. package/ws-video-manager/README.md +3 -0
  30. package/ws-video-manager/dist/types/src/hooks/useVideoPlay.d.ts +73 -0
  31. package/ws-video-manager/dist/types/src/index.d.ts +5 -0
  32. package/ws-video-manager/dist/types/src/loader/index.d.ts +2 -0
  33. package/ws-video-manager/dist/types/src/loader/websocket-loader.d.ts +62 -0
  34. package/ws-video-manager/dist/types/src/manager/index.d.ts +144 -0
  35. package/ws-video-manager/dist/types/src/render/drawer.d.ts +44 -0
  36. package/ws-video-manager/dist/types/src/render/index.d.ts +105 -0
  37. package/ws-video-manager/dist/ws-video-manager.mjs +8132 -0
  38. package/ws-video-manager/dist/ws-video-manager.umd.js +8135 -0
  39. package/ws-video-manager/node_modules/.bin/tsc +17 -0
  40. package/ws-video-manager/node_modules/.bin/tsc.CMD +12 -0
  41. package/ws-video-manager/node_modules/.bin/tsc.ps1 +41 -0
  42. package/ws-video-manager/node_modules/.bin/tsserver +17 -0
  43. package/ws-video-manager/node_modules/.bin/tsserver.CMD +12 -0
  44. package/ws-video-manager/node_modules/.bin/tsserver.ps1 +41 -0
  45. package/ws-video-manager/package.json +41 -0
  46. package/ws-video-manager/src/hooks/useVideoPlay.ts +357 -0
  47. package/ws-video-manager/src/index.ts +6 -0
  48. package/ws-video-manager/src/loader/index.ts +3 -0
  49. package/ws-video-manager/src/loader/websocket-loader.ts +278 -0
  50. package/ws-video-manager/src/manager/index.ts +429 -0
  51. package/ws-video-manager/src/render/drawer.ts +255 -0
  52. package/ws-video-manager/src/render/index.ts +475 -0
  53. package/ws-video-manager/vite.config.ts +4 -0
@@ -0,0 +1,278 @@
1
+ // #region typedefine
2
+ /** 心跳配置 */
3
+ type HeartbeatConfigType = {
4
+ /** 只发送一次 */
5
+ once: boolean
6
+ /** 心跳消息 */
7
+ message: string
8
+ /** 时间间隔 */
9
+ interval?: number
10
+ }
11
+
12
+ /** 重连配置 */
13
+ type InterruptConfigType = {
14
+ /** 是否重连 */
15
+ reconnect: boolean
16
+ /** 最大重连次数 */
17
+ maxReconnectTimes: number
18
+ /** 每次重连延时 */
19
+ delay: number
20
+ }
21
+
22
+ export type WebSocketOptionsType = {
23
+ /** WebSocket 子协议 WebSocket(url: string, protocols: string | string[]) */
24
+ protocols?: string | string[]
25
+ /** WebSocket 连接所传输二进制数据的类型 */
26
+ binaryType?: WebSocket['binaryType']
27
+ heartbeat?: HeartbeatConfigType
28
+ interrupt?: InterruptConfigType
29
+ }
30
+ // #endregion typedefine
31
+
32
+ type EventType = {
33
+ open: Event
34
+ message: MessageEvent
35
+ close: CloseEvent
36
+ error: Event
37
+ reconnect: undefined
38
+ }
39
+
40
+ const defaultOptions: Required<WebSocketOptionsType> = {
41
+ protocols: '',
42
+ binaryType: 'arraybuffer',
43
+ heartbeat: {
44
+ once: false,
45
+ message: 'heartbeat',
46
+ interval: 5000
47
+ },
48
+ interrupt: {
49
+ reconnect: true,
50
+ maxReconnectTimes: 3,
51
+ delay: 3000
52
+ }
53
+ }
54
+
55
+ class WebSocketLoader {
56
+ private _url: string
57
+ private _protocols: string | string[]
58
+ private _binaryType: WebSocket['binaryType']
59
+ private _ws: WebSocket | null = null
60
+ private _heartbeatConfig: HeartbeatConfigType
61
+ private _heartbeatTimer: ReturnType<typeof setInterval> | null = null
62
+ // 事件回调管理
63
+ private _eventMap: Map<keyof EventType, Set<(...args: any) => void>> | null
64
+ private _isManualClose = false
65
+ private _interruptConfig: InterruptConfigType
66
+ private _interruptReconnectTimes = 0
67
+ private _interruptReconnectTimer: ReturnType<typeof setTimeout> | null = null
68
+
69
+ constructor(url: string, options?: WebSocketOptionsType) {
70
+ const realOptions = options ? Object.assign(defaultOptions, options) : defaultOptions
71
+
72
+ this._url = url
73
+ this._protocols = realOptions.protocols
74
+ this._binaryType = realOptions.binaryType
75
+ this._eventMap = new Map()
76
+ this._heartbeatConfig = realOptions.heartbeat
77
+ this._interruptConfig = realOptions.interrupt
78
+ }
79
+
80
+ get isConnecting() {
81
+ return this._ws ? true : false
82
+ }
83
+
84
+ private triggerEvents<T extends keyof EventType>(eventName: T, event: EventType[T]) {
85
+ if (!this._eventMap) return
86
+
87
+ const set = this._eventMap.get(eventName)
88
+
89
+ if (!set) return
90
+
91
+ for (const callback of set) {
92
+ callback(event)
93
+ }
94
+ }
95
+
96
+ private bindWebSocketEvents(socket: WebSocket) {
97
+ socket.onopen = (e: Event) => {
98
+ this._isManualClose = false
99
+ this._interruptReconnectTimes = 0
100
+ this._interruptReconnectTimer && clearTimeout(this._interruptReconnectTimer)
101
+ this.startHeartbeat()
102
+ this.triggerEvents('open', e)
103
+ }
104
+
105
+ socket.onerror = (e: Event) => {
106
+ this.stopHeartbeat()
107
+
108
+ console.error('error: websocket error.')
109
+
110
+ this.triggerEvents('error', e)
111
+ }
112
+
113
+ socket.onmessage = (e: MessageEvent) => {
114
+ this.triggerEvents('message', e)
115
+ }
116
+
117
+ socket.onclose = (e: CloseEvent) => {
118
+ if (this._isManualClose) {
119
+ this.triggerEvents('close', e)
120
+ return
121
+ }
122
+
123
+ // 非手动关闭导致连接意外中断
124
+ if (this._interruptConfig.reconnect && this._interruptReconnectTimes < this._interruptConfig.maxReconnectTimes) {
125
+ this._interruptReconnectTimes++
126
+
127
+ console.warn(
128
+ `websocket's connection is interrupt by accident, auto try to reconnect. try times: ${this._interruptReconnectTimes}`
129
+ )
130
+
131
+ this._interruptReconnectTimer && clearTimeout(this._interruptReconnectTimer)
132
+ this._interruptReconnectTimer = setTimeout(() => {
133
+ this.innerReconnect()
134
+ }, this._interruptConfig.delay)
135
+
136
+ return
137
+ }
138
+
139
+ if (
140
+ this._interruptConfig.reconnect &&
141
+ this._interruptReconnectTimes === this._interruptConfig.maxReconnectTimes
142
+ ) {
143
+ console.error(`try to reconnect ${this._interruptReconnectTimes} times, unable to establish connection.`)
144
+ this._ws = null
145
+ this._interruptReconnectTimes = 0
146
+ this.triggerEvents('close', e)
147
+ return
148
+ }
149
+ }
150
+ }
151
+
152
+ private startHeartbeat() {
153
+ if (this._heartbeatTimer) {
154
+ clearInterval(this._heartbeatTimer)
155
+ this._heartbeatTimer = null
156
+ }
157
+
158
+ const { message, interval, once } = this._heartbeatConfig
159
+
160
+ // 只发一次心跳
161
+ if (once) {
162
+ this.sendMessage(message)
163
+ return
164
+ }
165
+
166
+ this._heartbeatTimer = setInterval(() => {
167
+ this.sendMessage(message)
168
+ }, interval || 5000)
169
+ }
170
+
171
+ private stopHeartbeat() {
172
+ if (this._heartbeatTimer) {
173
+ clearInterval(this._heartbeatTimer)
174
+ this._heartbeatTimer = null
175
+ }
176
+ }
177
+
178
+ private innerReconnect() {
179
+ if (this._ws) {
180
+ this.stopHeartbeat()
181
+ this._isManualClose = false
182
+ this._ws = null
183
+ }
184
+
185
+ this._ws = this._protocols ? new WebSocket(this._url, this._protocols) : new WebSocket(this._url)
186
+ this._ws.binaryType = this._binaryType
187
+ this.bindWebSocketEvents(this._ws)
188
+ this.triggerEvents('reconnect', undefined)
189
+ }
190
+
191
+ public open() {
192
+ this._ws = this._protocols ? new WebSocket(this._url, this._protocols) : new WebSocket(this._url)
193
+ this._ws.binaryType = this._binaryType
194
+ this.bindWebSocketEvents(this._ws)
195
+ }
196
+
197
+ public close() {
198
+ if (!this._ws) return
199
+
200
+ this.stopHeartbeat()
201
+ this._isManualClose = true
202
+ this._ws.close()
203
+ this._ws = null
204
+ }
205
+
206
+ public reconnect() {
207
+ if (this._ws) {
208
+ console.warn('websocket is connecting now, reconnection is unnecessary.')
209
+ return
210
+ }
211
+
212
+ this._ws = this._protocols ? new WebSocket(this._url, this._protocols) : new WebSocket(this._url)
213
+ this._ws.binaryType = this._binaryType
214
+ this.bindWebSocketEvents(this._ws)
215
+ }
216
+
217
+ public destroy() {
218
+ this._heartbeatTimer && clearInterval(this._heartbeatTimer)
219
+ this._interruptReconnectTimer && clearTimeout(this._interruptReconnectTimer)
220
+
221
+ if (this._ws) {
222
+ this.close()
223
+ }
224
+
225
+ this._eventMap?.clear()
226
+ this._eventMap = null
227
+ }
228
+
229
+ public on<T extends keyof EventType>(eventName: T, callback: (event: EventType[T]) => void) {
230
+ if (!this._eventMap) return
231
+
232
+ let set = this._eventMap.get(eventName)
233
+
234
+ if (!set) {
235
+ set = new Set()
236
+ }
237
+
238
+ set.add(callback)
239
+ this._eventMap.set(eventName, set)
240
+ }
241
+
242
+ public off<T extends keyof EventType>(eventName: T, callback: (event: EventType[T]) => void) {
243
+ if (!this._eventMap) return
244
+
245
+ const set = this._eventMap.get(eventName)
246
+
247
+ if (!set) return
248
+
249
+ set.delete(callback)
250
+ }
251
+
252
+ public clear<T extends keyof EventType>(eventName: T) {
253
+ if (!this._eventMap) return
254
+
255
+ const set = this._eventMap.get(eventName)
256
+
257
+ if (!set) return
258
+
259
+ set.clear()
260
+ this._eventMap.delete(eventName)
261
+ }
262
+
263
+ public sendMessage(message: string) {
264
+ if (!this._ws) {
265
+ console.error('error: websocket was not yet opened or was closed.')
266
+ return
267
+ }
268
+
269
+ if (this._ws.readyState !== this._ws.OPEN) {
270
+ console.error('error: can not send message, socket is not established.')
271
+ return
272
+ }
273
+
274
+ this._ws.send(message)
275
+ }
276
+ }
277
+
278
+ export default WebSocketLoader
@@ -0,0 +1,429 @@
1
+ // #region typeimport
2
+ import type { WebSocketOptionsType } from '../loader/websocket-loader'
3
+ import type { RenderConstructorOptionType, VideoInfo } from '../render'
4
+ // #endregion typeimport
5
+ import { EventBus, isMobile } from '@havue/shared'
6
+ import { WebSocketLoader } from '../loader'
7
+ import {
8
+ Render,
9
+ WS_VIDEO_RENDER_DEFAULT_OPTIONS as RENDER_DEFAULT_OPTIONS,
10
+ RenderEventsEnum,
11
+ AudioState,
12
+ VideoState
13
+ } from '../render'
14
+ import { CanvasDrawer } from '../render/drawer'
15
+
16
+ // #region typedefine
17
+ export type WsVideoManaCstorOptionType = {
18
+ /** 预监流连接数量限制, 移动端默认10个,pc端默认32个 */
19
+ connectLimit?: number
20
+ /** WebSocketLoader 实例配置 */
21
+ wsOptions?: WebSocketOptionsType
22
+ /**
23
+ * websocket重连时,重新解析视频编码方式,
24
+ * 默认 true
25
+ */
26
+ reparseMimeOnReconnect?: boolean
27
+ /** Render 实例配置 */
28
+ renderOptions?: Partial<RenderConstructorOptionType>
29
+ /**
30
+ * 是否使用WebGL,
31
+ * 默认 false,
32
+ * WebGL在不同游览器,以及受限于显存,不能同时创建过多WebGL上下文,一般8-16个 */
33
+ useWebgl?: boolean
34
+ }
35
+
36
+ const DEFAULT_OPTIONS: Required<WsVideoManaCstorOptionType> = Object.freeze({
37
+ connectLimit: isMobile ? 10 : 32,
38
+ wsOptions: {
39
+ binaryType: 'arraybuffer' as WebSocket['binaryType']
40
+ },
41
+ reparseMimeOnReconnect: true,
42
+ renderOptions: RENDER_DEFAULT_OPTIONS,
43
+ useWebgl: false
44
+ })
45
+
46
+ type WsInfoType = {
47
+ /** 需要绘制的canvas列表 */
48
+ canvasMap: Map<HTMLCanvasElement, CanvasDrawer>
49
+ /** WebSocketLoader 实例 */
50
+ socket: WebSocketLoader
51
+ /** socket连接渲染render实例 */
52
+ render: Render
53
+ }
54
+
55
+ export enum EventEnums {
56
+ WS_URL_CHANGE = 'wsUrlChange',
57
+ SOCKET_CLOSE = 'socketClose',
58
+ CONNECT_LIMIT = 'connectLimit'
59
+ }
60
+
61
+ type Events = {
62
+ [EventEnums.WS_URL_CHANGE]: (urls: string[]) => void
63
+ [RenderEventsEnum.AUDIO_STATE_CHANGE]: (url: string, state: AudioState) => void
64
+ [RenderEventsEnum.VIDEO_INFO_UPDATE]: (url: string, info: VideoInfo) => void
65
+ [RenderEventsEnum.VIDEO_STATE_CHANGE]: (url: string, state: VideoState) => void
66
+ [EventEnums.SOCKET_CLOSE]: (url: string) => void
67
+ [EventEnums.CONNECT_LIMIT]: () => void
68
+ }
69
+
70
+ export const WsVideoManagerEventEnums = Object.assign({}, EventEnums, RenderEventsEnum)
71
+
72
+ // #endregion typedefine
73
+
74
+ export class WsVideoManager extends EventBus<Events> {
75
+ /** socket连接 渲染相关对应信息 */
76
+ private _wsInfoMap: Map<string, WsInfoType> = new Map()
77
+
78
+ private _option: Required<WsVideoManaCstorOptionType> = DEFAULT_OPTIONS
79
+
80
+ private _reqAnimationID: number | null = null
81
+
82
+ constructor(options?: WsVideoManaCstorOptionType) {
83
+ super()
84
+ this._option = options ? Object.assign({}, DEFAULT_OPTIONS, options) : DEFAULT_OPTIONS
85
+ }
86
+
87
+ get linkedUrlList() {
88
+ return [...this._wsInfoMap.keys()]
89
+ }
90
+
91
+ get connectLimit() {
92
+ return this._option.connectLimit
93
+ }
94
+
95
+ private _setAnimate() {
96
+ this._reqAnimationID && cancelAnimationFrame(this._reqAnimationID)
97
+ const render = () => {
98
+ this._wsInfoMap.forEach((item) => {
99
+ const { render, canvasMap } = item
100
+
101
+ if (!render) {
102
+ return
103
+ }
104
+ const { videoEl, paused } = render
105
+ if (videoEl && !paused && canvasMap.size) {
106
+ const values = canvasMap.values()
107
+ ;[...values].forEach((drawer) => {
108
+ drawer.draw(videoEl)
109
+ })
110
+ }
111
+ })
112
+
113
+ this._reqAnimationID = requestAnimationFrame(render)
114
+ }
115
+ render()
116
+ }
117
+
118
+ /**
119
+ * 添加socket实例
120
+ * @param url socket地址
121
+ * @returns
122
+ */
123
+ private _addSocket(url: string, renderOptions?: Partial<RenderConstructorOptionType>) {
124
+ if (this._isSocketExist(url)) {
125
+ this.updateRenderOptions(url, renderOptions)
126
+ return
127
+ }
128
+ if (this._wsInfoMap.size >= this._option.connectLimit) {
129
+ this.emit(EventEnums.CONNECT_LIMIT)
130
+ return
131
+ }
132
+ const socket = new WebSocketLoader(url, this._option.wsOptions)
133
+
134
+ const render = new Render(Object.assign({}, this._option.renderOptions, renderOptions))
135
+ const wsInfo: WsInfoType = {
136
+ socket,
137
+ canvasMap: new Map(),
138
+ render: render
139
+ }
140
+
141
+ this._wsInfoMap.set(url, wsInfo)
142
+ this._emitWsUrlListChange()
143
+
144
+ this._bindRenderEvent(url, render)
145
+ this._bindSocketEvent(socket, render, url)
146
+
147
+ socket.open()
148
+ }
149
+
150
+ /**
151
+ * 绑定render事件
152
+ * @param url 连接地址
153
+ * @param render Render实例
154
+ */
155
+ private _bindRenderEvent(url: string, render: Render) {
156
+ render.on(RenderEventsEnum.AUDIO_STATE_CHANGE, (state) => {
157
+ this.emit(RenderEventsEnum.AUDIO_STATE_CHANGE, url, state)
158
+ })
159
+ render.on(RenderEventsEnum.VIDEO_STATE_CHANGE, (state) => {
160
+ this.emit(RenderEventsEnum.VIDEO_STATE_CHANGE, url, state)
161
+ })
162
+ render.on(RenderEventsEnum.VIDEO_INFO_UPDATE, (info) => {
163
+ this.emit(RenderEventsEnum.VIDEO_INFO_UPDATE, url, info)
164
+ })
165
+ }
166
+
167
+ /**
168
+ * 销毁socket实例
169
+ * @param url socket地址
170
+ */
171
+ private _removeSocket(url: string) {
172
+ const wsInfo = this._wsInfoMap.get(url)
173
+ if (wsInfo) {
174
+ const { socket, render } = wsInfo
175
+ socket.close()
176
+ socket.destroy()
177
+ render.destroy()
178
+ this._wsInfoMap.delete(url)
179
+ this._emitWsUrlListChange()
180
+ }
181
+ }
182
+
183
+ /**
184
+ * 绑定socket事件
185
+ * @param url 连接地址
186
+ * @param socket WebSocketLoader实例
187
+ */
188
+ private _bindSocketEvent(socket: WebSocketLoader, render: Render, url: string) {
189
+ socket.on('close', () => {
190
+ if (this._option.reparseMimeOnReconnect) {
191
+ render.resetMimeType()
192
+ }
193
+ this.emit(EventEnums.SOCKET_CLOSE, url)
194
+ })
195
+
196
+ socket.on('reconnect', () => {
197
+ if (this._option.reparseMimeOnReconnect) {
198
+ render.resetMimeType()
199
+ }
200
+ })
201
+
202
+ socket.on('message', (event: WebSocketEventMap['message']) => {
203
+ render.appendMediaBuffer([event.data])
204
+ })
205
+ }
206
+
207
+ private _emitWsUrlListChange() {
208
+ this.emit(EventEnums.WS_URL_CHANGE, [...this._wsInfoMap.keys()])
209
+ if (this._wsInfoMap.size) {
210
+ if (!this._reqAnimationID) {
211
+ this._setAnimate()
212
+ }
213
+ } else {
214
+ this._reqAnimationID && cancelAnimationFrame(this._reqAnimationID)
215
+ this._reqAnimationID = null
216
+ }
217
+ }
218
+
219
+ /**
220
+ * url对应的 socket实例是否已存在
221
+ * @param url socket地址
222
+ * @returns boolean
223
+ */
224
+ private _isSocketExist(url: string): boolean {
225
+ return this._wsInfoMap.has(url)
226
+ }
227
+
228
+ /**
229
+ * 添加url对应socket,以及需要绘制的canvas元素
230
+ * @param canvas canvas元素
231
+ * @param url socket url地址
232
+ */
233
+ public addCanvas(canvas: HTMLCanvasElement, url: string, renderOptions?: Partial<RenderConstructorOptionType>) {
234
+ this._addSocket(url, renderOptions)
235
+ if (this.isCanvasExist(canvas)) {
236
+ throw new Error('the canvas allready exsist! please remove it before add')
237
+ }
238
+ const wsInfo = this._wsInfoMap.get(url)
239
+ if (!wsInfo) {
240
+ return
241
+ }
242
+ const { canvasMap } = wsInfo
243
+ if (!canvasMap) {
244
+ wsInfo.canvasMap = new Map([[canvas, new CanvasDrawer(canvas, this._option.useWebgl)]])
245
+ } else {
246
+ canvasMap.set(canvas, new CanvasDrawer(canvas, this._option.useWebgl))
247
+ }
248
+
249
+ // this._setupCanvas(canvas)
250
+ }
251
+
252
+ /**
253
+ * 初始化canvas背景
254
+ * @param canvas canvas元素
255
+ * @returns
256
+ */
257
+ // private _setupCanvas(canvas: HTMLCanvasElement) {
258
+ // const ctx = canvas.getContext('2d')
259
+ // if (!ctx) {
260
+ // return
261
+ // }
262
+ // ctx.fillStyle = 'black'
263
+ // ctx.fillRect(0, 0, canvas.width, canvas.height)
264
+ // }
265
+
266
+ /**
267
+ * 删除canvas元素
268
+ * @param canvas canvas元素
269
+ */
270
+ public removeCanvas(canvas: HTMLCanvasElement) {
271
+ const entries = this._wsInfoMap.entries()
272
+ const entriesList = [...entries]
273
+ entriesList.some(([url, wsInfo]) => {
274
+ const { canvasMap } = wsInfo
275
+ if (canvasMap.has(canvas)) {
276
+ canvasMap.get(canvas)?.destroy()
277
+ canvasMap.delete(canvas)
278
+ if (canvasMap.size === 0) {
279
+ this._removeSocket(url)
280
+ }
281
+ return true
282
+ }
283
+ })
284
+ }
285
+
286
+ /**
287
+ * 返回canvas是否已经添加过
288
+ * @param canvas canvas元素
289
+ * @returns boolean
290
+ */
291
+ public isCanvasExist(canvas: HTMLCanvasElement): boolean {
292
+ const values = this._wsInfoMap.values()
293
+ return [...values].some((info) => {
294
+ return info.canvasMap.has(canvas)
295
+ })
296
+ }
297
+
298
+ /** 设置全部render静音状态 */
299
+ public setAllVideoMutedState(muted: boolean) {
300
+ this._wsInfoMap.forEach((wsInfo) => {
301
+ wsInfo.render.muted = muted
302
+ })
303
+ }
304
+
305
+ /** 更新单个render实例的配置 */
306
+ public updateRenderOptions(url: string, options?: Partial<RenderConstructorOptionType>) {
307
+ if (options) {
308
+ const wsInfo = this._wsInfoMap.get(url)
309
+ wsInfo?.render.updateOptions(options)
310
+ }
311
+ }
312
+
313
+ /**
314
+ * 设置单个render静音状态
315
+ * @param url
316
+ * @param muted boolean 是否静音
317
+ */
318
+ public setOneMutedState(url: string, muted: boolean) {
319
+ const wsInfo = this._wsInfoMap.get(url)
320
+ if (!wsInfo) {
321
+ return
322
+ }
323
+ wsInfo.render.muted = muted
324
+ }
325
+
326
+ /**
327
+ * 获取url对应render video元素是否静音
328
+ * @param url socket地址
329
+ */
330
+ public getOneMutedState(url: string) {
331
+ const wsInfo = this._wsInfoMap.get(url)
332
+ if (!wsInfo) {
333
+ return true
334
+ }
335
+ return wsInfo.render.muted
336
+ }
337
+
338
+ /**
339
+ * 单个解除静音,其他未静音的变成静音,只播放一个
340
+ * @param url socket地址
341
+ */
342
+ public playOneAudio(url: string) {
343
+ this.setAllVideoMutedState(true)
344
+ this.setOneMutedState(url, false)
345
+ }
346
+
347
+ /**
348
+ * 设置单个render是否继续处理ws数据
349
+ * @param url
350
+ */
351
+ public setOneVideoPausedState(url: string, paused: boolean) {
352
+ const wsInfo = this._wsInfoMap.get(url)
353
+ if (!wsInfo) {
354
+ return
355
+ }
356
+ wsInfo.render.paused = paused
357
+ }
358
+
359
+ /** 设置全部render是否继续处理ws数据 */
360
+ public setAllVideoPausedState(paused: boolean) {
361
+ this._wsInfoMap.forEach((wsInfo) => {
362
+ wsInfo.render.paused = paused
363
+ })
364
+ }
365
+
366
+ /**
367
+ * 获取url对应render video元素是否继续播放
368
+ * @param url socket地址
369
+ */
370
+ public getOneVideoPausedState(url: string) {
371
+ const wsInfo = this._wsInfoMap.get(url)
372
+ if (!wsInfo) {
373
+ return false
374
+ }
375
+ return wsInfo.render.paused
376
+ }
377
+
378
+ /**
379
+ * 单个视频继续播放,其他暂停处理数据
380
+ * @param url socket地址
381
+ */
382
+ public playOneVideo(url: string) {
383
+ this.setAllVideoPausedState(true)
384
+ this.setOneVideoPausedState(url, false)
385
+ }
386
+
387
+ /**
388
+ * 刷新socket,以及播放时间
389
+ */
390
+ public refresh(url?: string) {
391
+ if (url) {
392
+ const wsInfo: WsInfoType | undefined = this._wsInfoMap.get(url)
393
+ if (wsInfo) {
394
+ wsInfo.render.refresh()
395
+ if (!wsInfo.socket.isConnecting) {
396
+ wsInfo.socket.reconnect()
397
+ }
398
+ }
399
+ } else {
400
+ this._wsInfoMap.forEach((wsInfo) => {
401
+ wsInfo.render.refresh()
402
+ if (!wsInfo.socket.isConnecting) {
403
+ wsInfo.socket.reconnect()
404
+ }
405
+ })
406
+ }
407
+ }
408
+
409
+ /**
410
+ * 销毁
411
+ */
412
+ public destroy() {
413
+ this._wsInfoMap.forEach((wsInfo) => {
414
+ const { socket, render, canvasMap } = wsInfo
415
+ socket.close()
416
+ socket.destroy()
417
+ render.destroy()
418
+
419
+ canvasMap.forEach((drawer) => {
420
+ drawer.destroy()
421
+ })
422
+ canvasMap.clear()
423
+ })
424
+ this._wsInfoMap.clear()
425
+ this._emitWsUrlListChange()
426
+ this._reqAnimationID && cancelAnimationFrame(this._reqAnimationID)
427
+ this._reqAnimationID = null
428
+ }
429
+ }