deepsea-components 5.17.36 → 5.18.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.
@@ -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 _components_Scroll__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__("@/components/Scroll");
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 _components_Scroll__WEBPACK_IMPORTED_MODULE_18__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
251
- return _components_Scroll__WEBPACK_IMPORTED_MODULE_18__[key];
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 _components_ScrollMask__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__("@/components/ScrollMask");
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 _components_ScrollMask__WEBPACK_IMPORTED_MODULE_19__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
257
- return _components_ScrollMask__WEBPACK_IMPORTED_MODULE_19__[key];
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 _components_SectionRing__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__("@/components/SectionRing");
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 _components_SectionRing__WEBPACK_IMPORTED_MODULE_20__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
263
- return _components_SectionRing__WEBPACK_IMPORTED_MODULE_20__[key];
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 _components_Skeleton__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__("@/components/Skeleton");
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 _components_Skeleton__WEBPACK_IMPORTED_MODULE_21__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
269
- return _components_Skeleton__WEBPACK_IMPORTED_MODULE_21__[key];
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 _components_Title__WEBPACK_IMPORTED_MODULE_22__ = __webpack_require__("@/components/Title");
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 _components_Title__WEBPACK_IMPORTED_MODULE_22__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
275
- return _components_Title__WEBPACK_IMPORTED_MODULE_22__[key];
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 _components_TransitionBox__WEBPACK_IMPORTED_MODULE_23__ = __webpack_require__("@/components/TransitionBox");
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 _components_TransitionBox__WEBPACK_IMPORTED_MODULE_23__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
281
- return _components_TransitionBox__WEBPACK_IMPORTED_MODULE_23__[key];
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 _components_TransitionNum__WEBPACK_IMPORTED_MODULE_24__ = __webpack_require__("@/components/TransitionNum");
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 _components_TransitionNum__WEBPACK_IMPORTED_MODULE_24__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
287
- return _components_TransitionNum__WEBPACK_IMPORTED_MODULE_24__[key];
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 _components_Trapezium__WEBPACK_IMPORTED_MODULE_25__ = __webpack_require__("@/components/Trapezium");
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 _components_Trapezium__WEBPACK_IMPORTED_MODULE_25__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
293
- return _components_Trapezium__WEBPACK_IMPORTED_MODULE_25__[key];
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 _components_Unify__WEBPACK_IMPORTED_MODULE_26__ = __webpack_require__("@/components/Unify");
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 _components_Unify__WEBPACK_IMPORTED_MODULE_26__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
299
- return _components_Unify__WEBPACK_IMPORTED_MODULE_26__[key];
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 _components_WriteExcel__WEBPACK_IMPORTED_MODULE_27__ = __webpack_require__("@/components/WriteExcel");
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 _components_WriteExcel__WEBPACK_IMPORTED_MODULE_27__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
305
- return _components_WriteExcel__WEBPACK_IMPORTED_MODULE_27__[key];
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 _components_WriteSheet__WEBPACK_IMPORTED_MODULE_28__ = __webpack_require__("@/components/WriteSheet");
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 _components_WriteSheet__WEBPACK_IMPORTED_MODULE_28__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
311
- return _components_WriteSheet__WEBPACK_IMPORTED_MODULE_28__[key];
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 _utils_getReactVersion__WEBPACK_IMPORTED_MODULE_29__ = __webpack_require__("@/utils/getReactVersion");
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 _utils_getReactVersion__WEBPACK_IMPORTED_MODULE_29__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
317
- return _utils_getReactVersion__WEBPACK_IMPORTED_MODULE_29__[key];
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 _utils_index__WEBPACK_IMPORTED_MODULE_30__ = __webpack_require__("@/utils/index");
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 _utils_index__WEBPACK_IMPORTED_MODULE_30__)if ("default" !== __WEBPACK_IMPORT_KEY__) __WEBPACK_REEXPORT_OBJECT__[__WEBPACK_IMPORT_KEY__] = (function(key) {
323
- return _utils_index__WEBPACK_IMPORTED_MODULE_30__[key];
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.17.36",
3
+ "version": "5.18.1",
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
- "soda-hooks": "6.16.23",
45
- "deepsea-tools": "5.47.10"
44
+ "soda-hooks": "6.16.24",
45
+ "deepsea-tools": "5.47.11"
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
+ }