yjz-web-sdk 1.0.10 → 1.0.11-beta.1

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 (61) hide show
  1. package/lib/components/RemotePlayer/index.vue.d.ts +1 -73
  2. package/lib/composables/useCursorStyle.d.ts +1 -1
  3. package/lib/composables/useKeyboardControl.d.ts +1 -1
  4. package/lib/composables/useMouseTouchControl.d.ts +4 -4
  5. package/lib/composables/useRemoteVideo.d.ts +8 -25
  6. package/lib/composables/useResizeObserver.d.ts +1 -1
  7. package/lib/core/data/WebRtcError.d.ts +1 -2
  8. package/lib/core/data/WebrtcDataType.d.ts +1 -11
  9. package/lib/core/groupctrl/SdkController.d.ts +2 -2
  10. package/lib/core/rtc/WebRTCClient.d.ts +2 -5
  11. package/lib/core/rtc/WebRTCConfig.d.ts +1 -1
  12. package/lib/core/rtc/WebRtcNegotiate.d.ts +2 -2
  13. package/lib/core/signal/SignalingClient.d.ts +1 -1
  14. package/lib/core/util/TurnTestUtil.d.ts +2 -2
  15. package/lib/yjz-web-sdk.js +496 -1236
  16. package/package.json +5 -16
  17. package/lib/components/RemotePlayer/type.d.ts +0 -9
  18. package/lib/core/util/MapCache.d.ts +0 -20
  19. package/lib/render/Canvas2DRenderer.d.ts +0 -10
  20. package/lib/render/WebGLRenderer.d.ts +0 -16
  21. package/lib/render/WebGPURenderer.d.ts +0 -18
  22. package/lib/types/index.d.ts +0 -13
  23. package/lib/util/WasmUtil.d.ts +0 -17
  24. package/lib/worker/worker.d.ts +0 -1
  25. package/src/assets/icon/circle.svg +0 -1
  26. package/src/assets/icon/triangle.svg +0 -1
  27. package/src/assets/wasm/h264-atomic.wasm +0 -0
  28. package/src/assets/wasm/h264-simd.wasm +0 -0
  29. package/src/components/RemotePlayer/index.vue +0 -170
  30. package/src/components/RemotePlayer/type.ts +0 -11
  31. package/src/composables/useCursorStyle.ts +0 -15
  32. package/src/composables/useKeyboardControl.ts +0 -32
  33. package/src/composables/useMouseTouchControl.ts +0 -158
  34. package/src/composables/useRemoteVideo.ts +0 -248
  35. package/src/composables/useResizeObserver.ts +0 -27
  36. package/src/core/WebRTCSdk.ts +0 -561
  37. package/src/core/data/MessageType.ts +0 -70
  38. package/src/core/data/TurnType.ts +0 -25
  39. package/src/core/data/WebRtcError.ts +0 -93
  40. package/src/core/data/WebrtcDataType.ts +0 -354
  41. package/src/core/groupctrl/GroupCtrlSocketManager.ts +0 -94
  42. package/src/core/groupctrl/SdkController.ts +0 -96
  43. package/src/core/rtc/WebRTCClient.ts +0 -862
  44. package/src/core/rtc/WebRTCConfig.ts +0 -86
  45. package/src/core/rtc/WebRtcNegotiate.ts +0 -164
  46. package/src/core/signal/SignalingClient.ts +0 -221
  47. package/src/core/util/FileTypeUtils.ts +0 -75
  48. package/src/core/util/KeyCodeUtil.ts +0 -162
  49. package/src/core/util/Logger.ts +0 -83
  50. package/src/core/util/MapCache.ts +0 -135
  51. package/src/core/util/ScreenControlUtil.ts +0 -174
  52. package/src/core/util/TurnTestUtil.ts +0 -123
  53. package/src/env.d.ts +0 -30
  54. package/src/index.ts +0 -61
  55. package/src/render/Canvas2DRenderer.ts +0 -38
  56. package/src/render/WebGLRenderer.ts +0 -150
  57. package/src/render/WebGPURenderer.ts +0 -194
  58. package/src/types/index.ts +0 -15
  59. package/src/types/webgpu.d.ts +0 -1158
  60. package/src/util/WasmUtil.ts +0 -291
  61. package/src/worker/worker.ts +0 -292
@@ -1,86 +0,0 @@
1
- import { ConnectorType } from '../data/MessageType'
2
- import {LogLevel} from "../util/Logger";
3
-
4
- export interface WebRTCConfigOptions {
5
- signalServerUrl: string
6
- myId?: string
7
- roomId: string
8
- targetId?: string
9
- stunServerUri?: string
10
- stunServerUriAli?: string
11
- stunServerUriTel?: string
12
- turnServerUri?: string
13
- turnServerUserName?: string
14
- turnServerPassword?: string
15
- canOperate?: boolean
16
- screenUrl?: string
17
- connectStatus?: number
18
- cloudName?: string
19
- token: string
20
- isGroup: boolean
21
- turnKey?: Array<string>
22
- hostTurn?: Array<string>
23
- spareTurn?: Array<string>
24
- maxRecount?: number
25
- isCalculate?: boolean
26
- traceId?: string
27
- signAgain?: boolean
28
- connectorType?: ConnectorType
29
- mainRoomIdOfGroup?: string
30
- subRoomIdsOfGroup?: string[]
31
- mainCloudMyId?: string
32
- connectorAndRoomId?: Map<string, string>
33
- groupId?: string
34
- cacheTimeout?: number
35
- enableLogger?: boolean
36
- loggerLevel?: LogLevel
37
- }
38
-
39
- export class WebRTCConfig {
40
- signalServerUrl: string
41
- myId: string
42
- roomId: string
43
- targetId: string
44
- stunServerUri: string
45
- stunServerUriAli: string
46
- stunServerUriTel: string
47
- turnServerUri: string
48
- turnServerUserName: string
49
- turnServerPassword: string
50
- token: string
51
- connectorType: ConnectorType
52
- mainRoomIdOfGroup?: string
53
- subRoomIdsOfGroup?: string[]
54
- mainCloudMyId?: string
55
- connectorAndRoomId?: Map<string, string>
56
- groupId?: string
57
- isGroup: boolean
58
- turnKey: Array<string>
59
- traceId: string = ''
60
- signAgain: boolean
61
-
62
- constructor(options: WebRTCConfigOptions) {
63
- this.signalServerUrl = options.signalServerUrl || ''
64
- this.myId = options.myId || ''
65
- this.roomId = options.roomId || ''
66
- this.targetId = options.targetId || ''
67
- this.turnKey = options.turnKey || []
68
-
69
- this.stunServerUri = options.stunServerUri || 'stun:stun.l.google.com:19302'
70
- this.stunServerUriAli = options.stunServerUriAli || 'stun:stun.middle.aliyun.com:3478'
71
- this.stunServerUriTel = options.stunServerUriTel || 'stun:stun.qq.com:3478'
72
- this.turnServerUri = options.turnServerUri || 'turn:121.37.25.106:3478'
73
- this.turnServerUserName = options.turnServerUserName || 'yangyj'
74
- this.turnServerPassword = options.turnServerPassword || 'hb@2025@168'
75
- this.token = options.token
76
- this.connectorType = options.connectorType || ConnectorType.WebRTC
77
- this.mainRoomIdOfGroup = options.mainRoomIdOfGroup
78
- this.subRoomIdsOfGroup = options.subRoomIdsOfGroup
79
- this.connectorAndRoomId = options.connectorAndRoomId
80
- this.mainCloudMyId = options.mainCloudMyId
81
- this.groupId = options.groupId
82
- this.isGroup = options.isGroup
83
- this.traceId = options.traceId || ''
84
- this.signAgain = options.signAgain || true
85
- }
86
- }
@@ -1,164 +0,0 @@
1
- import {createWebRtcError, FailCode, type WebRtcError} from "../data/WebRtcError";
2
- import {WebRTCConfig} from "./WebRTCConfig";
3
- import 'webrtc-adapter';
4
- import {Logger} from "../util/Logger";
5
- let iceConnectionTimeout: ReturnType<typeof setTimeout> | null = null;
6
- export const setRemoteDescriptionWithHandleOffer = (
7
- peerConnection: RTCPeerConnection,
8
- sdp: string,
9
- sendAnswer?: (sdp: string) => void,
10
- onError?: (err: WebRtcError) => void
11
- ): void => {
12
- Logger.info('信息日志:', '设置远程offer Description=======>')
13
- const description = new RTCSessionDescription({ type: "offer", sdp });
14
- peerConnection.setRemoteDescription(description)
15
- .then(() => createAnswer(peerConnection, sendAnswer, onError))
16
- .catch(error => onError?.(createWebRtcError(FailCode.HANDLE_OFFER, error)));
17
- }
18
-
19
- const createAnswer = (
20
- peerConnection: RTCPeerConnection,
21
- sendAnswer?: (sdp: string) => void,
22
- onError?: (err: WebRtcError) => void
23
- ): void => {
24
- Logger.info('信息日志:', '创建webrtcAnswer=======>')
25
- peerConnection.createAnswer()
26
- .then(answer => setLocalDescriptionWithCreateAnswer(peerConnection, answer, sendAnswer, onError))
27
- .catch(error => onError?.(createWebRtcError(FailCode.CREATE_ANSWER, error)));
28
- }
29
-
30
- const setLocalDescriptionWithCreateAnswer = (
31
- peerConnection: RTCPeerConnection,
32
- answer: RTCSessionDescriptionInit,
33
- sendAnswer?: (sdp: string) => void,
34
- onError?: (err: WebRtcError) => void
35
- ): void => {
36
- Logger.info('信息日志:', '设置本地answer Description=======>')
37
-
38
- // peerConnection.setLocalDescription({ type: answer.type, sdp })
39
- peerConnection.setLocalDescription(answer)
40
- .then(() => {
41
- sendAnswer?.(answer.sdp ?? '');
42
- }).catch(error => {
43
- onError?.(createWebRtcError(FailCode.LOCAL_DES, error))
44
- });
45
- }
46
-
47
- export const createPeerConnection = (config: WebRTCConfig): RTCPeerConnection => {
48
- Logger.info('信息日志:', '初始化 Webrtc PeerConnection=======>')
49
- const iceServers: RTCIceServer[] = [
50
- { urls: config.stunServerUri },
51
- {
52
- urls: config.turnServerUri,
53
- username: config.turnServerUserName,
54
- credential: config.turnServerPassword,
55
- }
56
- ];
57
-
58
- const peerConnectionConfig: RTCConfiguration = {
59
- iceServers,
60
- iceTransportPolicy: "all",
61
- bundlePolicy: "max-bundle"
62
- };
63
- return new RTCPeerConnection(peerConnectionConfig);
64
- }
65
-
66
- export const configPeerConnection = (
67
- peerConnection: RTCPeerConnection,
68
- onICEMessage: (candidate: string) => void,
69
- onTrack?: (track: MediaStreamTrack) => void,
70
- onConnectState?: (state: RTCIceConnectionState) => void,
71
- onError?: (error: WebRtcError) => void
72
- ): void => {
73
- peerConnection.onicecandidate = event => {
74
- if (!event.candidate) {
75
- return;
76
- }
77
- const candidateObj = {
78
- sdp: event.candidate.candidate,
79
- sdpMid: event.candidate.sdpMid,
80
- sdpMLineIndex: event.candidate.sdpMLineIndex,
81
- };
82
- Logger.debug('信息日志:', `webrtc 生成的icecandidate===>${JSON.stringify(candidateObj)}`)
83
- onICEMessage(JSON.stringify(candidateObj));
84
- }
85
-
86
- peerConnection.onconnectionstatechange = () => {
87
- const state:RTCIceConnectionState = peerConnection.iceConnectionState;
88
- if(!state){
89
- return
90
- }
91
- Logger.debug('信息日志:', `webrtc p2p连接状态===>`, state)
92
- if (state === "failed" || state === 'disconnected' || state === 'closed') {
93
- onError?.(createWebRtcError(FailCode.ICE_STATE, "failed"));
94
- } else if(state === "connected"){
95
- if(iceConnectionTimeout){
96
- clearTimeout(iceConnectionTimeout)
97
- }
98
- } else if(state === "new" || state === "checking"){
99
- if(iceConnectionTimeout){
100
- iceConnectionTimeout = setTimeout(() => {
101
- onError?.(createWebRtcError(FailCode.ICE_STATE, "请检查相关配置"));
102
- iceConnectionTimeout = null;
103
- }, 6000);
104
- }
105
- }
106
- onConnectState?.(state)
107
- }
108
-
109
- peerConnection.ontrack = event => {
110
- Logger.debug('信息日志:', 'webrtc p2p连接后获取的音视频track')
111
- const track = event.track;
112
- track.contentHint = "motion";
113
- onTrack?.(track);
114
- }
115
- }
116
-
117
- export const setRemoteDescriptionWithHandleAnswer = (
118
- peerConnection: RTCPeerConnection,
119
- sdp: string,
120
- onError?: (error: WebRtcError) => void
121
- ): void => {
122
- const description = new RTCSessionDescription({ type: "answer", sdp });
123
- peerConnection.setRemoteDescription(description)
124
- .catch(err => onError?.(createWebRtcError(FailCode.REMOTE_DES, err)));
125
- }
126
-
127
- export const createOffer = async (
128
- peerConnection: RTCPeerConnection,
129
- sendOfferMessage: (sdp: string) => void
130
- ): Promise<void> => {
131
- try {
132
- const offer = await peerConnection.createOffer({"offerToReceiveAudio":true,"offerToReceiveVideo":true});
133
- // setLocalDescriptionWithCreateOffer 也可能抛异常,需要 await
134
- await setLocalDescriptionWithCreateOffer(peerConnection, offer, sendOfferMessage);
135
- } catch (err) {
136
- // 直接抛出异常,不再依赖回调
137
- throw new Error("摄像头视频流添加失败");
138
- }
139
- };
140
-
141
- export const setLocalDescriptionWithCreateOffer = async (
142
- peerConnection: RTCPeerConnection,
143
- offer: RTCSessionDescriptionInit,
144
- sendOfferMessage: (sdp: string) => void
145
- ): Promise<void> => {
146
- Logger.info('信息日志:', '设置本地offer Description=======>')
147
- try {
148
- await peerConnection.setLocalDescription(offer);
149
- sendOfferMessage(offer.sdp ?? '');
150
- } catch (err) {
151
- // 直接抛出异常,不使用回调
152
- throw new Error("摄像头视频流添加失败");
153
- }
154
- };
155
-
156
- export const addIceCandidate = (
157
- peerConnection: RTCPeerConnection,
158
- candidate: RTCIceCandidateInit,
159
- onError?: (error: WebRtcError) => void
160
- ): void => {
161
- Logger.info('信息日志:', '接收远程ice 并设置=======>')
162
- peerConnection.addIceCandidate(candidate)
163
- .catch(err => onError?.(createWebRtcError(FailCode.HANDLE_ICE, err)));
164
- }
@@ -1,221 +0,0 @@
1
- import { EventEmitter } from 'eventemitter3'
2
- import type { WebRTCConfig } from '../rtc/WebRTCConfig'
3
- import { ConnectorType, MessageType, SendType } from '../data/MessageType'
4
- import { createWebRtcError, EmitType, FailCode } from '../data/WebRtcError'
5
- import {Logger} from "../util/Logger";
6
-
7
- // 导出一个名为 SignalingClient 的类,该类继承自 EventEmitter
8
- export class SignalingClient extends EventEmitter {
9
- // 私有属性 config,用于存储 WebRTC 配置信息
10
- private config: WebRTCConfig // 根据实际情况替换为具体配置类型
11
- // 私有属性 webSocket,用于存储 WebSocket 连接实例,初始值为 null
12
- private webSocket: WebSocket | null = null
13
- private timeout: ReturnType<typeof setTimeout> | null = null
14
-
15
- constructor(config: WebRTCConfig) {
16
- // 构造函数,接收一个 WebRTCConfig 类型的参数 config
17
- super()
18
- // 调用父类 EventEmitter 的构造函数
19
- this.config = config
20
- // 将传入的配置信息赋值给私有属性 config
21
- }
22
-
23
- /**
24
- * 启动 WebSocket 连接,并注册各事件处理
25
- */
26
- start() {
27
- // 创建一个新的 WebSocket 连接,使用配置中的信号服务器 URL
28
- this.webSocket = new WebSocket(this.config.signalServerUrl)
29
- // 当 WebSocket 连接成功打开时触发
30
- this.webSocket.onopen = () => {
31
- // 连接成功后发送 SignIn 消息
32
- this.config.traceId = generateRandomString(16)
33
- if (this.config.connectorType === ConnectorType.LanForwarding) {
34
- this.sendGroupSignInMessage()
35
- }
36
- else {
37
- this.sendSignInMessage()
38
- }
39
- // 设置 4 秒超时,如果在超时前没有收到 Offer 消息,则触发 notAvailable 事件
40
- this.timeStart()
41
- }
42
-
43
- // 当连接关闭时触发 close 事件
44
- this.webSocket.onclose = () => {
45
- this.webSocket = null
46
- }
47
-
48
- // 当发生错误时触发 wsError 事件
49
- this.webSocket.onerror = (event: Event) => {
50
- Logger.error('错误日志:', `信令服务器连接失败或者已断开=======>${this.config.signalServerUrl}`)
51
- this.emit(EmitType.webrtcError, createWebRtcError(FailCode.SOCKET, event))
52
- }
53
-
54
- // 处理收到的消息
55
- this.webSocket.onmessage = (event: MessageEvent) => {
56
- const message = JSON.parse(event.data)
57
- // 当收到 Offer 消息时,清除超时定时器
58
- if (message.type === MessageType.Offer) {
59
- this.clearTime()
60
- }
61
- // 通过事件通知外部收到消息
62
- this.emit(EmitType.signalMessage, message)
63
- }
64
- }
65
-
66
- timeStart() {
67
- this.clearTime()
68
- if (this.timeout === null) {
69
- this.timeout = setTimeout(() => {
70
- Logger.error('错误日志:', `远端云机未响应=======>${this.config.roomId}`)
71
- this.emit(EmitType.webrtcError, createWebRtcError(FailCode.STATE_ERR, ''))
72
- this.timeout = null
73
- }, 6000)
74
- }
75
- }
76
-
77
- clearTime() {
78
- if (this.timeout) {
79
- clearTimeout(this.timeout)
80
- this.timeout = null
81
- }
82
- }
83
-
84
- /**
85
- * 关闭 WebSocket 连接,并通知外部
86
- */
87
- close() {
88
- if (this.webSocket) {
89
- this.webSocket.onopen = null
90
- // 清除所有事件处理函数
91
- this.webSocket.onmessage = null
92
- this.webSocket.onerror = null
93
- this.webSocket.onclose = null
94
- if (this.webSocket.readyState === WebSocket.OPEN || this.webSocket.readyState === WebSocket.CONNECTING) {
95
- // 如果 WebSocket 连接处于打开或连接中状态,则关闭连接
96
- this.webSocket.close(1000, 'Normal Closure')
97
- }
98
- this.webSocket = null
99
- // 将 WebSocket 实例置为 null
100
- }
101
- this.removeAllListeners()
102
- // 移除所有事件监听器
103
- }
104
-
105
- /**
106
- * 发送消息到服务端
107
- * @param message - 消息字符串
108
- */
109
- sendMessage(message: string) {
110
- if (this.webSocket && this.webSocket.readyState === WebSocket.OPEN) {
111
- // 如果 WebSocket 连接存在且处于打开状态,则发送消息
112
- this.webSocket.send(message)
113
- }
114
- }
115
-
116
- sendSignInMessage() {
117
- // 发送 SignIn 消息
118
- const message = {
119
- traceId: this.config.traceId,
120
- type: SendType.SignIn,
121
- targetId: this.config.myId,
122
- identity: SendType.Identity,
123
- turnServerUri: this.config.turnServerUri,
124
- roomId: this.config.roomId,
125
- token: this.config.token,
126
- isGroup: this.config.isGroup,
127
- turnKey: this.config.turnKey,
128
- connectorType: this.config.connectorType,
129
- }
130
- const jsonMessage = JSON.stringify(message)
131
- Logger.debug("调试日志:", `屏控登录信息====>${jsonMessage}`)
132
- this.sendMessage(jsonMessage)
133
- }
134
-
135
- sendSwitchControlMessage(roomId: string, connectorType: ConnectorType) {
136
- const message = {
137
- type: SendType.SwitchControl,
138
- identity: SendType.Identity,
139
- roomId: roomId,
140
- connectorType: connectorType,
141
- targetId: this.config.myId,
142
- }
143
- const jsonMessage = JSON.stringify(message)
144
- Logger.debug("调试日志:", `切换主控信令消息====>${jsonMessage}`)
145
- this.sendMessage(jsonMessage)
146
- }
147
-
148
- sendSwitchControlToMainMessage(roomId: string) {
149
- const message = {
150
- type: SendType.SwitchControlToMain,
151
- identity: SendType.Identity,
152
- groupId: this.config.groupId,
153
- turnKey: this.config.turnKey,
154
- roomId: roomId,
155
- traceId: generateRandomString(16),
156
- }
157
- const jsonMessage = JSON.stringify(message)
158
- Logger.debug("调试日志:", `切换主控信令消息====>${jsonMessage}`)
159
- this.sendMessage(jsonMessage)
160
- }
161
-
162
- sendGroupAcceptControl(roomIds: string[], acceptControl: boolean) {
163
- const message = {
164
- type: SendType.GroupAcceptControl,
165
- targetId: this.config.myId,
166
- identity: SendType.Identity,
167
- roomIds: roomIds,
168
- isAccept: acceptControl,
169
- }
170
- const jsonMessage = JSON.stringify(message)
171
- Logger.debug("调试日志:", `修改控制状态====>${jsonMessage}`)
172
- this.sendMessage(jsonMessage)
173
- }
174
-
175
- sendGroupAction(action: string) {
176
- const tid = generateRandomString(16)
177
- const message = {
178
- type: SendType.GroupSendAction,
179
- identity: SendType.Identity,
180
- targetId: this.config.myId,
181
- groupId: this.config.groupId,
182
- action: action,
183
- epSendAt: Date.now(),
184
- traceId: tid,
185
- }
186
- const jsonMessage = JSON.stringify(message)
187
- Logger.debug("调试日志:", `发送群控事件====>${jsonMessage}`)
188
- this.sendMessage(jsonMessage)
189
- }
190
-
191
- sendGroupSignInMessage() {
192
- const message = {
193
- traceId: this.config.traceId,
194
- type: SendType.GroupSignIn,
195
- connectorType: 'websocket',
196
- identity: SendType.Identity,
197
- turnServerUri: this.config.turnServerUri,
198
- mainRoomIdOfGroup: this.config.mainRoomIdOfGroup,
199
- subRoomIdsOfGroup: this.config.subRoomIdsOfGroup,
200
- turnKey: this.config.turnKey,
201
- groupId: 'group_0001',
202
- token: this.config.token,
203
- }
204
- const jsonMessage = JSON.stringify(message)
205
- Logger.debug("调试日志:", `群控登录信息====>${jsonMessage}`)
206
- this.sendMessage(jsonMessage)
207
- }
208
- }
209
-
210
- function generateRandomString(length: number): string {
211
- const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
212
- let result = ''
213
- const charsLength = chars.length
214
-
215
- for (let i = 0; i < length; i++) {
216
- const randomIndex = Math.floor(Math.random() * charsLength)
217
- result += chars[randomIndex]
218
- }
219
-
220
- return result
221
- }
@@ -1,75 +0,0 @@
1
- // FileTypeUtils.ts
2
- export type FileKind = 'video' | 'image' | 'unknown'
3
-
4
- // eslint-disable-next-line @typescript-eslint/no-extraneous-class
5
- export class FileTypeUtils {
6
- /** 读取文件头并判断类型 */
7
- static async detectFileType(file: File): Promise<FileKind> {
8
- const buffer = await file.slice(0, 16).arrayBuffer()
9
- const bytes = new Uint8Array(buffer)
10
-
11
- // JPEG: FF D8 FF
12
- if (bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff) {
13
- return 'image'
14
- }
15
- // PNG: 89 50 4E 47
16
- if (bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47) {
17
- return 'image'
18
- }
19
- // GIF: "GIF87a" or "GIF89a"
20
- if (
21
- bytes[0] === 0x47
22
- && bytes[1] === 0x49
23
- && bytes[2] === 0x46
24
- && bytes[3] === 0x38
25
- ) {
26
- return 'image'
27
- }
28
- // WebP: "RIFF" .... "WEBP"
29
- if (
30
- bytes[0] === 0x52 // R
31
- && bytes[1] === 0x49 // I
32
- && bytes[2] === 0x46 // F
33
- && bytes[3] === 0x46 // F
34
- && bytes[8] === 0x57 // W
35
- && bytes[9] === 0x45 // E
36
- && bytes[10] === 0x42 // B
37
- && bytes[11] === 0x50 // P
38
- ) {
39
- return 'image'
40
- }
41
- // MP4/QuickTime: ftyp
42
- if (String.fromCharCode(...bytes.slice(4, 8)) === 'ftyp') {
43
- return 'video'
44
- }
45
- // WebM / Matroska: 1A 45 DF A3
46
- if (
47
- bytes[0] === 0x1a
48
- && bytes[1] === 0x45
49
- && bytes[2] === 0xdf
50
- && bytes[3] === 0xa3
51
- ) {
52
- return 'video'
53
- }
54
- // Ogg: "OggS"
55
- if (
56
- bytes[0] === 0x4f
57
- && bytes[1] === 0x67
58
- && bytes[2] === 0x67
59
- && bytes[3] === 0x53
60
- ) {
61
- return 'video'
62
- }
63
-
64
- return 'unknown'
65
- }
66
- }
67
-
68
- export const formattedTime = (timestamp: number): string => {
69
- const date = new Date(timestamp)
70
- const h = String(date.getHours()).padStart(2, '0')
71
- const m = String(date.getMinutes()).padStart(2, '0')
72
- const s = String(date.getSeconds()).padStart(2, '0')
73
- const ms = String(date.getMilliseconds()).padStart(3, '0')
74
- return `${h}:${m}:${s}.${ms}`
75
- }
@@ -1,162 +0,0 @@
1
- // 定义 Android 键盘状态位常量
2
- const META_SHIFT_ON = 1 // Shift 键
3
- const META_ALT_ON = 2 // Alt 键
4
- const META_CTRL_ON = 4096 // Ctrl 键
5
- const META_META_ON = 65536 // Meta 键
6
-
7
- /**
8
- * 根据浏览器 KeyboardEvent 计算 Android 平台的 meta 状态值
9
- * @param event - 浏览器的 KeyboardEvent
10
- * @returns 计算后的 meta 状态
11
- */
12
- export const computeAndroidMetaState = (event: KeyboardEvent): number => {
13
- let state = 0
14
- if (event.shiftKey) state |= META_SHIFT_ON
15
- if (event.altKey) state |= META_ALT_ON
16
- if (event.ctrlKey) state |= META_CTRL_ON
17
- if (event.metaKey) state |= META_META_ON
18
- return state
19
- }
20
-
21
- // 定义 KeyCodeMap 类型(键名映射为 Android KeyCode)
22
- const KeyCodeMap: Record<string, number> = {
23
- // 数字键
24
- Digit1: 8,
25
- Digit2: 9,
26
- Digit3: 10,
27
- Digit4: 11,
28
- Digit5: 12,
29
- Digit6: 13,
30
- Digit7: 14,
31
- Digit8: 15,
32
- Digit9: 16,
33
- Digit0: 7,
34
-
35
- // 符号键(主键盘区)
36
- Minus: 69,
37
- Equal: 70,
38
- Backquote: 68,
39
- BracketLeft: 71,
40
- BracketRight: 72,
41
- Backslash: 73,
42
- Semicolon: 74,
43
- Quote: 75,
44
- Comma: 55,
45
- Period: 56,
46
- Slash: 76,
47
-
48
- // 功能键
49
- Escape: 111,
50
- Backspace: 67,
51
- Tab: 61,
52
- Enter: 66,
53
- Space: 62,
54
- CapsLock: 115,
55
-
56
- // 修饰键
57
- ShiftLeft: 59,
58
- ShiftRight: 60,
59
- ControlLeft: 113,
60
- ControlRight: 114,
61
- AltLeft: 57,
62
- AltRight: 58,
63
- MetaLeft: 117,
64
- MetaRight: 118,
65
-
66
- // 方向键
67
- ArrowUp: 19,
68
- ArrowDown: 20,
69
- ArrowLeft: 21,
70
- ArrowRight: 22,
71
-
72
- // 字母键(物理位置映射)
73
- KeyA: 29,
74
- KeyB: 30,
75
- KeyC: 31,
76
- KeyD: 32,
77
- KeyE: 33,
78
- KeyF: 34,
79
- KeyG: 35,
80
- KeyH: 36,
81
- KeyI: 37,
82
- KeyJ: 38,
83
- KeyK: 39,
84
- KeyL: 40,
85
- KeyM: 41,
86
- KeyN: 42,
87
- KeyO: 43,
88
- KeyP: 44,
89
- KeyQ: 45,
90
- KeyR: 46,
91
- KeyS: 47,
92
- KeyT: 48,
93
- KeyU: 49,
94
- KeyV: 50,
95
- KeyW: 51,
96
- KeyX: 52,
97
- KeyY: 53,
98
- KeyZ: 54,
99
-
100
- // 功能键扩展
101
- F1: 131,
102
- F2: 132,
103
- F3: 133,
104
- F4: 134,
105
- F5: 135,
106
- F6: 136,
107
- F7: 137,
108
- F8: 138,
109
- F9: 139,
110
- F10: 140,
111
- F11: 141,
112
- F12: 142,
113
-
114
- // 数字小键盘
115
- Numpad0: 7,
116
- Numpad1: 8,
117
- Numpad2: 9,
118
- Numpad3: 10,
119
- Numpad4: 11,
120
- Numpad5: 12,
121
- Numpad6: 13,
122
- Numpad7: 14,
123
- Numpad8: 15,
124
- Numpad9: 16,
125
- NumpadAdd: 157,
126
- NumpadSubtract: 156,
127
- NumpadMultiply: 155,
128
- NumpadDivide: 154,
129
- NumpadDecimal: 158,
130
- NumpadEnter: 66,
131
-
132
- // 其他特殊键
133
- Insert: 124,
134
- Delete: 112,
135
- Home: 122,
136
- End: 123,
137
- PageUp: 92,
138
- PageDown: 93,
139
- ScrollLock: 116,
140
- Pause: 121,
141
- ContextMenu: 117,
142
- }
143
-
144
- export interface KeyEventData {
145
- keyCode: number
146
- meta: number
147
- }
148
-
149
- /**
150
- * 根据 KeyboardEvent 生成 Android 平台下的按键数据
151
- * @param event - 浏览器的 KeyboardEvent 对象
152
- * @returns 包含 androidKeyCode 和 metaState 的对象
153
- */
154
- export const getKeyEventData = (event: KeyboardEvent): KeyEventData => {
155
- // 查找映射的 Android 键码,如果找不到返回 -1
156
- const keyCode = KeyCodeMap[event.code] ?? -1
157
- const meta = computeAndroidMetaState(event)
158
- return {
159
- keyCode,
160
- meta,
161
- }
162
- }