From a1b0764e99f3d0a92ca4ef3c02909e218d09ae5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=E4=BA=86=E4=B8=AAJ?= Date: Tue, 12 May 2026 20:08:56 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=9A=E7=A7=BB=E9=99=A4=E7=9B=B4?= =?UTF-8?q?=E6=92=AD=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 - pnpm-lock.yaml | 99 +--------- src/page/live/StreamVideo.vue | 36 ---- src/page/live/index.vue | 322 -------------------------------- src/page/live/room-ws-client.ts | 276 --------------------------- src/page/live/routes.ts | 10 - src/page/login/index.vue | 12 +- src/stores/student-access.ts | 1 - 8 files changed, 13 insertions(+), 744 deletions(-) delete mode 100644 src/page/live/StreamVideo.vue delete mode 100644 src/page/live/index.vue delete mode 100644 src/page/live/room-ws-client.ts delete mode 100644 src/page/live/routes.ts diff --git a/package.json b/package.json index cde9404..cca1dad 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,6 @@ "lottie-web": "^5.12.2", "markdown-it": "^14.1.0", "mathjax": "3.0.1", - "mediasoup-client": "^3.20.0", "mermaid-it-markdown": "^1.0.8", "nprogress": "^0.2.0", "pinia": "^2.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1ff42c1..4856514 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -92,9 +92,6 @@ importers: mathjax: specifier: 3.0.1 version: 3.0.1 - mediasoup-client: - specifier: ^3.20.0 - version: 3.20.0 mermaid-it-markdown: specifier: ^1.0.8 version: 1.0.8(mermaid@10.9.3)(typescript@5.8.2) @@ -658,14 +655,6 @@ packages: '@kurkle/color@0.3.4': resolution: {integrity: sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==} - '@lukeed/csprng@1.1.0': - resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==} - engines: {node: '>=8'} - - '@lukeed/uuid@2.0.1': - resolution: {integrity: sha512-qC72D4+CDdjGqJvkFMMEAtancHUQ7/d/tAiHf64z8MopFDmcrtbcJuerDtFceuAfQJ2pDSfCKCtbqoGBNnwg0w==} - engines: {node: '>=8'} - '@microsoft/fetch-event-source@2.0.1': resolution: {integrity: sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA==} @@ -917,18 +906,12 @@ packages: '@types/d3-time@3.0.4': resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} - '@types/debug@4.1.12': - resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} - '@types/debug@4.1.13': resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} '@types/estree@1.0.7': resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} - '@types/events@3.0.3': - resolution: {integrity: sha512-trOc4AAUThEz9hapPtSd7wf5tiQKvTtu5b371UxXdTuqzIh0ArcRspRP0i0Viu+LXstIQ1z96t1nsPxT9ol01g==} - '@types/hammerjs@2.0.46': resolution: {integrity: sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==} @@ -1358,10 +1341,6 @@ packages: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} - awaitqueue@3.3.0: - resolution: {integrity: sha512-zLxDhzQbzHmOyvxi7g3OlfR7jLrcmElStPxfLPpJkrFSDm71RSrY/MvsDA8Btlx8X1nOHUzGhQvc6bdUjL2f2w==} - engines: {node: '>=20'} - axios@1.8.4: resolution: {integrity: sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==} @@ -2108,10 +2087,6 @@ packages: eventemitter3@5.0.1: resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} - events@3.3.0: - resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} - engines: {node: '>=0.8.x'} - execa@8.0.1: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} engines: {node: '>=16.17'} @@ -2122,10 +2097,6 @@ packages: ext@1.7.0: resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==} - fake-mediastreamtrack@2.2.1: - resolution: {integrity: sha512-SITLc7UTDirSdgLGORrlmqjWLJtbtfIz/xO7DwVbJH3f0ds+NQok4ccl/WEzz49NSgiUlXf2wDW0+y5C+TO6EA==} - engines: {node: '>=20'} - fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -2294,10 +2265,6 @@ packages: graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - h264-profile-level-id@2.3.2: - resolution: {integrity: sha512-hnq1UDlw7WGJV6GCr/g7wnkHYUjdAY2bis9rgn2JqSdQS2WfVvnt1ZE9g8nTguracodf5LLKZOwURsDN49YtBQ==} - engines: {node: '>=20'} - hammerjs@2.0.8: resolution: {integrity: sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ==} engines: {node: '>=0.8.0'} @@ -2754,10 +2721,6 @@ packages: mdurl@2.0.0: resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==} - mediasoup-client@3.20.0: - resolution: {integrity: sha512-FL6RlsGwvmXbXkbMoc1eW2EqW7k8Cw/Mhls3QewQXH+mioWVeVNWjzcDPmTZ3Sw+mua/n0IH5C/ntuVsx+KegA==} - engines: {node: '>=22'} - memoize-one@6.0.0: resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==} @@ -3266,10 +3229,6 @@ packages: scule@1.3.0: resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} - sdp-transform@3.0.0: - resolution: {integrity: sha512-gfYVRGxjHkGF2NPeUWHw5u6T/KGFtS5/drPms73gaSuMaVHKCY3lpLnGDfswVQO0kddeePoti09AwhYP4zA8dQ==} - hasBin: true - semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -3413,10 +3372,6 @@ packages: stylis@4.3.6: resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==} - supports-color@10.2.2: - resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} - engines: {node: '>=18'} - supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -4246,12 +4201,6 @@ snapshots: '@kurkle/color@0.3.4': {} - '@lukeed/csprng@1.1.0': {} - - '@lukeed/uuid@2.0.1': - dependencies: - '@lukeed/csprng': 1.1.0 - '@microsoft/fetch-event-source@2.0.1': {} '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': @@ -4420,18 +4369,12 @@ snapshots: '@types/d3-time@3.0.4': {} - '@types/debug@4.1.12': - dependencies: - '@types/ms': 2.1.0 - '@types/debug@4.1.13': dependencies: '@types/ms': 2.1.0 '@types/estree@1.0.7': {} - '@types/events@3.0.3': {} - '@types/hammerjs@2.0.46': {} '@types/json-schema@7.0.15': {} @@ -4980,12 +4923,6 @@ snapshots: dependencies: possible-typed-array-names: 1.1.0 - awaitqueue@3.3.0(supports-color@10.2.2): - dependencies: - debug: 4.4.3(supports-color@10.2.2) - transitivePeerDependencies: - - supports-color - axios@1.8.4: dependencies: follow-redirects: 1.15.9 @@ -5432,11 +5369,9 @@ snapshots: dependencies: ms: 2.1.3 - debug@4.4.3(supports-color@10.2.2): + debug@4.4.3: dependencies: ms: 2.1.3 - optionalDependencies: - supports-color: 10.2.2 decamelize@1.2.0: {} @@ -5867,8 +5802,6 @@ snapshots: eventemitter3@5.0.1: {} - events@3.3.0: {} - execa@8.0.1: dependencies: cross-spawn: 7.0.6 @@ -5887,10 +5820,6 @@ snapshots: dependencies: type: 2.7.3 - fake-mediastreamtrack@2.2.1: - dependencies: - '@lukeed/uuid': 2.0.1 - fast-deep-equal@3.1.3: {} fast-glob@3.3.3: @@ -6077,12 +6006,6 @@ snapshots: graphemer@1.4.0: {} - h264-profile-level-id@2.3.2(supports-color@10.2.2): - dependencies: - debug: 4.4.3(supports-color@10.2.2) - transitivePeerDependencies: - - supports-color - hammerjs@2.0.8: {} has-bigints@1.1.0: {} @@ -6531,18 +6454,6 @@ snapshots: mdurl@2.0.0: {} - mediasoup-client@3.20.0: - dependencies: - '@types/debug': 4.1.13 - '@types/events-alias': '@types/events@3.0.3' - awaitqueue: 3.3.0(supports-color@10.2.2) - debug: 4.4.3(supports-color@10.2.2) - events-alias: events@3.3.0 - fake-mediastreamtrack: 2.2.1 - h264-profile-level-id: 2.3.2(supports-color@10.2.2) - sdp-transform: 3.0.0 - supports-color: 10.2.2 - memoize-one@6.0.0: {} meow@12.1.1: {} @@ -6703,8 +6614,8 @@ snapshots: micromark@3.2.0: dependencies: - '@types/debug': 4.1.12 - debug: 4.4.0 + '@types/debug': 4.1.13 + debug: 4.4.3 decode-named-character-reference: 1.1.0 micromark-core-commonmark: 1.1.0 micromark-factory-space: 1.1.0 @@ -7142,8 +7053,6 @@ snapshots: scule@1.3.0: {} - sdp-transform@3.0.0: {} - semver@6.3.1: {} semver@7.7.2: {} @@ -7301,8 +7210,6 @@ snapshots: stylis@4.3.6: {} - supports-color@10.2.2: {} - supports-color@7.2.0: dependencies: has-flag: 4.0.0 diff --git a/src/page/live/StreamVideo.vue b/src/page/live/StreamVideo.vue deleted file mode 100644 index 766fea9..0000000 --- a/src/page/live/StreamVideo.vue +++ /dev/null @@ -1,36 +0,0 @@ - - - - - diff --git a/src/page/live/index.vue b/src/page/live/index.vue deleted file mode 100644 index 1c54270..0000000 --- a/src/page/live/index.vue +++ /dev/null @@ -1,322 +0,0 @@ - - - - - diff --git a/src/page/live/room-ws-client.ts b/src/page/live/room-ws-client.ts deleted file mode 100644 index 2d10af6..0000000 --- a/src/page/live/room-ws-client.ts +++ /dev/null @@ -1,276 +0,0 @@ -import * as mediasoupClient from 'mediasoup-client' -import type { Device, Transport, Consumer } from 'mediasoup-client/lib/types' -import type * as T from 'mediasoup-client/lib/types' - -export type Role = 'teacher' | 'student' - -type Handler = (msg: { type: string; data?: unknown }) => void - -export class RoomWsClient { - private ws!: WebSocket - private pending = new Map void; reject: (e: Error) => void }>() - private seq = 0 - private handlers: Handler[] = [] - private device: Device | null = null - private onWsClosed: (() => void) | null = null - private voluntaryClose = false - private signalingEverOpened = false - - constructor(private readonly url: string) {} - - onDisconnect(handler: (() => void) | null) { - this.onWsClosed = handler - } - - connect(timeoutMs = 20000): Promise { - return new Promise((resolve, reject) => { - let settled = false - const finish = (fn: () => void) => { - if (settled) return - settled = true - clearTimeout(timer) - fn() - } - - this.signalingEverOpened = false - this.ws = new WebSocket(this.url) - - const timer = setTimeout(() => { - finish(() => { - this.voluntaryClose = true - try { - this.ws.close() - } catch { - /* ignore */ - } - reject( - new Error( - `信令连接超时(${timeoutMs / 1000}s):请确认教师端已启动 SFU、端口正确;跨设备时「WS 主机」须填教师机局域网 IP,勿使用 127.0.0.1`, - ), - ) - }) - }, timeoutMs) - - this.ws.addEventListener('open', () => { - this.signalingEverOpened = true - finish(() => resolve()) - }) - this.ws.addEventListener('error', () => { - finish(() => - reject( - new Error( - '无法连接信令:请核对「端口」与教师端状态栏里「SFU 已监听」的端口一致;跨设备时「WS 主机」填教师机局域网 IP(勿用 127.0.0.1);并确认教师已启动 SFU、防火墙放行该端口。', - ), - ), - ) - }) - this.ws.addEventListener('message', ev => this.handleRaw(ev.data)) - this.ws.addEventListener('close', () => this.handleSocketClosed()) - }) - } - - private handleSocketClosed() { - for (const [, p] of this.pending) { - p.reject(new Error('WebSocket closed')) - } - this.pending.clear() - const notify = !this.voluntaryClose - const hadOpened = this.signalingEverOpened - this.voluntaryClose = false - this.signalingEverOpened = false - if (notify && hadOpened) { - try { - this.onWsClosed?.() - } finally { - this.onWsClosed = null - } - } else { - this.onWsClosed = null - } - } - - close() { - this.voluntaryClose = true - try { - this.ws?.close() - } catch { - /* ignore */ - } - } - - subscribe(handler: Handler) { - this.handlers.push(handler) - return () => { - this.handlers = this.handlers.filter(h => h !== handler) - } - } - - private emit(msg: { type: string; data?: unknown }) { - for (const h of this.handlers) h(msg) - } - - private handleRaw(raw: unknown) { - let msg: { type: string; requestId?: string; data?: unknown; error?: string } - try { - msg = JSON.parse(String(raw)) - } catch { - return - } - if (msg.type === 'response') { - const p = this.pending.get(msg.requestId || '') - if (p) { - this.pending.delete(msg.requestId || '') - if (msg.error) p.reject(new Error(msg.error)) - else p.resolve(msg.data) - } - return - } - this.emit(msg as { type: string; data?: unknown }) - } - - request(type: string, data: Record = {}): Promise { - if (this.ws.readyState !== WebSocket.OPEN) { - return Promise.reject(new Error('WebSocket is not open')) - } - const requestId = `r${++this.seq}` - return new Promise((resolve, reject) => { - this.pending.set(requestId, { resolve: resolve as (v: unknown) => void, reject }) - try { - this.ws.send(JSON.stringify({ type, requestId, data })) - } catch (e) { - this.pending.delete(requestId) - reject(e instanceof Error ? e : new Error(String(e))) - } - }) - } - - async join(roomId: string, role: Role, displayName: string) { - return (await this.request('join', { roomId, role, displayName })) as { - peerId: string - roomId: string - role: Role - rtpCapabilities: T.RtpCapabilities - existingProducers: { - peerId: string - producerId: string - kind: T.MediaKind - appData: Record - }[] - } - } - - async loadDevice(rtpCapabilities: T.RtpCapabilities) { - this.device = new mediasoupClient.Device() - await this.device.load({ routerRtpCapabilities: rtpCapabilities }) - return this.device - } - - getDevice(): Device { - if (!this.device) throw new Error('Device not loaded') - return this.device - } - - async createWebRtcTransport(direction: 'send' | 'recv') { - return (await this.request('createWebRtcTransport', { direction })) as { - id: string - iceParameters: T.IceParameters - iceCandidates: T.IceCandidate[] - dtlsParameters: T.DtlsParameters - } - } - - async connectWebRtcTransport(transportId: string, dtlsParameters: T.DtlsParameters) { - await this.request('connectWebRtcTransport', { transportId, dtlsParameters }) - } - - async createSendTransport(): Promise { - const device = this.getDevice() - const opt = await this.createWebRtcTransport('send') - const transport = device.createSendTransport(opt) - transport.on( - 'connect', - ( - { dtlsParameters }: { dtlsParameters: T.DtlsParameters }, - cb: () => void, - err: (e: Error) => void, - ) => { - this.connectWebRtcTransport(transport.id, dtlsParameters) - .then(() => cb()) - .catch(err) - }, - ) - transport.on( - 'produce', - async ( - { - kind, - rtpParameters, - appData, - }: { - kind: T.MediaKind - rtpParameters: T.RtpParameters - appData?: Record - }, - cb: (args: { id: string }) => void, - err: (e: Error) => void, - ) => { - try { - const { id } = (await this.request('produce', { - transportId: transport.id, - kind, - rtpParameters, - appData: appData || {}, - })) as { id: string } - cb({ id }) - } catch (e) { - err(e instanceof Error ? e : new Error(String(e))) - } - }, - ) - return transport - } - - async createRecvTransport(): Promise { - const device = this.getDevice() - const opt = await this.createWebRtcTransport('recv') - const transport = device.createRecvTransport(opt) - transport.on( - 'connect', - ( - { dtlsParameters }: { dtlsParameters: T.DtlsParameters }, - cb: () => void, - err: (e: Error) => void, - ) => { - this.connectWebRtcTransport(transport.id, dtlsParameters) - .then(() => cb()) - .catch(err) - }, - ) - return transport - } - - async consume(recvTransport: Transport, producerId: string): Promise { - const device = this.getDevice() - const { id, kind, rtpParameters, type } = (await this.request('consume', { - transportId: recvTransport.id, - producerId, - rtpCapabilities: device.rtpCapabilities, - })) as { - id: string - kind: T.MediaKind - rtpParameters: T.RtpParameters - type: T.ConsumerType - } - const consumer = await recvTransport.consume({ id, producerId, kind, rtpParameters, type }) - await this.request('resumeConsumer', { consumerId: consumer.id }) - return consumer - } - - async pauseConsumer(consumerId: string) { - await this.request('pauseConsumer', { consumerId }) - } - - async resumeConsumer(consumerId: string) { - await this.request('resumeConsumer', { consumerId }) - } -} diff --git a/src/page/live/routes.ts b/src/page/live/routes.ts deleted file mode 100644 index f34ce00..0000000 --- a/src/page/live/routes.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { RouteRecordRaw } from 'vue-router' - -const routes: RouteRecordRaw = { - name: 'live', - path: '/live', - component: () => import('./index.vue'), - meta: { title: '直播课堂(学员)', isPublic: true }, -} - -export default routes diff --git a/src/page/login/index.vue b/src/page/login/index.vue index 06a92fb..7bce199 100644 --- a/src/page/login/index.vue +++ b/src/page/login/index.vue @@ -9,7 +9,6 @@ import { isShufaAccount, SHUFA_PATH } from '@/page/app/app.ts' import hud from '@/utils/hud' import sass from '@/utils/sass' -const OSS_URL = import.meta.env.VITE_OSS_URL const logo = sass.getLogo() const qr = sass.getQrCode() const url = window.location.href @@ -31,6 +30,15 @@ const hidePassword = ref(true) // const loading = ref(false) const onLogin = debounce( async () => { + // native_api?.startReceiveTeacherScreen( + // JSON.stringify({ + // host: '192.168.0.108', + // port: 30443, + // roomId: 'classroom', + // name: '李明杰', + // }), + // ) + // return if (!account.value) { hud.error('请输入账号') return @@ -148,7 +156,7 @@ defineOptions({ diff --git a/src/stores/student-access.ts b/src/stores/student-access.ts index 89942d5..459acfd 100644 --- a/src/stores/student-access.ts +++ b/src/stores/student-access.ts @@ -16,7 +16,6 @@ const lead_black_list = [ const path_white_list = [ '/', '/login', - '/live', '/subject', // 学科 '/subject/learningArtifact', // 学习神器 '/system/setting', // 设置