@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.
- package/README.md +0 -0
- package/bc-connect/README.md +3 -0
- package/bc-connect/dist/bc-connect.mjs +342 -0
- package/bc-connect/dist/bc-connect.umd.js +346 -0
- package/bc-connect/dist/types/src/index.d.ts +1 -0
- package/bc-connect/dist/types/src/manager.d.ts +135 -0
- package/bc-connect/package.json +38 -0
- package/bc-connect/src/index.ts +1 -0
- package/bc-connect/src/manager.ts +388 -0
- package/bc-connect/vite.config.ts +4 -0
- package/dist/solutions.full.js +8504 -0
- package/dist/solutions.full.min.js +18 -0
- package/dist/solutions.full.min.js.map +1 -0
- package/dist/solutions.mjs +2 -0
- package/dist/solutions.umd.js +18 -0
- package/dist/types/bc-connect/src/index.d.ts +1 -0
- package/dist/types/bc-connect/src/manager.d.ts +135 -0
- package/dist/types/src/index.d.ts +2 -0
- package/dist/types/ws-video-manager/src/hooks/useVideoPlay.d.ts +73 -0
- package/dist/types/ws-video-manager/src/index.d.ts +5 -0
- package/dist/types/ws-video-manager/src/loader/index.d.ts +2 -0
- package/dist/types/ws-video-manager/src/loader/websocket-loader.d.ts +62 -0
- package/dist/types/ws-video-manager/src/manager/index.d.ts +144 -0
- package/dist/types/ws-video-manager/src/render/drawer.d.ts +44 -0
- package/dist/types/ws-video-manager/src/render/index.d.ts +105 -0
- package/package.json +44 -0
- package/src/index.ts +2 -0
- package/vite.config.ts +4 -0
- package/ws-video-manager/README.md +3 -0
- package/ws-video-manager/dist/types/src/hooks/useVideoPlay.d.ts +73 -0
- package/ws-video-manager/dist/types/src/index.d.ts +5 -0
- package/ws-video-manager/dist/types/src/loader/index.d.ts +2 -0
- package/ws-video-manager/dist/types/src/loader/websocket-loader.d.ts +62 -0
- package/ws-video-manager/dist/types/src/manager/index.d.ts +144 -0
- package/ws-video-manager/dist/types/src/render/drawer.d.ts +44 -0
- package/ws-video-manager/dist/types/src/render/index.d.ts +105 -0
- package/ws-video-manager/dist/ws-video-manager.mjs +8132 -0
- package/ws-video-manager/dist/ws-video-manager.umd.js +8135 -0
- package/ws-video-manager/node_modules/.bin/tsc +17 -0
- package/ws-video-manager/node_modules/.bin/tsc.CMD +12 -0
- package/ws-video-manager/node_modules/.bin/tsc.ps1 +41 -0
- package/ws-video-manager/node_modules/.bin/tsserver +17 -0
- package/ws-video-manager/node_modules/.bin/tsserver.CMD +12 -0
- package/ws-video-manager/node_modules/.bin/tsserver.ps1 +41 -0
- package/ws-video-manager/package.json +41 -0
- package/ws-video-manager/src/hooks/useVideoPlay.ts +357 -0
- package/ws-video-manager/src/index.ts +6 -0
- package/ws-video-manager/src/loader/index.ts +3 -0
- package/ws-video-manager/src/loader/websocket-loader.ts +278 -0
- package/ws-video-manager/src/manager/index.ts +429 -0
- package/ws-video-manager/src/render/drawer.ts +255 -0
- package/ws-video-manager/src/render/index.ts +475 -0
- 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
|
+
}
|