feat:移除直播模块
This commit is contained in:
parent
4805c7ee35
commit
a1b0764e99
@ -43,7 +43,6 @@
|
|||||||
"lottie-web": "^5.12.2",
|
"lottie-web": "^5.12.2",
|
||||||
"markdown-it": "^14.1.0",
|
"markdown-it": "^14.1.0",
|
||||||
"mathjax": "3.0.1",
|
"mathjax": "3.0.1",
|
||||||
"mediasoup-client": "^3.20.0",
|
|
||||||
"mermaid-it-markdown": "^1.0.8",
|
"mermaid-it-markdown": "^1.0.8",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
"pinia": "^2.2.0",
|
"pinia": "^2.2.0",
|
||||||
|
|||||||
99
pnpm-lock.yaml
generated
99
pnpm-lock.yaml
generated
@ -92,9 +92,6 @@ importers:
|
|||||||
mathjax:
|
mathjax:
|
||||||
specifier: 3.0.1
|
specifier: 3.0.1
|
||||||
version: 3.0.1
|
version: 3.0.1
|
||||||
mediasoup-client:
|
|
||||||
specifier: ^3.20.0
|
|
||||||
version: 3.20.0
|
|
||||||
mermaid-it-markdown:
|
mermaid-it-markdown:
|
||||||
specifier: ^1.0.8
|
specifier: ^1.0.8
|
||||||
version: 1.0.8(mermaid@10.9.3)(typescript@5.8.2)
|
version: 1.0.8(mermaid@10.9.3)(typescript@5.8.2)
|
||||||
@ -658,14 +655,6 @@ packages:
|
|||||||
'@kurkle/color@0.3.4':
|
'@kurkle/color@0.3.4':
|
||||||
resolution: {integrity: sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==}
|
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':
|
'@microsoft/fetch-event-source@2.0.1':
|
||||||
resolution: {integrity: sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA==}
|
resolution: {integrity: sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA==}
|
||||||
|
|
||||||
@ -917,18 +906,12 @@ packages:
|
|||||||
'@types/d3-time@3.0.4':
|
'@types/d3-time@3.0.4':
|
||||||
resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==}
|
resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==}
|
||||||
|
|
||||||
'@types/debug@4.1.12':
|
|
||||||
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
|
|
||||||
|
|
||||||
'@types/debug@4.1.13':
|
'@types/debug@4.1.13':
|
||||||
resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==}
|
resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==}
|
||||||
|
|
||||||
'@types/estree@1.0.7':
|
'@types/estree@1.0.7':
|
||||||
resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==}
|
resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==}
|
||||||
|
|
||||||
'@types/events@3.0.3':
|
|
||||||
resolution: {integrity: sha512-trOc4AAUThEz9hapPtSd7wf5tiQKvTtu5b371UxXdTuqzIh0ArcRspRP0i0Viu+LXstIQ1z96t1nsPxT9ol01g==}
|
|
||||||
|
|
||||||
'@types/hammerjs@2.0.46':
|
'@types/hammerjs@2.0.46':
|
||||||
resolution: {integrity: sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==}
|
resolution: {integrity: sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==}
|
||||||
|
|
||||||
@ -1358,10 +1341,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
|
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
awaitqueue@3.3.0:
|
|
||||||
resolution: {integrity: sha512-zLxDhzQbzHmOyvxi7g3OlfR7jLrcmElStPxfLPpJkrFSDm71RSrY/MvsDA8Btlx8X1nOHUzGhQvc6bdUjL2f2w==}
|
|
||||||
engines: {node: '>=20'}
|
|
||||||
|
|
||||||
axios@1.8.4:
|
axios@1.8.4:
|
||||||
resolution: {integrity: sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==}
|
resolution: {integrity: sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==}
|
||||||
|
|
||||||
@ -2108,10 +2087,6 @@ packages:
|
|||||||
eventemitter3@5.0.1:
|
eventemitter3@5.0.1:
|
||||||
resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==}
|
resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==}
|
||||||
|
|
||||||
events@3.3.0:
|
|
||||||
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
|
|
||||||
engines: {node: '>=0.8.x'}
|
|
||||||
|
|
||||||
execa@8.0.1:
|
execa@8.0.1:
|
||||||
resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==}
|
resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==}
|
||||||
engines: {node: '>=16.17'}
|
engines: {node: '>=16.17'}
|
||||||
@ -2122,10 +2097,6 @@ packages:
|
|||||||
ext@1.7.0:
|
ext@1.7.0:
|
||||||
resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==}
|
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:
|
fast-deep-equal@3.1.3:
|
||||||
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
||||||
|
|
||||||
@ -2294,10 +2265,6 @@ packages:
|
|||||||
graphemer@1.4.0:
|
graphemer@1.4.0:
|
||||||
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
|
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
|
||||||
|
|
||||||
h264-profile-level-id@2.3.2:
|
|
||||||
resolution: {integrity: sha512-hnq1UDlw7WGJV6GCr/g7wnkHYUjdAY2bis9rgn2JqSdQS2WfVvnt1ZE9g8nTguracodf5LLKZOwURsDN49YtBQ==}
|
|
||||||
engines: {node: '>=20'}
|
|
||||||
|
|
||||||
hammerjs@2.0.8:
|
hammerjs@2.0.8:
|
||||||
resolution: {integrity: sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ==}
|
resolution: {integrity: sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ==}
|
||||||
engines: {node: '>=0.8.0'}
|
engines: {node: '>=0.8.0'}
|
||||||
@ -2754,10 +2721,6 @@ packages:
|
|||||||
mdurl@2.0.0:
|
mdurl@2.0.0:
|
||||||
resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==}
|
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:
|
memoize-one@6.0.0:
|
||||||
resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==}
|
resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==}
|
||||||
|
|
||||||
@ -3266,10 +3229,6 @@ packages:
|
|||||||
scule@1.3.0:
|
scule@1.3.0:
|
||||||
resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==}
|
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:
|
semver@6.3.1:
|
||||||
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
|
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@ -3413,10 +3372,6 @@ packages:
|
|||||||
stylis@4.3.6:
|
stylis@4.3.6:
|
||||||
resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==}
|
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:
|
supports-color@7.2.0:
|
||||||
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
|
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@ -4246,12 +4201,6 @@ snapshots:
|
|||||||
|
|
||||||
'@kurkle/color@0.3.4': {}
|
'@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': {}
|
'@microsoft/fetch-event-source@2.0.1': {}
|
||||||
|
|
||||||
'@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1':
|
'@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1':
|
||||||
@ -4420,18 +4369,12 @@ snapshots:
|
|||||||
|
|
||||||
'@types/d3-time@3.0.4': {}
|
'@types/d3-time@3.0.4': {}
|
||||||
|
|
||||||
'@types/debug@4.1.12':
|
|
||||||
dependencies:
|
|
||||||
'@types/ms': 2.1.0
|
|
||||||
|
|
||||||
'@types/debug@4.1.13':
|
'@types/debug@4.1.13':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/ms': 2.1.0
|
'@types/ms': 2.1.0
|
||||||
|
|
||||||
'@types/estree@1.0.7': {}
|
'@types/estree@1.0.7': {}
|
||||||
|
|
||||||
'@types/events@3.0.3': {}
|
|
||||||
|
|
||||||
'@types/hammerjs@2.0.46': {}
|
'@types/hammerjs@2.0.46': {}
|
||||||
|
|
||||||
'@types/json-schema@7.0.15': {}
|
'@types/json-schema@7.0.15': {}
|
||||||
@ -4980,12 +4923,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
possible-typed-array-names: 1.1.0
|
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:
|
axios@1.8.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
follow-redirects: 1.15.9
|
follow-redirects: 1.15.9
|
||||||
@ -5432,11 +5369,9 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ms: 2.1.3
|
ms: 2.1.3
|
||||||
|
|
||||||
debug@4.4.3(supports-color@10.2.2):
|
debug@4.4.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
ms: 2.1.3
|
ms: 2.1.3
|
||||||
optionalDependencies:
|
|
||||||
supports-color: 10.2.2
|
|
||||||
|
|
||||||
decamelize@1.2.0: {}
|
decamelize@1.2.0: {}
|
||||||
|
|
||||||
@ -5867,8 +5802,6 @@ snapshots:
|
|||||||
|
|
||||||
eventemitter3@5.0.1: {}
|
eventemitter3@5.0.1: {}
|
||||||
|
|
||||||
events@3.3.0: {}
|
|
||||||
|
|
||||||
execa@8.0.1:
|
execa@8.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
cross-spawn: 7.0.6
|
cross-spawn: 7.0.6
|
||||||
@ -5887,10 +5820,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
type: 2.7.3
|
type: 2.7.3
|
||||||
|
|
||||||
fake-mediastreamtrack@2.2.1:
|
|
||||||
dependencies:
|
|
||||||
'@lukeed/uuid': 2.0.1
|
|
||||||
|
|
||||||
fast-deep-equal@3.1.3: {}
|
fast-deep-equal@3.1.3: {}
|
||||||
|
|
||||||
fast-glob@3.3.3:
|
fast-glob@3.3.3:
|
||||||
@ -6077,12 +6006,6 @@ snapshots:
|
|||||||
|
|
||||||
graphemer@1.4.0: {}
|
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: {}
|
hammerjs@2.0.8: {}
|
||||||
|
|
||||||
has-bigints@1.1.0: {}
|
has-bigints@1.1.0: {}
|
||||||
@ -6531,18 +6454,6 @@ snapshots:
|
|||||||
|
|
||||||
mdurl@2.0.0: {}
|
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: {}
|
memoize-one@6.0.0: {}
|
||||||
|
|
||||||
meow@12.1.1: {}
|
meow@12.1.1: {}
|
||||||
@ -6703,8 +6614,8 @@ snapshots:
|
|||||||
|
|
||||||
micromark@3.2.0:
|
micromark@3.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/debug': 4.1.12
|
'@types/debug': 4.1.13
|
||||||
debug: 4.4.0
|
debug: 4.4.3
|
||||||
decode-named-character-reference: 1.1.0
|
decode-named-character-reference: 1.1.0
|
||||||
micromark-core-commonmark: 1.1.0
|
micromark-core-commonmark: 1.1.0
|
||||||
micromark-factory-space: 1.1.0
|
micromark-factory-space: 1.1.0
|
||||||
@ -7142,8 +7053,6 @@ snapshots:
|
|||||||
|
|
||||||
scule@1.3.0: {}
|
scule@1.3.0: {}
|
||||||
|
|
||||||
sdp-transform@3.0.0: {}
|
|
||||||
|
|
||||||
semver@6.3.1: {}
|
semver@6.3.1: {}
|
||||||
|
|
||||||
semver@7.7.2: {}
|
semver@7.7.2: {}
|
||||||
@ -7301,8 +7210,6 @@ snapshots:
|
|||||||
|
|
||||||
stylis@4.3.6: {}
|
stylis@4.3.6: {}
|
||||||
|
|
||||||
supports-color@10.2.2: {}
|
|
||||||
|
|
||||||
supports-color@7.2.0:
|
supports-color@7.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
has-flag: 4.0.0
|
has-flag: 4.0.0
|
||||||
|
|||||||
@ -1,36 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref, watch, nextTick } from 'vue'
|
|
||||||
|
|
||||||
const props = defineProps<{ stream: MediaStream | null }>()
|
|
||||||
const el = ref<HTMLVideoElement | null>(null)
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.stream,
|
|
||||||
async s => {
|
|
||||||
await nextTick()
|
|
||||||
const v = el.value
|
|
||||||
if (!v) return
|
|
||||||
v.srcObject = s
|
|
||||||
try {
|
|
||||||
await v.play()
|
|
||||||
} catch {
|
|
||||||
/* autoplay policy / transient */
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ immediate: true, flush: 'post' },
|
|
||||||
)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<!-- muted: remote video autoplay is often blocked without muted -->
|
|
||||||
<video ref="el" class="vid" autoplay playsinline muted />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.vid {
|
|
||||||
width: 100%;
|
|
||||||
max-height: 70vh;
|
|
||||||
background: #000;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,322 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import StreamVideo from './StreamVideo.vue'
|
|
||||||
import { RoomWsClient } from './room-ws-client'
|
|
||||||
import type { Transport } from 'mediasoup-client/lib/types'
|
|
||||||
|
|
||||||
const roomId = ref('classroom')
|
|
||||||
const displayName = ref('学员')
|
|
||||||
const wsHost = ref('127.0.0.1')
|
|
||||||
const wsPort = ref(30443)
|
|
||||||
const status = ref('未连接')
|
|
||||||
const teacherStream = ref<MediaStream | null>(null)
|
|
||||||
const localPreview = ref<MediaStream | null>(null)
|
|
||||||
|
|
||||||
let client: RoomWsClient | null = null
|
|
||||||
let unsub: (() => void) | null = null
|
|
||||||
let sendTransport: Transport | null = null
|
|
||||||
let recvTransport: Transport | null = null
|
|
||||||
let teacherPeerId: string | null = null
|
|
||||||
const consumed = new Set<string>()
|
|
||||||
const pendingTeacherProducerIds: string[] = []
|
|
||||||
|
|
||||||
function wsUrl() {
|
|
||||||
return `ws://${wsHost.value}:${wsPort.value}`
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetAfterRoomGone() {
|
|
||||||
teacherStream.value = null
|
|
||||||
recvTransport = null
|
|
||||||
sendTransport = null
|
|
||||||
teacherPeerId = null
|
|
||||||
consumed.clear()
|
|
||||||
pendingTeacherProducerIds.length = 0
|
|
||||||
localPreview.value?.getTracks().forEach(t => t.stop())
|
|
||||||
localPreview.value = null
|
|
||||||
unsub?.()
|
|
||||||
unsub = null
|
|
||||||
client?.close()
|
|
||||||
client = null
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
try {
|
|
||||||
const q = new URLSearchParams(window.location.search)
|
|
||||||
const hp = q.get('port')
|
|
||||||
const hh = q.get('host')
|
|
||||||
if (hh?.trim()) wsHost.value = hh.trim()
|
|
||||||
if (hp) {
|
|
||||||
const n = Number(hp)
|
|
||||||
if (Number.isFinite(n) && n > 0) wsPort.value = n
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
/* ignore */
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
async function consumeTeacher(recv: Transport, producerId: string) {
|
|
||||||
if (consumed.has(producerId)) return
|
|
||||||
if (!client) return
|
|
||||||
try {
|
|
||||||
const consumer = await client.consume(recv, producerId)
|
|
||||||
consumed.add(producerId)
|
|
||||||
teacherStream.value = new MediaStream([consumer.track])
|
|
||||||
consumer.on('producerclose', () => {
|
|
||||||
consumed.delete(producerId)
|
|
||||||
teacherStream.value = null
|
|
||||||
})
|
|
||||||
} catch (e) {
|
|
||||||
status.value = `拉取教师画面失败:${e instanceof Error ? e.message : String(e)}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function joinClass() {
|
|
||||||
teacherStream.value = null
|
|
||||||
localPreview.value?.getTracks().forEach(t => t.stop())
|
|
||||||
localPreview.value = null
|
|
||||||
consumed.clear()
|
|
||||||
pendingTeacherProducerIds.length = 0
|
|
||||||
unsub?.()
|
|
||||||
client?.close()
|
|
||||||
client = null
|
|
||||||
sendTransport = null
|
|
||||||
recvTransport = null
|
|
||||||
teacherPeerId = null
|
|
||||||
|
|
||||||
status.value = '连接信令…'
|
|
||||||
client = new RoomWsClient(wsUrl())
|
|
||||||
client.onDisconnect(() => {
|
|
||||||
status.value = '与教室断开,请重新点击「加入课堂」'
|
|
||||||
resetAfterRoomGone()
|
|
||||||
})
|
|
||||||
|
|
||||||
try {
|
|
||||||
await client.connect()
|
|
||||||
|
|
||||||
unsub = client.subscribe(msg => {
|
|
||||||
if (msg.type === 'newProducer' && msg.data) {
|
|
||||||
const d = msg.data as {
|
|
||||||
peerId: string
|
|
||||||
producerId: string
|
|
||||||
appData?: { role?: string }
|
|
||||||
}
|
|
||||||
if (d.appData?.role === 'teacher') {
|
|
||||||
teacherPeerId = d.peerId
|
|
||||||
if (recvTransport) void consumeTeacher(recvTransport, d.producerId)
|
|
||||||
else if (!pendingTeacherProducerIds.includes(d.producerId)) {
|
|
||||||
pendingTeacherProducerIds.push(d.producerId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (msg.type === 'peerLeft' && msg.data) {
|
|
||||||
const d = msg.data as { peerId: string; role?: string }
|
|
||||||
const teacherLeft =
|
|
||||||
d.role === 'teacher' || (teacherPeerId !== null && d.peerId === teacherPeerId)
|
|
||||||
if (teacherLeft) {
|
|
||||||
status.value = '未连接(教师端已断开,请重新点击「加入课堂」)'
|
|
||||||
resetAfterRoomGone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (msg.type === 'peerJoined' && msg.data) {
|
|
||||||
const d = msg.data as {
|
|
||||||
peerId: string
|
|
||||||
role?: string
|
|
||||||
self?: boolean
|
|
||||||
}
|
|
||||||
if (!d.self && d.role === 'teacher') {
|
|
||||||
teacherPeerId = d.peerId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (msg.type === 'producerClosed' && msg.data) {
|
|
||||||
const d = msg.data as { producerId: string }
|
|
||||||
consumed.delete(d.producerId)
|
|
||||||
const i = pendingTeacherProducerIds.indexOf(d.producerId)
|
|
||||||
if (i !== -1) pendingTeacherProducerIds.splice(i, 1)
|
|
||||||
teacherStream.value = null
|
|
||||||
status.value = '教师已停止共享屏幕;恢复后请重新加入或等待教师再次推流'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const joined = await client.join(roomId.value, 'student', displayName.value)
|
|
||||||
await client.loadDevice(joined.rtpCapabilities)
|
|
||||||
|
|
||||||
recvTransport = await client.createRecvTransport()
|
|
||||||
for (const pid of pendingTeacherProducerIds.splice(0)) {
|
|
||||||
await consumeTeacher(recvTransport, pid)
|
|
||||||
}
|
|
||||||
for (const ep of joined.existingProducers) {
|
|
||||||
if (ep.appData?.role === 'teacher') {
|
|
||||||
teacherPeerId = ep.peerId
|
|
||||||
await consumeTeacher(recvTransport, ep.producerId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
status.value =
|
|
||||||
'已加入课堂。请点击「共享我的屏幕」将画面推送给教师(手机浏览器上必须点一次该按钮)。'
|
|
||||||
} catch (e) {
|
|
||||||
unsub?.()
|
|
||||||
unsub = null
|
|
||||||
client?.close()
|
|
||||||
client = null
|
|
||||||
recvTransport = null
|
|
||||||
sendTransport = null
|
|
||||||
status.value = `加入失败:${e instanceof Error ? e.message : String(e)}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function pushScreenToTeacher(): Promise<boolean> {
|
|
||||||
if (!client) {
|
|
||||||
status.value = '请先加入课堂'
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (sendTransport) {
|
|
||||||
status.value = '已在向教师推送屏幕'
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if (!window.isSecureContext) {
|
|
||||||
status.value =
|
|
||||||
'当前页面不是安全上下文(例如用 http://局域网IP 打开)。屏幕共享会被浏览器禁用,请改用 HTTPS 部署,或使用 https://localhost / 本机调试。'
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
const gdm = navigator.mediaDevices?.getDisplayMedia
|
|
||||||
if (typeof gdm !== 'function') {
|
|
||||||
status.value =
|
|
||||||
'当前浏览器不支持屏幕共享(无 getDisplayMedia)。请换用 Chrome 等浏览器,或确认系统版本。'
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
status.value = '正在采集屏幕以推送给教师…'
|
|
||||||
let stream: MediaStream
|
|
||||||
try {
|
|
||||||
stream = await gdm.call(navigator.mediaDevices, {
|
|
||||||
video: true,
|
|
||||||
audio: false,
|
|
||||||
})
|
|
||||||
} catch (e) {
|
|
||||||
const msg = e instanceof Error ? e.message : String(e)
|
|
||||||
status.value = `未开始共享(${msg})。请直接点击「共享我的屏幕」(不要依赖加入课堂后自动共享);若通过 http 访问局域网地址,需改为 HTTPS。`
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
localPreview.value = stream
|
|
||||||
stream.getVideoTracks()[0].addEventListener('ended', () => {
|
|
||||||
sendTransport?.close()
|
|
||||||
sendTransport = null
|
|
||||||
localPreview.value?.getTracks().forEach(t => t.stop())
|
|
||||||
localPreview.value = null
|
|
||||||
status.value = '本机屏幕共享已结束;仍与课堂保持连接,可再次点击「共享我的屏幕」推送'
|
|
||||||
})
|
|
||||||
sendTransport = await client.createSendTransport()
|
|
||||||
await sendTransport.produce({
|
|
||||||
track: stream.getVideoTracks()[0],
|
|
||||||
appData: { source: 'screen' },
|
|
||||||
})
|
|
||||||
status.value = '已连接,正在向教师推送本机屏幕'
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
async function shareMyScreen() {
|
|
||||||
await pushScreenToTeacher()
|
|
||||||
}
|
|
||||||
|
|
||||||
async function leave() {
|
|
||||||
resetAfterRoomGone()
|
|
||||||
status.value = '已离开'
|
|
||||||
}
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
void leave()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="live-student px-w">
|
|
||||||
<div class="app">
|
|
||||||
<header class="bar">
|
|
||||||
<h1>学员端</h1>
|
|
||||||
<div class="row">
|
|
||||||
<label>房间 <input v-model="roomId" /></label>
|
|
||||||
<label>显示名 <input v-model="displayName" /></label>
|
|
||||||
<label>WS 主机 <input v-model="wsHost" placeholder="教师机 IP" /></label>
|
|
||||||
<label>
|
|
||||||
端口
|
|
||||||
<input
|
|
||||||
v-model.number="wsPort"
|
|
||||||
type="number"
|
|
||||||
title="须与教师端界面上的端口一致(教师端可能为随机端口)"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<button type="button" class="primary" @click="joinClass">加入课堂</button>
|
|
||||||
<button type="button" @click="shareMyScreen">共享我的屏幕</button>
|
|
||||||
<button type="button" @click="leave">离开</button>
|
|
||||||
</div>
|
|
||||||
<p class="status">{{ status }}</p>
|
|
||||||
</header>
|
|
||||||
<section class="main">
|
|
||||||
<h2>教师屏幕</h2>
|
|
||||||
<StreamVideo v-if="teacherStream" :stream="teacherStream" />
|
|
||||||
<p v-else class="muted">等待教师推流…</p>
|
|
||||||
</section>
|
|
||||||
<section class="pip">
|
|
||||||
<h2>我的屏幕(预览)</h2>
|
|
||||||
<StreamVideo v-if="localPreview" :stream="localPreview" />
|
|
||||||
<p v-else class="muted">未共享</p>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.live-student {
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
.app {
|
|
||||||
font-family: system-ui, sans-serif;
|
|
||||||
padding: 12px;
|
|
||||||
color: #e8e8ef;
|
|
||||||
background: #0f1419;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
.bar h1 {
|
|
||||||
margin: 0 0 8px;
|
|
||||||
font-size: 1.25rem;
|
|
||||||
}
|
|
||||||
.row {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 8px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.row input {
|
|
||||||
width: 130px;
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
padding: 6px 12px;
|
|
||||||
border-radius: 6px;
|
|
||||||
border: 1px solid #444;
|
|
||||||
background: #252b36;
|
|
||||||
color: inherit;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
button.primary {
|
|
||||||
background: #2f9e6d;
|
|
||||||
border-color: #2f9e6d;
|
|
||||||
}
|
|
||||||
.status {
|
|
||||||
margin: 8px 0 0;
|
|
||||||
opacity: 0.85;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
.main,
|
|
||||||
.pip {
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
|
||||||
.main h2,
|
|
||||||
.pip h2 {
|
|
||||||
font-size: 1rem;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
.muted {
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
.pip .vid {
|
|
||||||
max-height: 200px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -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<string, { resolve: (v: unknown) => 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<void> {
|
|
||||||
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<T>(type: string, data: Record<string, unknown> = {}): Promise<T> {
|
|
||||||
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<string, unknown>
|
|
||||||
}[]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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<Transport> {
|
|
||||||
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<string, unknown>
|
|
||||||
},
|
|
||||||
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<Transport> {
|
|
||||||
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<Consumer> {
|
|
||||||
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 })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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
|
|
||||||
@ -9,7 +9,6 @@ import { isShufaAccount, SHUFA_PATH } from '@/page/app/app.ts'
|
|||||||
import hud from '@/utils/hud'
|
import hud from '@/utils/hud'
|
||||||
import sass from '@/utils/sass'
|
import sass from '@/utils/sass'
|
||||||
|
|
||||||
const OSS_URL = import.meta.env.VITE_OSS_URL
|
|
||||||
const logo = sass.getLogo()
|
const logo = sass.getLogo()
|
||||||
const qr = sass.getQrCode()
|
const qr = sass.getQrCode()
|
||||||
const url = window.location.href
|
const url = window.location.href
|
||||||
@ -31,6 +30,15 @@ const hidePassword = ref(true)
|
|||||||
// const loading = ref(false)
|
// const loading = ref(false)
|
||||||
const onLogin = debounce(
|
const onLogin = debounce(
|
||||||
async () => {
|
async () => {
|
||||||
|
// native_api?.startReceiveTeacherScreen(
|
||||||
|
// JSON.stringify({
|
||||||
|
// host: '192.168.0.108',
|
||||||
|
// port: 30443,
|
||||||
|
// roomId: 'classroom',
|
||||||
|
// name: '李明杰',
|
||||||
|
// }),
|
||||||
|
// )
|
||||||
|
// return
|
||||||
if (!account.value) {
|
if (!account.value) {
|
||||||
hud.error('请输入账号')
|
hud.error('请输入账号')
|
||||||
return
|
return
|
||||||
@ -148,7 +156,7 @@ defineOptions({
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="showIcp" class="icp-info">
|
<div v-if="showIcp" class="icp-info">
|
||||||
<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener noreferrer">
|
<a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener noreferrer">
|
||||||
粤ICP备2023060369号-9
|
蜀ICP备2026014928号-1
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -16,7 +16,6 @@ const lead_black_list = [
|
|||||||
const path_white_list = [
|
const path_white_list = [
|
||||||
'/',
|
'/',
|
||||||
'/login',
|
'/login',
|
||||||
'/live',
|
|
||||||
'/subject', // 学科
|
'/subject', // 学科
|
||||||
'/subject/learningArtifact', // 学习神器
|
'/subject/learningArtifact', // 学习神器
|
||||||
'/system/setting', // 设置
|
'/system/setting', // 设置
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user