deepsea-components 5.17.36 → 5.18.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/dist/components/RtspPlayer.cjs +113 -0
- package/dist/components/RtspPlayer.d.ts +9 -0
- package/dist/components/RtspPlayer.js +79 -0
- package/dist/components/rtspPlayer/reader.cjs +423 -0
- package/dist/components/rtspPlayer/reader.d.ts +46 -0
- package/dist/components/rtspPlayer/reader.js +389 -0
- package/dist/index.cjs +48 -39
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +3 -3
- package/src/components/RtspPlayer.tsx +93 -0
- package/src/components/rtspPlayer/reader.ts +554 -0
- package/src/index.ts +1 -0
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
class MediaMTXWebRTCReader {
|
|
2
|
+
retryPause = 2000;
|
|
3
|
+
conf;
|
|
4
|
+
state = "getting_codecs";
|
|
5
|
+
restartTimeout = null;
|
|
6
|
+
pc = null;
|
|
7
|
+
offerData = null;
|
|
8
|
+
sessionUrl = null;
|
|
9
|
+
queuedCandidates = [];
|
|
10
|
+
nonAdvertisedCodecs = [];
|
|
11
|
+
constructor(conf){
|
|
12
|
+
this.conf = conf;
|
|
13
|
+
this.getNonAdvertisedCodecs();
|
|
14
|
+
}
|
|
15
|
+
close() {
|
|
16
|
+
this.state = "closed";
|
|
17
|
+
if (this.pc) {
|
|
18
|
+
this.pc.close();
|
|
19
|
+
this.pc = null;
|
|
20
|
+
}
|
|
21
|
+
if (null !== this.restartTimeout) {
|
|
22
|
+
clearTimeout(this.restartTimeout);
|
|
23
|
+
this.restartTimeout = null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
static supportsNonAdvertisedCodec(codec, fmtp) {
|
|
27
|
+
return new Promise((resolve)=>{
|
|
28
|
+
const pc = new RTCPeerConnection({
|
|
29
|
+
iceServers: []
|
|
30
|
+
});
|
|
31
|
+
const mediaType = "audio";
|
|
32
|
+
let payloadType = "";
|
|
33
|
+
pc.addTransceiver(mediaType, {
|
|
34
|
+
direction: "recvonly"
|
|
35
|
+
});
|
|
36
|
+
pc.createOffer().then((offer)=>{
|
|
37
|
+
if (void 0 === offer.sdp) throw new Error("SDP not present");
|
|
38
|
+
if (offer.sdp.includes(` ${codec}`)) throw new Error("already present");
|
|
39
|
+
const sections = offer.sdp.split(`m=${mediaType}`);
|
|
40
|
+
const payloadTypes = sections.slice(1).map((section)=>section.split("\r\n")[0].split(" ").slice(3)).reduce((prev, cur)=>[
|
|
41
|
+
...prev,
|
|
42
|
+
...cur
|
|
43
|
+
], []);
|
|
44
|
+
payloadType = MediaMTXWebRTCReader.reservePayloadType(payloadTypes);
|
|
45
|
+
const lines = sections[1].split("\r\n");
|
|
46
|
+
lines[0] += ` ${payloadType}`;
|
|
47
|
+
lines.splice(lines.length - 1, 0, `a=rtpmap:${payloadType} ${codec}`);
|
|
48
|
+
if (void 0 !== fmtp) lines.splice(lines.length - 1, 0, `a=fmtp:${payloadType} ${fmtp}`);
|
|
49
|
+
sections[1] = lines.join("\r\n");
|
|
50
|
+
offer.sdp = sections.join(`m=${mediaType}`);
|
|
51
|
+
return pc.setLocalDescription(offer);
|
|
52
|
+
}).then(()=>pc.setRemoteDescription(new RTCSessionDescription({
|
|
53
|
+
type: "answer",
|
|
54
|
+
sdp: `v=0\r\no=- 6539324223450680508 0 IN IP4 0.0.0.0\r\ns=-\r\nt=0 0\r\na=fingerprint:sha-256 0D:9F:78:15:42:B5:4B:E6:E2:94:3E:5B:37:78:E1:4B:54:59:A3:36:3A:E5:05:EB:27:EE:8F:D2:2D:41:29:25\r\nm=${mediaType} 9 UDP/TLS/RTP/SAVPF ${payloadType}\r\nc=IN IP4 0.0.0.0\r\na=ice-pwd:7c3bf4770007e7432ee4ea4d697db675\r\na=ice-ufrag:29e036dc\r\na=sendonly\r\na=rtcp-mux\r\na=rtpmap:${payloadType} ${codec}\r\n` + (void 0 !== fmtp ? `a=fmtp:${payloadType} ${fmtp}\r\n` : "")
|
|
55
|
+
}))).then(()=>resolve(true)).catch(()=>resolve(false)).finally(()=>pc.close());
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
static unquoteCredential(value) {
|
|
59
|
+
return JSON.parse(`"${value}"`);
|
|
60
|
+
}
|
|
61
|
+
static linkToIceServers(links) {
|
|
62
|
+
if (null === links) return [];
|
|
63
|
+
return links.split(", ").map((link)=>{
|
|
64
|
+
const match = link.match(/^<(.+?)>; rel="ice-server"(; username="(.*?)"; credential="(.*?)"; credential-type="password")?/i);
|
|
65
|
+
if (!match) return null;
|
|
66
|
+
const ret = {
|
|
67
|
+
urls: [
|
|
68
|
+
match[1]
|
|
69
|
+
]
|
|
70
|
+
};
|
|
71
|
+
if (void 0 !== match[3] && void 0 !== match[4]) {
|
|
72
|
+
ret.username = MediaMTXWebRTCReader.unquoteCredential(match[3]);
|
|
73
|
+
ret.credential = MediaMTXWebRTCReader.unquoteCredential(match[4]);
|
|
74
|
+
ret.credentialType = "password";
|
|
75
|
+
}
|
|
76
|
+
return ret;
|
|
77
|
+
}).filter((server)=>null !== server);
|
|
78
|
+
}
|
|
79
|
+
static parseOffer(sdp) {
|
|
80
|
+
const ret = {
|
|
81
|
+
iceUfrag: "",
|
|
82
|
+
icePwd: "",
|
|
83
|
+
medias: []
|
|
84
|
+
};
|
|
85
|
+
for (const line of sdp.split("\r\n"))if (line.startsWith("m=")) ret.medias.push(line.slice(2));
|
|
86
|
+
else if ("" === ret.iceUfrag && line.startsWith("a=ice-ufrag:")) ret.iceUfrag = line.slice(12);
|
|
87
|
+
else if ("" === ret.icePwd && line.startsWith("a=ice-pwd:")) ret.icePwd = line.slice(10);
|
|
88
|
+
return ret;
|
|
89
|
+
}
|
|
90
|
+
static reservePayloadType(payloadTypes) {
|
|
91
|
+
for(let i = 30; i <= 127; i++)if ((i <= 63 || i >= 96) && !payloadTypes.includes(i.toString())) {
|
|
92
|
+
const payloadType = i.toString();
|
|
93
|
+
payloadTypes.push(payloadType);
|
|
94
|
+
return payloadType;
|
|
95
|
+
}
|
|
96
|
+
throw new Error("unable to find a free payload type");
|
|
97
|
+
}
|
|
98
|
+
static enableStereoPcmau(payloadTypes, section) {
|
|
99
|
+
const lines = section.split("\r\n");
|
|
100
|
+
let payloadType = MediaMTXWebRTCReader.reservePayloadType(payloadTypes);
|
|
101
|
+
lines[0] += ` ${payloadType}`;
|
|
102
|
+
lines.splice(lines.length - 1, 0, `a=rtpmap:${payloadType} PCMU/8000/2`);
|
|
103
|
+
lines.splice(lines.length - 1, 0, `a=rtcp-fb:${payloadType} transport-cc`);
|
|
104
|
+
payloadType = MediaMTXWebRTCReader.reservePayloadType(payloadTypes);
|
|
105
|
+
lines[0] += ` ${payloadType}`;
|
|
106
|
+
lines.splice(lines.length - 1, 0, `a=rtpmap:${payloadType} PCMA/8000/2`);
|
|
107
|
+
lines.splice(lines.length - 1, 0, `a=rtcp-fb:${payloadType} transport-cc`);
|
|
108
|
+
return lines.join("\r\n");
|
|
109
|
+
}
|
|
110
|
+
static enableMultichannelOpus(payloadTypes, section) {
|
|
111
|
+
const lines = section.split("\r\n");
|
|
112
|
+
let payloadType = MediaMTXWebRTCReader.reservePayloadType(payloadTypes);
|
|
113
|
+
lines[0] += ` ${payloadType}`;
|
|
114
|
+
lines.splice(lines.length - 1, 0, `a=rtpmap:${payloadType} multiopus/48000/3`);
|
|
115
|
+
lines.splice(lines.length - 1, 0, `a=fmtp:${payloadType} channel_mapping=0,2,1;num_streams=2;coupled_streams=1`);
|
|
116
|
+
lines.splice(lines.length - 1, 0, `a=rtcp-fb:${payloadType} transport-cc`);
|
|
117
|
+
payloadType = MediaMTXWebRTCReader.reservePayloadType(payloadTypes);
|
|
118
|
+
lines[0] += ` ${payloadType}`;
|
|
119
|
+
lines.splice(lines.length - 1, 0, `a=rtpmap:${payloadType} multiopus/48000/4`);
|
|
120
|
+
lines.splice(lines.length - 1, 0, `a=fmtp:${payloadType} channel_mapping=0,1,2,3;num_streams=2;coupled_streams=2`);
|
|
121
|
+
lines.splice(lines.length - 1, 0, `a=rtcp-fb:${payloadType} transport-cc`);
|
|
122
|
+
payloadType = MediaMTXWebRTCReader.reservePayloadType(payloadTypes);
|
|
123
|
+
lines[0] += ` ${payloadType}`;
|
|
124
|
+
lines.splice(lines.length - 1, 0, `a=rtpmap:${payloadType} multiopus/48000/5`);
|
|
125
|
+
lines.splice(lines.length - 1, 0, `a=fmtp:${payloadType} channel_mapping=0,4,1,2,3;num_streams=3;coupled_streams=2`);
|
|
126
|
+
lines.splice(lines.length - 1, 0, `a=rtcp-fb:${payloadType} transport-cc`);
|
|
127
|
+
payloadType = MediaMTXWebRTCReader.reservePayloadType(payloadTypes);
|
|
128
|
+
lines[0] += ` ${payloadType}`;
|
|
129
|
+
lines.splice(lines.length - 1, 0, `a=rtpmap:${payloadType} multiopus/48000/6`);
|
|
130
|
+
lines.splice(lines.length - 1, 0, `a=fmtp:${payloadType} channel_mapping=0,4,1,2,3,5;num_streams=4;coupled_streams=2`);
|
|
131
|
+
lines.splice(lines.length - 1, 0, `a=rtcp-fb:${payloadType} transport-cc`);
|
|
132
|
+
payloadType = MediaMTXWebRTCReader.reservePayloadType(payloadTypes);
|
|
133
|
+
lines[0] += ` ${payloadType}`;
|
|
134
|
+
lines.splice(lines.length - 1, 0, `a=rtpmap:${payloadType} multiopus/48000/7`);
|
|
135
|
+
lines.splice(lines.length - 1, 0, `a=fmtp:${payloadType} channel_mapping=0,4,1,2,3,5,6;num_streams=4;coupled_streams=4`);
|
|
136
|
+
lines.splice(lines.length - 1, 0, `a=rtcp-fb:${payloadType} transport-cc`);
|
|
137
|
+
payloadType = MediaMTXWebRTCReader.reservePayloadType(payloadTypes);
|
|
138
|
+
lines[0] += ` ${payloadType}`;
|
|
139
|
+
lines.splice(lines.length - 1, 0, `a=rtpmap:${payloadType} multiopus/48000/8`);
|
|
140
|
+
lines.splice(lines.length - 1, 0, `a=fmtp:${payloadType} channel_mapping=0,6,1,4,5,2,3,7;num_streams=5;coupled_streams=4`);
|
|
141
|
+
lines.splice(lines.length - 1, 0, `a=rtcp-fb:${payloadType} transport-cc`);
|
|
142
|
+
return lines.join("\r\n");
|
|
143
|
+
}
|
|
144
|
+
static enableL16(payloadTypes, section) {
|
|
145
|
+
const lines = section.split("\r\n");
|
|
146
|
+
let payloadType = MediaMTXWebRTCReader.reservePayloadType(payloadTypes);
|
|
147
|
+
lines[0] += ` ${payloadType}`;
|
|
148
|
+
lines.splice(lines.length - 1, 0, `a=rtpmap:${payloadType} L16/8000/2`);
|
|
149
|
+
lines.splice(lines.length - 1, 0, `a=rtcp-fb:${payloadType} transport-cc`);
|
|
150
|
+
payloadType = MediaMTXWebRTCReader.reservePayloadType(payloadTypes);
|
|
151
|
+
lines[0] += ` ${payloadType}`;
|
|
152
|
+
lines.splice(lines.length - 1, 0, `a=rtpmap:${payloadType} L16/16000/2`);
|
|
153
|
+
lines.splice(lines.length - 1, 0, `a=rtcp-fb:${payloadType} transport-cc`);
|
|
154
|
+
payloadType = MediaMTXWebRTCReader.reservePayloadType(payloadTypes);
|
|
155
|
+
lines[0] += ` ${payloadType}`;
|
|
156
|
+
lines.splice(lines.length - 1, 0, `a=rtpmap:${payloadType} L16/48000/2`);
|
|
157
|
+
lines.splice(lines.length - 1, 0, `a=rtcp-fb:${payloadType} transport-cc`);
|
|
158
|
+
return lines.join("\r\n");
|
|
159
|
+
}
|
|
160
|
+
static enableStereoOpus(section) {
|
|
161
|
+
let opusPayloadFormat = "";
|
|
162
|
+
const lines = section.split("\r\n");
|
|
163
|
+
for(let i = 0; i < lines.length; i++)if (lines[i].startsWith("a=rtpmap:") && lines[i].toLowerCase().includes("opus/")) {
|
|
164
|
+
opusPayloadFormat = lines[i].slice(9).split(" ")[0];
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
if ("" === opusPayloadFormat) return section;
|
|
168
|
+
for(let i = 0; i < lines.length; i++)if (lines[i].startsWith(`a=fmtp:${opusPayloadFormat} `)) {
|
|
169
|
+
if (!lines[i].includes("stereo")) lines[i] += ";stereo=1";
|
|
170
|
+
if (!lines[i].includes("sprop-stereo")) lines[i] += ";sprop-stereo=1";
|
|
171
|
+
}
|
|
172
|
+
return lines.join("\r\n");
|
|
173
|
+
}
|
|
174
|
+
static editOffer(sdp, nonAdvertisedCodecs) {
|
|
175
|
+
const sections = sdp.split("m=");
|
|
176
|
+
const payloadTypes = sections.slice(1).map((section)=>section.split("\r\n")[0].split(" ").slice(3)).reduce((prev, cur)=>[
|
|
177
|
+
...prev,
|
|
178
|
+
...cur
|
|
179
|
+
], []);
|
|
180
|
+
for(let i = 1; i < sections.length; i++)if (sections[i].startsWith("audio")) {
|
|
181
|
+
sections[i] = MediaMTXWebRTCReader.enableStereoOpus(sections[i]);
|
|
182
|
+
if (nonAdvertisedCodecs.includes("pcma/8000/2")) sections[i] = MediaMTXWebRTCReader.enableStereoPcmau(payloadTypes, sections[i]);
|
|
183
|
+
if (nonAdvertisedCodecs.includes("multiopus/48000/6")) sections[i] = MediaMTXWebRTCReader.enableMultichannelOpus(payloadTypes, sections[i]);
|
|
184
|
+
if (nonAdvertisedCodecs.includes("L16/48000/2")) sections[i] = MediaMTXWebRTCReader.enableL16(payloadTypes, sections[i]);
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
return sections.join("m=");
|
|
188
|
+
}
|
|
189
|
+
static generateSdpFragment(offerData, candidates) {
|
|
190
|
+
const candidatesByMedia = {};
|
|
191
|
+
for (const candidate of candidates){
|
|
192
|
+
const mid = candidate.sdpMLineIndex;
|
|
193
|
+
if (null !== mid) {
|
|
194
|
+
candidatesByMedia[mid] ??= [];
|
|
195
|
+
candidatesByMedia[mid].push(candidate);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
let frag = `a=ice-ufrag:${offerData.iceUfrag}\r\na=ice-pwd:${offerData.icePwd}\r\n`;
|
|
199
|
+
for(let mid = 0; mid < offerData.medias.length; mid++){
|
|
200
|
+
const mediaCandidates = candidatesByMedia[mid];
|
|
201
|
+
if (void 0 !== mediaCandidates) {
|
|
202
|
+
frag += `m=${offerData.medias[mid]}\r\na=mid:${mid}\r\n`;
|
|
203
|
+
for (const candidate of mediaCandidates)frag += `a=${candidate.candidate}\r\n`;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return frag;
|
|
207
|
+
}
|
|
208
|
+
handleError(err) {
|
|
209
|
+
if ("running" === this.state) {
|
|
210
|
+
var _this_conf_onError, _this_conf;
|
|
211
|
+
if (this.pc) {
|
|
212
|
+
this.pc.close();
|
|
213
|
+
this.pc = null;
|
|
214
|
+
}
|
|
215
|
+
this.offerData = null;
|
|
216
|
+
if (null !== this.sessionUrl) {
|
|
217
|
+
fetch(this.sessionUrl, {
|
|
218
|
+
method: "DELETE"
|
|
219
|
+
});
|
|
220
|
+
this.sessionUrl = null;
|
|
221
|
+
}
|
|
222
|
+
this.queuedCandidates = [];
|
|
223
|
+
this.state = "restarting";
|
|
224
|
+
this.restartTimeout = window.setTimeout(()=>{
|
|
225
|
+
this.restartTimeout = null;
|
|
226
|
+
this.state = "running";
|
|
227
|
+
this.start();
|
|
228
|
+
}, this.retryPause);
|
|
229
|
+
null == (_this_conf_onError = (_this_conf = this.conf).onError) || _this_conf_onError.call(_this_conf, `${err}, retrying in some seconds`);
|
|
230
|
+
} else if ("getting_codecs" === this.state) {
|
|
231
|
+
var _this_conf_onError1, _this_conf1;
|
|
232
|
+
this.state = "failed";
|
|
233
|
+
null == (_this_conf_onError1 = (_this_conf1 = this.conf).onError) || _this_conf_onError1.call(_this_conf1, err);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
getNonAdvertisedCodecs() {
|
|
237
|
+
Promise.all([
|
|
238
|
+
[
|
|
239
|
+
"pcma/8000/2"
|
|
240
|
+
],
|
|
241
|
+
[
|
|
242
|
+
"multiopus/48000/6",
|
|
243
|
+
"channel_mapping=0,4,1,2,3,5;num_streams=4;coupled_streams=2"
|
|
244
|
+
],
|
|
245
|
+
[
|
|
246
|
+
"L16/48000/2"
|
|
247
|
+
]
|
|
248
|
+
].map((codec)=>MediaMTXWebRTCReader.supportsNonAdvertisedCodec(codec[0], codec[1]).then((isSupported)=>{
|
|
249
|
+
if (!isSupported) return false;
|
|
250
|
+
return codec[0];
|
|
251
|
+
}))).then((codecs)=>codecs.filter((codec)=>false !== codec)).then((codecs)=>{
|
|
252
|
+
if ("getting_codecs" !== this.state) throw new Error("closed");
|
|
253
|
+
this.nonAdvertisedCodecs = codecs;
|
|
254
|
+
this.state = "running";
|
|
255
|
+
this.start();
|
|
256
|
+
}).catch((err)=>this.handleError(String(err)));
|
|
257
|
+
}
|
|
258
|
+
start() {
|
|
259
|
+
this.requestICEServers().then((iceServers)=>this.setupPeerConnection(iceServers)).then((offer)=>this.sendOffer(offer)).then((answer)=>this.setAnswer(answer)).catch((err)=>this.handleError(String(err)));
|
|
260
|
+
}
|
|
261
|
+
authHeader() {
|
|
262
|
+
if (void 0 !== this.conf.user && "" !== this.conf.user) {
|
|
263
|
+
const credentials = btoa(`${this.conf.user}:${this.conf.pass ?? ""}`);
|
|
264
|
+
return {
|
|
265
|
+
Authorization: `Basic ${credentials}`
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
if (void 0 !== this.conf.token && "" !== this.conf.token) return {
|
|
269
|
+
Authorization: `Bearer ${this.conf.token}`
|
|
270
|
+
};
|
|
271
|
+
return {};
|
|
272
|
+
}
|
|
273
|
+
requestICEServers() {
|
|
274
|
+
return fetch(this.conf.url, {
|
|
275
|
+
method: "OPTIONS",
|
|
276
|
+
headers: {
|
|
277
|
+
...this.authHeader()
|
|
278
|
+
}
|
|
279
|
+
}).then((res)=>MediaMTXWebRTCReader.linkToIceServers(res.headers.get("Link")));
|
|
280
|
+
}
|
|
281
|
+
setupPeerConnection(iceServers) {
|
|
282
|
+
if ("running" !== this.state) throw new Error("closed");
|
|
283
|
+
const pc = new RTCPeerConnection({
|
|
284
|
+
iceServers,
|
|
285
|
+
sdpSemantics: "unified-plan"
|
|
286
|
+
});
|
|
287
|
+
this.pc = pc;
|
|
288
|
+
const direction = "recvonly";
|
|
289
|
+
pc.addTransceiver("video", {
|
|
290
|
+
direction
|
|
291
|
+
});
|
|
292
|
+
pc.addTransceiver("audio", {
|
|
293
|
+
direction
|
|
294
|
+
});
|
|
295
|
+
pc.createDataChannel("");
|
|
296
|
+
pc.onicecandidate = (evt)=>this.onLocalCandidate(evt);
|
|
297
|
+
pc.onconnectionstatechange = ()=>this.onConnectionState();
|
|
298
|
+
pc.ontrack = (evt)=>this.onTrack(evt);
|
|
299
|
+
pc.ondatachannel = (evt)=>this.onDataChannel(evt);
|
|
300
|
+
return pc.createOffer().then((offer)=>{
|
|
301
|
+
if (void 0 === offer.sdp) throw new Error("SDP not present");
|
|
302
|
+
offer.sdp = MediaMTXWebRTCReader.editOffer(offer.sdp, this.nonAdvertisedCodecs);
|
|
303
|
+
this.offerData = MediaMTXWebRTCReader.parseOffer(offer.sdp);
|
|
304
|
+
return pc.setLocalDescription(offer).then(()=>offer.sdp);
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
sendOffer(offer) {
|
|
308
|
+
if ("running" !== this.state) throw new Error("closed");
|
|
309
|
+
return fetch(this.conf.url, {
|
|
310
|
+
method: "POST",
|
|
311
|
+
headers: {
|
|
312
|
+
...this.authHeader(),
|
|
313
|
+
"Content-Type": "application/sdp"
|
|
314
|
+
},
|
|
315
|
+
body: offer
|
|
316
|
+
}).then((res)=>{
|
|
317
|
+
switch(res.status){
|
|
318
|
+
case 201:
|
|
319
|
+
break;
|
|
320
|
+
case 404:
|
|
321
|
+
throw new Error("stream not found");
|
|
322
|
+
case 400:
|
|
323
|
+
return res.json().then((body)=>{
|
|
324
|
+
throw new Error(body.error ?? "bad request");
|
|
325
|
+
});
|
|
326
|
+
default:
|
|
327
|
+
throw new Error(`bad status code ${res.status}`);
|
|
328
|
+
}
|
|
329
|
+
const location = res.headers.get("location");
|
|
330
|
+
if (null === location) throw new Error("session location not present");
|
|
331
|
+
this.sessionUrl = new URL(location, this.conf.url).toString();
|
|
332
|
+
return res.text();
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
setAnswer(answer) {
|
|
336
|
+
if ("running" !== this.state) throw new Error("closed");
|
|
337
|
+
if (!this.pc) throw new Error("peer connection not present");
|
|
338
|
+
return this.pc.setRemoteDescription(new RTCSessionDescription({
|
|
339
|
+
type: "answer",
|
|
340
|
+
sdp: answer
|
|
341
|
+
})).then(()=>{
|
|
342
|
+
if ("running" !== this.state) return;
|
|
343
|
+
if (0 !== this.queuedCandidates.length) {
|
|
344
|
+
this.sendLocalCandidates(this.queuedCandidates);
|
|
345
|
+
this.queuedCandidates = [];
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
onLocalCandidate(evt) {
|
|
350
|
+
if ("running" !== this.state) return;
|
|
351
|
+
if (null !== evt.candidate) if (null === this.sessionUrl) this.queuedCandidates.push(evt.candidate);
|
|
352
|
+
else this.sendLocalCandidates([
|
|
353
|
+
evt.candidate
|
|
354
|
+
]);
|
|
355
|
+
}
|
|
356
|
+
sendLocalCandidates(candidates) {
|
|
357
|
+
if (null === this.sessionUrl || null === this.offerData) return;
|
|
358
|
+
fetch(this.sessionUrl, {
|
|
359
|
+
method: "PATCH",
|
|
360
|
+
headers: {
|
|
361
|
+
"Content-Type": "application/trickle-ice-sdpfrag",
|
|
362
|
+
"If-Match": "*"
|
|
363
|
+
},
|
|
364
|
+
body: MediaMTXWebRTCReader.generateSdpFragment(this.offerData, candidates)
|
|
365
|
+
}).then((res)=>{
|
|
366
|
+
switch(res.status){
|
|
367
|
+
case 204:
|
|
368
|
+
break;
|
|
369
|
+
case 404:
|
|
370
|
+
throw new Error("stream not found");
|
|
371
|
+
default:
|
|
372
|
+
throw new Error(`bad status code ${res.status}`);
|
|
373
|
+
}
|
|
374
|
+
}).catch((err)=>this.handleError(String(err)));
|
|
375
|
+
}
|
|
376
|
+
onConnectionState() {
|
|
377
|
+
if ("running" !== this.state || !this.pc) return;
|
|
378
|
+
if ("failed" === this.pc.connectionState || "closed" === this.pc.connectionState) this.handleError("peer connection closed");
|
|
379
|
+
}
|
|
380
|
+
onTrack(evt) {
|
|
381
|
+
var _this_conf_onTrack, _this_conf;
|
|
382
|
+
null == (_this_conf_onTrack = (_this_conf = this.conf).onTrack) || _this_conf_onTrack.call(_this_conf, evt);
|
|
383
|
+
}
|
|
384
|
+
onDataChannel(evt) {
|
|
385
|
+
var _this_conf_onDataChannel, _this_conf;
|
|
386
|
+
null == (_this_conf_onDataChannel = (_this_conf = this.conf).onDataChannel) || _this_conf_onDataChannel.call(_this_conf, evt);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
export { MediaMTXWebRTCReader };
|
package/dist/index.cjs
CHANGED
|
@@ -54,6 +54,9 @@ var __webpack_modules__ = {
|
|
|
54
54
|
"@/components/Ring": function(module) {
|
|
55
55
|
module.exports = require("./components/Ring.cjs");
|
|
56
56
|
},
|
|
57
|
+
"@/components/RtspPlayer": function(module) {
|
|
58
|
+
module.exports = require("./components/RtspPlayer.cjs");
|
|
59
|
+
},
|
|
57
60
|
"@/components/Scroll": function(module) {
|
|
58
61
|
module.exports = require("./components/Scroll.cjs");
|
|
59
62
|
},
|
|
@@ -245,82 +248,88 @@ var __webpack_exports__ = {};
|
|
|
245
248
|
return _components_Ring__WEBPACK_IMPORTED_MODULE_17__[key];
|
|
246
249
|
}).bind(0, __WEBPACK_IMPORT_KEY__);
|
|
247
250
|
__webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
|
|
248
|
-
var
|
|
251
|
+
var _components_RtspPlayer__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__("@/components/RtspPlayer");
|
|
252
|
+
var __WEBPACK_REEXPORT_OBJECT__ = {};
|
|
253
|
+
for(var __WEBPACK_IMPORT_KEY__ in _components_RtspPlayer__WEBPACK_IMPORTED_MODULE_18__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
|
|
254
|
+
return _components_RtspPlayer__WEBPACK_IMPORTED_MODULE_18__[key];
|
|
255
|
+
}).bind(0, __WEBPACK_IMPORT_KEY__);
|
|
256
|
+
__webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
|
|
257
|
+
var _components_Scroll__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__("@/components/Scroll");
|
|
249
258
|
var __WEBPACK_REEXPORT_OBJECT__ = {};
|
|
250
|
-
for(var __WEBPACK_IMPORT_KEY__ in
|
|
251
|
-
return
|
|
259
|
+
for(var __WEBPACK_IMPORT_KEY__ in _components_Scroll__WEBPACK_IMPORTED_MODULE_19__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
|
|
260
|
+
return _components_Scroll__WEBPACK_IMPORTED_MODULE_19__[key];
|
|
252
261
|
}).bind(0, __WEBPACK_IMPORT_KEY__);
|
|
253
262
|
__webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
|
|
254
|
-
var
|
|
263
|
+
var _components_ScrollMask__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__("@/components/ScrollMask");
|
|
255
264
|
var __WEBPACK_REEXPORT_OBJECT__ = {};
|
|
256
|
-
for(var __WEBPACK_IMPORT_KEY__ in
|
|
257
|
-
return
|
|
265
|
+
for(var __WEBPACK_IMPORT_KEY__ in _components_ScrollMask__WEBPACK_IMPORTED_MODULE_20__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
|
|
266
|
+
return _components_ScrollMask__WEBPACK_IMPORTED_MODULE_20__[key];
|
|
258
267
|
}).bind(0, __WEBPACK_IMPORT_KEY__);
|
|
259
268
|
__webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
|
|
260
|
-
var
|
|
269
|
+
var _components_SectionRing__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__("@/components/SectionRing");
|
|
261
270
|
var __WEBPACK_REEXPORT_OBJECT__ = {};
|
|
262
|
-
for(var __WEBPACK_IMPORT_KEY__ in
|
|
263
|
-
return
|
|
271
|
+
for(var __WEBPACK_IMPORT_KEY__ in _components_SectionRing__WEBPACK_IMPORTED_MODULE_21__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
|
|
272
|
+
return _components_SectionRing__WEBPACK_IMPORTED_MODULE_21__[key];
|
|
264
273
|
}).bind(0, __WEBPACK_IMPORT_KEY__);
|
|
265
274
|
__webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
|
|
266
|
-
var
|
|
275
|
+
var _components_Skeleton__WEBPACK_IMPORTED_MODULE_22__ = __webpack_require__("@/components/Skeleton");
|
|
267
276
|
var __WEBPACK_REEXPORT_OBJECT__ = {};
|
|
268
|
-
for(var __WEBPACK_IMPORT_KEY__ in
|
|
269
|
-
return
|
|
277
|
+
for(var __WEBPACK_IMPORT_KEY__ in _components_Skeleton__WEBPACK_IMPORTED_MODULE_22__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
|
|
278
|
+
return _components_Skeleton__WEBPACK_IMPORTED_MODULE_22__[key];
|
|
270
279
|
}).bind(0, __WEBPACK_IMPORT_KEY__);
|
|
271
280
|
__webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
|
|
272
|
-
var
|
|
281
|
+
var _components_Title__WEBPACK_IMPORTED_MODULE_23__ = __webpack_require__("@/components/Title");
|
|
273
282
|
var __WEBPACK_REEXPORT_OBJECT__ = {};
|
|
274
|
-
for(var __WEBPACK_IMPORT_KEY__ in
|
|
275
|
-
return
|
|
283
|
+
for(var __WEBPACK_IMPORT_KEY__ in _components_Title__WEBPACK_IMPORTED_MODULE_23__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
|
|
284
|
+
return _components_Title__WEBPACK_IMPORTED_MODULE_23__[key];
|
|
276
285
|
}).bind(0, __WEBPACK_IMPORT_KEY__);
|
|
277
286
|
__webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
|
|
278
|
-
var
|
|
287
|
+
var _components_TransitionBox__WEBPACK_IMPORTED_MODULE_24__ = __webpack_require__("@/components/TransitionBox");
|
|
279
288
|
var __WEBPACK_REEXPORT_OBJECT__ = {};
|
|
280
|
-
for(var __WEBPACK_IMPORT_KEY__ in
|
|
281
|
-
return
|
|
289
|
+
for(var __WEBPACK_IMPORT_KEY__ in _components_TransitionBox__WEBPACK_IMPORTED_MODULE_24__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
|
|
290
|
+
return _components_TransitionBox__WEBPACK_IMPORTED_MODULE_24__[key];
|
|
282
291
|
}).bind(0, __WEBPACK_IMPORT_KEY__);
|
|
283
292
|
__webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
|
|
284
|
-
var
|
|
293
|
+
var _components_TransitionNum__WEBPACK_IMPORTED_MODULE_25__ = __webpack_require__("@/components/TransitionNum");
|
|
285
294
|
var __WEBPACK_REEXPORT_OBJECT__ = {};
|
|
286
|
-
for(var __WEBPACK_IMPORT_KEY__ in
|
|
287
|
-
return
|
|
295
|
+
for(var __WEBPACK_IMPORT_KEY__ in _components_TransitionNum__WEBPACK_IMPORTED_MODULE_25__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
|
|
296
|
+
return _components_TransitionNum__WEBPACK_IMPORTED_MODULE_25__[key];
|
|
288
297
|
}).bind(0, __WEBPACK_IMPORT_KEY__);
|
|
289
298
|
__webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
|
|
290
|
-
var
|
|
299
|
+
var _components_Trapezium__WEBPACK_IMPORTED_MODULE_26__ = __webpack_require__("@/components/Trapezium");
|
|
291
300
|
var __WEBPACK_REEXPORT_OBJECT__ = {};
|
|
292
|
-
for(var __WEBPACK_IMPORT_KEY__ in
|
|
293
|
-
return
|
|
301
|
+
for(var __WEBPACK_IMPORT_KEY__ in _components_Trapezium__WEBPACK_IMPORTED_MODULE_26__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
|
|
302
|
+
return _components_Trapezium__WEBPACK_IMPORTED_MODULE_26__[key];
|
|
294
303
|
}).bind(0, __WEBPACK_IMPORT_KEY__);
|
|
295
304
|
__webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
|
|
296
|
-
var
|
|
305
|
+
var _components_Unify__WEBPACK_IMPORTED_MODULE_27__ = __webpack_require__("@/components/Unify");
|
|
297
306
|
var __WEBPACK_REEXPORT_OBJECT__ = {};
|
|
298
|
-
for(var __WEBPACK_IMPORT_KEY__ in
|
|
299
|
-
return
|
|
307
|
+
for(var __WEBPACK_IMPORT_KEY__ in _components_Unify__WEBPACK_IMPORTED_MODULE_27__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
|
|
308
|
+
return _components_Unify__WEBPACK_IMPORTED_MODULE_27__[key];
|
|
300
309
|
}).bind(0, __WEBPACK_IMPORT_KEY__);
|
|
301
310
|
__webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
|
|
302
|
-
var
|
|
311
|
+
var _components_WriteExcel__WEBPACK_IMPORTED_MODULE_28__ = __webpack_require__("@/components/WriteExcel");
|
|
303
312
|
var __WEBPACK_REEXPORT_OBJECT__ = {};
|
|
304
|
-
for(var __WEBPACK_IMPORT_KEY__ in
|
|
305
|
-
return
|
|
313
|
+
for(var __WEBPACK_IMPORT_KEY__ in _components_WriteExcel__WEBPACK_IMPORTED_MODULE_28__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
|
|
314
|
+
return _components_WriteExcel__WEBPACK_IMPORTED_MODULE_28__[key];
|
|
306
315
|
}).bind(0, __WEBPACK_IMPORT_KEY__);
|
|
307
316
|
__webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
|
|
308
|
-
var
|
|
317
|
+
var _components_WriteSheet__WEBPACK_IMPORTED_MODULE_29__ = __webpack_require__("@/components/WriteSheet");
|
|
309
318
|
var __WEBPACK_REEXPORT_OBJECT__ = {};
|
|
310
|
-
for(var __WEBPACK_IMPORT_KEY__ in
|
|
311
|
-
return
|
|
319
|
+
for(var __WEBPACK_IMPORT_KEY__ in _components_WriteSheet__WEBPACK_IMPORTED_MODULE_29__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
|
|
320
|
+
return _components_WriteSheet__WEBPACK_IMPORTED_MODULE_29__[key];
|
|
312
321
|
}).bind(0, __WEBPACK_IMPORT_KEY__);
|
|
313
322
|
__webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
|
|
314
|
-
var
|
|
323
|
+
var _utils_getReactVersion__WEBPACK_IMPORTED_MODULE_30__ = __webpack_require__("@/utils/getReactVersion");
|
|
315
324
|
var __WEBPACK_REEXPORT_OBJECT__ = {};
|
|
316
|
-
for(var __WEBPACK_IMPORT_KEY__ in
|
|
317
|
-
return
|
|
325
|
+
for(var __WEBPACK_IMPORT_KEY__ in _utils_getReactVersion__WEBPACK_IMPORTED_MODULE_30__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
|
|
326
|
+
return _utils_getReactVersion__WEBPACK_IMPORTED_MODULE_30__[key];
|
|
318
327
|
}).bind(0, __WEBPACK_IMPORT_KEY__);
|
|
319
328
|
__webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
|
|
320
|
-
var
|
|
329
|
+
var _utils_index__WEBPACK_IMPORTED_MODULE_31__ = __webpack_require__("@/utils/index");
|
|
321
330
|
var __WEBPACK_REEXPORT_OBJECT__ = {};
|
|
322
|
-
for(var __WEBPACK_IMPORT_KEY__ in
|
|
323
|
-
return
|
|
331
|
+
for(var __WEBPACK_IMPORT_KEY__ in _utils_index__WEBPACK_IMPORTED_MODULE_31__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
|
|
332
|
+
return _utils_index__WEBPACK_IMPORTED_MODULE_31__[key];
|
|
324
333
|
}).bind(0, __WEBPACK_IMPORT_KEY__);
|
|
325
334
|
__webpack_require__.d(__webpack_exports__, __WEBPACK_REEXPORT_OBJECT__);
|
|
326
335
|
})();
|
package/dist/index.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export * from "./components/LoopSwiper";
|
|
|
16
16
|
export * from "./components/ReadExcel";
|
|
17
17
|
export * from "./components/ReadSheet";
|
|
18
18
|
export * from "./components/Ring";
|
|
19
|
+
export * from "./components/RtspPlayer";
|
|
19
20
|
export * from "./components/Scroll";
|
|
20
21
|
export * from "./components/ScrollMask";
|
|
21
22
|
export * from "./components/SectionRing";
|
package/dist/index.js
CHANGED
|
@@ -16,6 +16,7 @@ export * from "./components/LoopSwiper.js";
|
|
|
16
16
|
export * from "./components/ReadExcel.js";
|
|
17
17
|
export * from "./components/ReadSheet.js";
|
|
18
18
|
export * from "./components/Ring.js";
|
|
19
|
+
export * from "./components/RtspPlayer.js";
|
|
19
20
|
export * from "./components/Scroll.js";
|
|
20
21
|
export * from "./components/ScrollMask.js";
|
|
21
22
|
export * from "./components/SectionRing.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deepsea-components",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.18.0",
|
|
4
4
|
"description": "格数科技自用组件库",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
"hls.js": "^1.6.13",
|
|
42
42
|
"smooth-scrollbar": "^8.8.4",
|
|
43
43
|
"stable-hash": "^0.0.6",
|
|
44
|
-
"
|
|
45
|
-
"
|
|
44
|
+
"deepsea-tools": "5.47.10",
|
|
45
|
+
"soda-hooks": "6.16.23"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"typescript": ">=5.8.3"
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { ComponentProps, FC, useEffect, useImperativeHandle, useRef } from "react"
|
|
4
|
+
|
|
5
|
+
import { MediaMTXWebRTCReader } from "@/components/rtspPlayer/reader"
|
|
6
|
+
|
|
7
|
+
export interface RtspPlayerProps extends Omit<ComponentProps<"video">, "src" | "children"> {
|
|
8
|
+
src: string
|
|
9
|
+
user?: string
|
|
10
|
+
pass?: string
|
|
11
|
+
token?: string
|
|
12
|
+
onReaderError?: (error: string) => void
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function getWhepUrl(src: string) {
|
|
16
|
+
const url = new URL(src, window.location.href)
|
|
17
|
+
|
|
18
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
19
|
+
throw new Error("RtspPlayer expects a MediaMTX HTTP(S) playback URL, not an RTSP URL")
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const pathname = url.pathname.replace(/\/+$/, "")
|
|
23
|
+
url.pathname = pathname.endsWith("/whep") ? pathname : `${pathname}/whep`
|
|
24
|
+
url.hash = ""
|
|
25
|
+
|
|
26
|
+
return url.toString()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function stopStream(stream: MediaStream) {
|
|
30
|
+
stream.getTracks().forEach(track => track.stop())
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const RtspPlayer: FC<RtspPlayerProps> = ({ ref, src, user, pass, token, onReaderError, ...rest }) => {
|
|
34
|
+
const video = useRef<HTMLVideoElement>(null)
|
|
35
|
+
const onReaderErrorRef = useRef(onReaderError)
|
|
36
|
+
|
|
37
|
+
useImperativeHandle(ref, () => video.current!, [])
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
onReaderErrorRef.current = onReaderError
|
|
41
|
+
}, [onReaderError])
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
const { current: player } = video
|
|
45
|
+
if (!player || !src) return
|
|
46
|
+
|
|
47
|
+
if (typeof RTCPeerConnection === "undefined" || typeof MediaStream === "undefined") {
|
|
48
|
+
onReaderErrorRef.current?.("RtspPlayer requires WebRTC support in the current browser")
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let whepUrl: string
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
whepUrl = getWhepUrl(src)
|
|
56
|
+
} catch (error) {
|
|
57
|
+
onReaderErrorRef.current?.(error instanceof Error ? error.message : String(error))
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let closed = false
|
|
62
|
+
const fallbackStream = new MediaStream()
|
|
63
|
+
const reader = new MediaMTXWebRTCReader({
|
|
64
|
+
url: whepUrl,
|
|
65
|
+
user,
|
|
66
|
+
pass,
|
|
67
|
+
token,
|
|
68
|
+
onError: error => onReaderErrorRef.current?.(error),
|
|
69
|
+
onTrack: evt => {
|
|
70
|
+
if (closed) return
|
|
71
|
+
|
|
72
|
+
const stream = evt.streams[0] ?? fallbackStream
|
|
73
|
+
|
|
74
|
+
if (evt.streams.length === 0 && !fallbackStream.getTracks().includes(evt.track)) fallbackStream.addTrack(evt.track)
|
|
75
|
+
if (player.srcObject !== stream) player.srcObject = stream
|
|
76
|
+
},
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
return () => {
|
|
80
|
+
closed = true
|
|
81
|
+
reader.close()
|
|
82
|
+
|
|
83
|
+
const { srcObject } = player
|
|
84
|
+
if (srcObject instanceof MediaStream) stopStream(srcObject)
|
|
85
|
+
if (srcObject !== fallbackStream) stopStream(fallbackStream)
|
|
86
|
+
|
|
87
|
+
player.srcObject = null
|
|
88
|
+
player.load()
|
|
89
|
+
}
|
|
90
|
+
}, [src, user, pass, token])
|
|
91
|
+
|
|
92
|
+
return <video ref={video} {...rest} />
|
|
93
|
+
}
|