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,113 @@
1
+ "use client"
2
+ "use strict";
3
+ var __webpack_require__ = {};
4
+ (()=>{
5
+ __webpack_require__.d = (exports1, definition)=>{
6
+ for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
7
+ enumerable: true,
8
+ get: definition[key]
9
+ });
10
+ };
11
+ })();
12
+ (()=>{
13
+ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
14
+ })();
15
+ (()=>{
16
+ __webpack_require__.r = (exports1)=>{
17
+ if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
18
+ value: 'Module'
19
+ });
20
+ Object.defineProperty(exports1, '__esModule', {
21
+ value: true
22
+ });
23
+ };
24
+ })();
25
+ var __webpack_exports__ = {};
26
+ __webpack_require__.r(__webpack_exports__);
27
+ __webpack_require__.d(__webpack_exports__, {
28
+ RtspPlayer: ()=>RtspPlayer
29
+ });
30
+ const jsx_runtime_namespaceObject = require("react/jsx-runtime");
31
+ const external_react_namespaceObject = require("react");
32
+ const reader_cjs_namespaceObject = require("./rtspPlayer/reader.cjs");
33
+ function getWhepUrl(src) {
34
+ const url = new URL(src, window.location.href);
35
+ if ("http:" !== url.protocol && "https:" !== url.protocol) throw new Error("RtspPlayer expects a MediaMTX HTTP(S) playback URL, not an RTSP URL");
36
+ const pathname = url.pathname.replace(/\/+$/, "");
37
+ url.pathname = pathname.endsWith("/whep") ? pathname : `${pathname}/whep`;
38
+ url.hash = "";
39
+ return url.toString();
40
+ }
41
+ function stopStream(stream) {
42
+ stream.getTracks().forEach((track)=>track.stop());
43
+ }
44
+ const RtspPlayer = ({ ref, src, user, pass, token, onReaderError, ...rest })=>{
45
+ const video = (0, external_react_namespaceObject.useRef)(null);
46
+ const onReaderErrorRef = (0, external_react_namespaceObject.useRef)(onReaderError);
47
+ (0, external_react_namespaceObject.useImperativeHandle)(ref, ()=>video.current, []);
48
+ (0, external_react_namespaceObject.useEffect)(()=>{
49
+ onReaderErrorRef.current = onReaderError;
50
+ }, [
51
+ onReaderError
52
+ ]);
53
+ (0, external_react_namespaceObject.useEffect)(()=>{
54
+ const { current: player } = video;
55
+ if (!player || !src) return;
56
+ if ("undefined" == typeof RTCPeerConnection || "undefined" == typeof MediaStream) {
57
+ var _onReaderErrorRef_current;
58
+ null == (_onReaderErrorRef_current = onReaderErrorRef.current) || _onReaderErrorRef_current.call(onReaderErrorRef, "RtspPlayer requires WebRTC support in the current browser");
59
+ return;
60
+ }
61
+ let whepUrl;
62
+ try {
63
+ whepUrl = getWhepUrl(src);
64
+ } catch (error) {
65
+ var _onReaderErrorRef_current1;
66
+ null == (_onReaderErrorRef_current1 = onReaderErrorRef.current) || _onReaderErrorRef_current1.call(onReaderErrorRef, error instanceof Error ? error.message : String(error));
67
+ return;
68
+ }
69
+ let closed = false;
70
+ const fallbackStream = new MediaStream();
71
+ const reader = new reader_cjs_namespaceObject.MediaMTXWebRTCReader({
72
+ url: whepUrl,
73
+ user,
74
+ pass,
75
+ token,
76
+ onError: (error)=>{
77
+ var _onReaderErrorRef_current;
78
+ return null == (_onReaderErrorRef_current = onReaderErrorRef.current) ? void 0 : _onReaderErrorRef_current.call(onReaderErrorRef, error);
79
+ },
80
+ onTrack: (evt)=>{
81
+ if (closed) return;
82
+ const stream = evt.streams[0] ?? fallbackStream;
83
+ if (0 === evt.streams.length && !fallbackStream.getTracks().includes(evt.track)) fallbackStream.addTrack(evt.track);
84
+ if (player.srcObject !== stream) player.srcObject = stream;
85
+ }
86
+ });
87
+ return ()=>{
88
+ closed = true;
89
+ reader.close();
90
+ const { srcObject } = player;
91
+ if (srcObject instanceof MediaStream) stopStream(srcObject);
92
+ if (srcObject !== fallbackStream) stopStream(fallbackStream);
93
+ player.srcObject = null;
94
+ player.load();
95
+ };
96
+ }, [
97
+ src,
98
+ user,
99
+ pass,
100
+ token
101
+ ]);
102
+ return /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)("video", {
103
+ ref: video,
104
+ ...rest
105
+ });
106
+ };
107
+ exports.RtspPlayer = __webpack_exports__.RtspPlayer;
108
+ for(var __webpack_i__ in __webpack_exports__)if (-1 === [
109
+ "RtspPlayer"
110
+ ].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
111
+ Object.defineProperty(exports, '__esModule', {
112
+ value: true
113
+ });
@@ -0,0 +1,9 @@
1
+ import { ComponentProps, FC } from "react";
2
+ export interface RtspPlayerProps extends Omit<ComponentProps<"video">, "src" | "children"> {
3
+ src: string;
4
+ user?: string;
5
+ pass?: string;
6
+ token?: string;
7
+ onReaderError?: (error: string) => void;
8
+ }
9
+ export declare const RtspPlayer: FC<RtspPlayerProps>;
@@ -0,0 +1,79 @@
1
+ "use client"
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { useEffect, useImperativeHandle, useRef } from "react";
4
+ import { MediaMTXWebRTCReader } from "./rtspPlayer/reader.js";
5
+ function getWhepUrl(src) {
6
+ const url = new URL(src, window.location.href);
7
+ if ("http:" !== url.protocol && "https:" !== url.protocol) throw new Error("RtspPlayer expects a MediaMTX HTTP(S) playback URL, not an RTSP URL");
8
+ const pathname = url.pathname.replace(/\/+$/, "");
9
+ url.pathname = pathname.endsWith("/whep") ? pathname : `${pathname}/whep`;
10
+ url.hash = "";
11
+ return url.toString();
12
+ }
13
+ function stopStream(stream) {
14
+ stream.getTracks().forEach((track)=>track.stop());
15
+ }
16
+ const RtspPlayer = ({ ref, src, user, pass, token, onReaderError, ...rest })=>{
17
+ const video = useRef(null);
18
+ const onReaderErrorRef = useRef(onReaderError);
19
+ useImperativeHandle(ref, ()=>video.current, []);
20
+ useEffect(()=>{
21
+ onReaderErrorRef.current = onReaderError;
22
+ }, [
23
+ onReaderError
24
+ ]);
25
+ useEffect(()=>{
26
+ const { current: player } = video;
27
+ if (!player || !src) return;
28
+ if ("undefined" == typeof RTCPeerConnection || "undefined" == typeof MediaStream) {
29
+ var _onReaderErrorRef_current;
30
+ null == (_onReaderErrorRef_current = onReaderErrorRef.current) || _onReaderErrorRef_current.call(onReaderErrorRef, "RtspPlayer requires WebRTC support in the current browser");
31
+ return;
32
+ }
33
+ let whepUrl;
34
+ try {
35
+ whepUrl = getWhepUrl(src);
36
+ } catch (error) {
37
+ var _onReaderErrorRef_current1;
38
+ null == (_onReaderErrorRef_current1 = onReaderErrorRef.current) || _onReaderErrorRef_current1.call(onReaderErrorRef, error instanceof Error ? error.message : String(error));
39
+ return;
40
+ }
41
+ let closed = false;
42
+ const fallbackStream = new MediaStream();
43
+ const reader = new MediaMTXWebRTCReader({
44
+ url: whepUrl,
45
+ user,
46
+ pass,
47
+ token,
48
+ onError: (error)=>{
49
+ var _onReaderErrorRef_current;
50
+ return null == (_onReaderErrorRef_current = onReaderErrorRef.current) ? void 0 : _onReaderErrorRef_current.call(onReaderErrorRef, error);
51
+ },
52
+ onTrack: (evt)=>{
53
+ if (closed) return;
54
+ const stream = evt.streams[0] ?? fallbackStream;
55
+ if (0 === evt.streams.length && !fallbackStream.getTracks().includes(evt.track)) fallbackStream.addTrack(evt.track);
56
+ if (player.srcObject !== stream) player.srcObject = stream;
57
+ }
58
+ });
59
+ return ()=>{
60
+ closed = true;
61
+ reader.close();
62
+ const { srcObject } = player;
63
+ if (srcObject instanceof MediaStream) stopStream(srcObject);
64
+ if (srcObject !== fallbackStream) stopStream(fallbackStream);
65
+ player.srcObject = null;
66
+ player.load();
67
+ };
68
+ }, [
69
+ src,
70
+ user,
71
+ pass,
72
+ token
73
+ ]);
74
+ return /*#__PURE__*/ jsx("video", {
75
+ ref: video,
76
+ ...rest
77
+ });
78
+ };
79
+ export { RtspPlayer };
@@ -0,0 +1,423 @@
1
+ "use strict";
2
+ var __webpack_require__ = {};
3
+ (()=>{
4
+ __webpack_require__.d = (exports1, definition)=>{
5
+ for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
6
+ enumerable: true,
7
+ get: definition[key]
8
+ });
9
+ };
10
+ })();
11
+ (()=>{
12
+ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
13
+ })();
14
+ (()=>{
15
+ __webpack_require__.r = (exports1)=>{
16
+ if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
17
+ value: 'Module'
18
+ });
19
+ Object.defineProperty(exports1, '__esModule', {
20
+ value: true
21
+ });
22
+ };
23
+ })();
24
+ var __webpack_exports__ = {};
25
+ __webpack_require__.r(__webpack_exports__);
26
+ __webpack_require__.d(__webpack_exports__, {
27
+ MediaMTXWebRTCReader: ()=>MediaMTXWebRTCReader
28
+ });
29
+ class MediaMTXWebRTCReader {
30
+ retryPause = 2000;
31
+ conf;
32
+ state = "getting_codecs";
33
+ restartTimeout = null;
34
+ pc = null;
35
+ offerData = null;
36
+ sessionUrl = null;
37
+ queuedCandidates = [];
38
+ nonAdvertisedCodecs = [];
39
+ constructor(conf){
40
+ this.conf = conf;
41
+ this.getNonAdvertisedCodecs();
42
+ }
43
+ close() {
44
+ this.state = "closed";
45
+ if (this.pc) {
46
+ this.pc.close();
47
+ this.pc = null;
48
+ }
49
+ if (null !== this.restartTimeout) {
50
+ clearTimeout(this.restartTimeout);
51
+ this.restartTimeout = null;
52
+ }
53
+ }
54
+ static supportsNonAdvertisedCodec(codec, fmtp) {
55
+ return new Promise((resolve)=>{
56
+ const pc = new RTCPeerConnection({
57
+ iceServers: []
58
+ });
59
+ const mediaType = "audio";
60
+ let payloadType = "";
61
+ pc.addTransceiver(mediaType, {
62
+ direction: "recvonly"
63
+ });
64
+ pc.createOffer().then((offer)=>{
65
+ if (void 0 === offer.sdp) throw new Error("SDP not present");
66
+ if (offer.sdp.includes(` ${codec}`)) throw new Error("already present");
67
+ const sections = offer.sdp.split(`m=${mediaType}`);
68
+ const payloadTypes = sections.slice(1).map((section)=>section.split("\r\n")[0].split(" ").slice(3)).reduce((prev, cur)=>[
69
+ ...prev,
70
+ ...cur
71
+ ], []);
72
+ payloadType = MediaMTXWebRTCReader.reservePayloadType(payloadTypes);
73
+ const lines = sections[1].split("\r\n");
74
+ lines[0] += ` ${payloadType}`;
75
+ lines.splice(lines.length - 1, 0, `a=rtpmap:${payloadType} ${codec}`);
76
+ if (void 0 !== fmtp) lines.splice(lines.length - 1, 0, `a=fmtp:${payloadType} ${fmtp}`);
77
+ sections[1] = lines.join("\r\n");
78
+ offer.sdp = sections.join(`m=${mediaType}`);
79
+ return pc.setLocalDescription(offer);
80
+ }).then(()=>pc.setRemoteDescription(new RTCSessionDescription({
81
+ type: "answer",
82
+ 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` : "")
83
+ }))).then(()=>resolve(true)).catch(()=>resolve(false)).finally(()=>pc.close());
84
+ });
85
+ }
86
+ static unquoteCredential(value) {
87
+ return JSON.parse(`"${value}"`);
88
+ }
89
+ static linkToIceServers(links) {
90
+ if (null === links) return [];
91
+ return links.split(", ").map((link)=>{
92
+ const match = link.match(/^<(.+?)>; rel="ice-server"(; username="(.*?)"; credential="(.*?)"; credential-type="password")?/i);
93
+ if (!match) return null;
94
+ const ret = {
95
+ urls: [
96
+ match[1]
97
+ ]
98
+ };
99
+ if (void 0 !== match[3] && void 0 !== match[4]) {
100
+ ret.username = MediaMTXWebRTCReader.unquoteCredential(match[3]);
101
+ ret.credential = MediaMTXWebRTCReader.unquoteCredential(match[4]);
102
+ ret.credentialType = "password";
103
+ }
104
+ return ret;
105
+ }).filter((server)=>null !== server);
106
+ }
107
+ static parseOffer(sdp) {
108
+ const ret = {
109
+ iceUfrag: "",
110
+ icePwd: "",
111
+ medias: []
112
+ };
113
+ for (const line of sdp.split("\r\n"))if (line.startsWith("m=")) ret.medias.push(line.slice(2));
114
+ else if ("" === ret.iceUfrag && line.startsWith("a=ice-ufrag:")) ret.iceUfrag = line.slice(12);
115
+ else if ("" === ret.icePwd && line.startsWith("a=ice-pwd:")) ret.icePwd = line.slice(10);
116
+ return ret;
117
+ }
118
+ static reservePayloadType(payloadTypes) {
119
+ for(let i = 30; i <= 127; i++)if ((i <= 63 || i >= 96) && !payloadTypes.includes(i.toString())) {
120
+ const payloadType = i.toString();
121
+ payloadTypes.push(payloadType);
122
+ return payloadType;
123
+ }
124
+ throw new Error("unable to find a free payload type");
125
+ }
126
+ static enableStereoPcmau(payloadTypes, section) {
127
+ const lines = section.split("\r\n");
128
+ let payloadType = MediaMTXWebRTCReader.reservePayloadType(payloadTypes);
129
+ lines[0] += ` ${payloadType}`;
130
+ lines.splice(lines.length - 1, 0, `a=rtpmap:${payloadType} PCMU/8000/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} PCMA/8000/2`);
135
+ lines.splice(lines.length - 1, 0, `a=rtcp-fb:${payloadType} transport-cc`);
136
+ return lines.join("\r\n");
137
+ }
138
+ static enableMultichannelOpus(payloadTypes, section) {
139
+ const lines = section.split("\r\n");
140
+ let payloadType = MediaMTXWebRTCReader.reservePayloadType(payloadTypes);
141
+ lines[0] += ` ${payloadType}`;
142
+ lines.splice(lines.length - 1, 0, `a=rtpmap:${payloadType} multiopus/48000/3`);
143
+ lines.splice(lines.length - 1, 0, `a=fmtp:${payloadType} channel_mapping=0,2,1;num_streams=2;coupled_streams=1`);
144
+ lines.splice(lines.length - 1, 0, `a=rtcp-fb:${payloadType} transport-cc`);
145
+ payloadType = MediaMTXWebRTCReader.reservePayloadType(payloadTypes);
146
+ lines[0] += ` ${payloadType}`;
147
+ lines.splice(lines.length - 1, 0, `a=rtpmap:${payloadType} multiopus/48000/4`);
148
+ lines.splice(lines.length - 1, 0, `a=fmtp:${payloadType} channel_mapping=0,1,2,3;num_streams=2;coupled_streams=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} multiopus/48000/5`);
153
+ lines.splice(lines.length - 1, 0, `a=fmtp:${payloadType} channel_mapping=0,4,1,2,3;num_streams=3;coupled_streams=2`);
154
+ lines.splice(lines.length - 1, 0, `a=rtcp-fb:${payloadType} transport-cc`);
155
+ payloadType = MediaMTXWebRTCReader.reservePayloadType(payloadTypes);
156
+ lines[0] += ` ${payloadType}`;
157
+ lines.splice(lines.length - 1, 0, `a=rtpmap:${payloadType} multiopus/48000/6`);
158
+ lines.splice(lines.length - 1, 0, `a=fmtp:${payloadType} channel_mapping=0,4,1,2,3,5;num_streams=4;coupled_streams=2`);
159
+ lines.splice(lines.length - 1, 0, `a=rtcp-fb:${payloadType} transport-cc`);
160
+ payloadType = MediaMTXWebRTCReader.reservePayloadType(payloadTypes);
161
+ lines[0] += ` ${payloadType}`;
162
+ lines.splice(lines.length - 1, 0, `a=rtpmap:${payloadType} multiopus/48000/7`);
163
+ lines.splice(lines.length - 1, 0, `a=fmtp:${payloadType} channel_mapping=0,4,1,2,3,5,6;num_streams=4;coupled_streams=4`);
164
+ lines.splice(lines.length - 1, 0, `a=rtcp-fb:${payloadType} transport-cc`);
165
+ payloadType = MediaMTXWebRTCReader.reservePayloadType(payloadTypes);
166
+ lines[0] += ` ${payloadType}`;
167
+ lines.splice(lines.length - 1, 0, `a=rtpmap:${payloadType} multiopus/48000/8`);
168
+ 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`);
169
+ lines.splice(lines.length - 1, 0, `a=rtcp-fb:${payloadType} transport-cc`);
170
+ return lines.join("\r\n");
171
+ }
172
+ static enableL16(payloadTypes, section) {
173
+ const lines = section.split("\r\n");
174
+ let payloadType = MediaMTXWebRTCReader.reservePayloadType(payloadTypes);
175
+ lines[0] += ` ${payloadType}`;
176
+ lines.splice(lines.length - 1, 0, `a=rtpmap:${payloadType} L16/8000/2`);
177
+ lines.splice(lines.length - 1, 0, `a=rtcp-fb:${payloadType} transport-cc`);
178
+ payloadType = MediaMTXWebRTCReader.reservePayloadType(payloadTypes);
179
+ lines[0] += ` ${payloadType}`;
180
+ lines.splice(lines.length - 1, 0, `a=rtpmap:${payloadType} L16/16000/2`);
181
+ lines.splice(lines.length - 1, 0, `a=rtcp-fb:${payloadType} transport-cc`);
182
+ payloadType = MediaMTXWebRTCReader.reservePayloadType(payloadTypes);
183
+ lines[0] += ` ${payloadType}`;
184
+ lines.splice(lines.length - 1, 0, `a=rtpmap:${payloadType} L16/48000/2`);
185
+ lines.splice(lines.length - 1, 0, `a=rtcp-fb:${payloadType} transport-cc`);
186
+ return lines.join("\r\n");
187
+ }
188
+ static enableStereoOpus(section) {
189
+ let opusPayloadFormat = "";
190
+ const lines = section.split("\r\n");
191
+ for(let i = 0; i < lines.length; i++)if (lines[i].startsWith("a=rtpmap:") && lines[i].toLowerCase().includes("opus/")) {
192
+ opusPayloadFormat = lines[i].slice(9).split(" ")[0];
193
+ break;
194
+ }
195
+ if ("" === opusPayloadFormat) return section;
196
+ for(let i = 0; i < lines.length; i++)if (lines[i].startsWith(`a=fmtp:${opusPayloadFormat} `)) {
197
+ if (!lines[i].includes("stereo")) lines[i] += ";stereo=1";
198
+ if (!lines[i].includes("sprop-stereo")) lines[i] += ";sprop-stereo=1";
199
+ }
200
+ return lines.join("\r\n");
201
+ }
202
+ static editOffer(sdp, nonAdvertisedCodecs) {
203
+ const sections = sdp.split("m=");
204
+ const payloadTypes = sections.slice(1).map((section)=>section.split("\r\n")[0].split(" ").slice(3)).reduce((prev, cur)=>[
205
+ ...prev,
206
+ ...cur
207
+ ], []);
208
+ for(let i = 1; i < sections.length; i++)if (sections[i].startsWith("audio")) {
209
+ sections[i] = MediaMTXWebRTCReader.enableStereoOpus(sections[i]);
210
+ if (nonAdvertisedCodecs.includes("pcma/8000/2")) sections[i] = MediaMTXWebRTCReader.enableStereoPcmau(payloadTypes, sections[i]);
211
+ if (nonAdvertisedCodecs.includes("multiopus/48000/6")) sections[i] = MediaMTXWebRTCReader.enableMultichannelOpus(payloadTypes, sections[i]);
212
+ if (nonAdvertisedCodecs.includes("L16/48000/2")) sections[i] = MediaMTXWebRTCReader.enableL16(payloadTypes, sections[i]);
213
+ break;
214
+ }
215
+ return sections.join("m=");
216
+ }
217
+ static generateSdpFragment(offerData, candidates) {
218
+ const candidatesByMedia = {};
219
+ for (const candidate of candidates){
220
+ const mid = candidate.sdpMLineIndex;
221
+ if (null !== mid) {
222
+ candidatesByMedia[mid] ??= [];
223
+ candidatesByMedia[mid].push(candidate);
224
+ }
225
+ }
226
+ let frag = `a=ice-ufrag:${offerData.iceUfrag}\r\na=ice-pwd:${offerData.icePwd}\r\n`;
227
+ for(let mid = 0; mid < offerData.medias.length; mid++){
228
+ const mediaCandidates = candidatesByMedia[mid];
229
+ if (void 0 !== mediaCandidates) {
230
+ frag += `m=${offerData.medias[mid]}\r\na=mid:${mid}\r\n`;
231
+ for (const candidate of mediaCandidates)frag += `a=${candidate.candidate}\r\n`;
232
+ }
233
+ }
234
+ return frag;
235
+ }
236
+ handleError(err) {
237
+ if ("running" === this.state) {
238
+ var _this_conf_onError, _this_conf;
239
+ if (this.pc) {
240
+ this.pc.close();
241
+ this.pc = null;
242
+ }
243
+ this.offerData = null;
244
+ if (null !== this.sessionUrl) {
245
+ fetch(this.sessionUrl, {
246
+ method: "DELETE"
247
+ });
248
+ this.sessionUrl = null;
249
+ }
250
+ this.queuedCandidates = [];
251
+ this.state = "restarting";
252
+ this.restartTimeout = window.setTimeout(()=>{
253
+ this.restartTimeout = null;
254
+ this.state = "running";
255
+ this.start();
256
+ }, this.retryPause);
257
+ null == (_this_conf_onError = (_this_conf = this.conf).onError) || _this_conf_onError.call(_this_conf, `${err}, retrying in some seconds`);
258
+ } else if ("getting_codecs" === this.state) {
259
+ var _this_conf_onError1, _this_conf1;
260
+ this.state = "failed";
261
+ null == (_this_conf_onError1 = (_this_conf1 = this.conf).onError) || _this_conf_onError1.call(_this_conf1, err);
262
+ }
263
+ }
264
+ getNonAdvertisedCodecs() {
265
+ Promise.all([
266
+ [
267
+ "pcma/8000/2"
268
+ ],
269
+ [
270
+ "multiopus/48000/6",
271
+ "channel_mapping=0,4,1,2,3,5;num_streams=4;coupled_streams=2"
272
+ ],
273
+ [
274
+ "L16/48000/2"
275
+ ]
276
+ ].map((codec)=>MediaMTXWebRTCReader.supportsNonAdvertisedCodec(codec[0], codec[1]).then((isSupported)=>{
277
+ if (!isSupported) return false;
278
+ return codec[0];
279
+ }))).then((codecs)=>codecs.filter((codec)=>false !== codec)).then((codecs)=>{
280
+ if ("getting_codecs" !== this.state) throw new Error("closed");
281
+ this.nonAdvertisedCodecs = codecs;
282
+ this.state = "running";
283
+ this.start();
284
+ }).catch((err)=>this.handleError(String(err)));
285
+ }
286
+ start() {
287
+ this.requestICEServers().then((iceServers)=>this.setupPeerConnection(iceServers)).then((offer)=>this.sendOffer(offer)).then((answer)=>this.setAnswer(answer)).catch((err)=>this.handleError(String(err)));
288
+ }
289
+ authHeader() {
290
+ if (void 0 !== this.conf.user && "" !== this.conf.user) {
291
+ const credentials = btoa(`${this.conf.user}:${this.conf.pass ?? ""}`);
292
+ return {
293
+ Authorization: `Basic ${credentials}`
294
+ };
295
+ }
296
+ if (void 0 !== this.conf.token && "" !== this.conf.token) return {
297
+ Authorization: `Bearer ${this.conf.token}`
298
+ };
299
+ return {};
300
+ }
301
+ requestICEServers() {
302
+ return fetch(this.conf.url, {
303
+ method: "OPTIONS",
304
+ headers: {
305
+ ...this.authHeader()
306
+ }
307
+ }).then((res)=>MediaMTXWebRTCReader.linkToIceServers(res.headers.get("Link")));
308
+ }
309
+ setupPeerConnection(iceServers) {
310
+ if ("running" !== this.state) throw new Error("closed");
311
+ const pc = new RTCPeerConnection({
312
+ iceServers,
313
+ sdpSemantics: "unified-plan"
314
+ });
315
+ this.pc = pc;
316
+ const direction = "recvonly";
317
+ pc.addTransceiver("video", {
318
+ direction
319
+ });
320
+ pc.addTransceiver("audio", {
321
+ direction
322
+ });
323
+ pc.createDataChannel("");
324
+ pc.onicecandidate = (evt)=>this.onLocalCandidate(evt);
325
+ pc.onconnectionstatechange = ()=>this.onConnectionState();
326
+ pc.ontrack = (evt)=>this.onTrack(evt);
327
+ pc.ondatachannel = (evt)=>this.onDataChannel(evt);
328
+ return pc.createOffer().then((offer)=>{
329
+ if (void 0 === offer.sdp) throw new Error("SDP not present");
330
+ offer.sdp = MediaMTXWebRTCReader.editOffer(offer.sdp, this.nonAdvertisedCodecs);
331
+ this.offerData = MediaMTXWebRTCReader.parseOffer(offer.sdp);
332
+ return pc.setLocalDescription(offer).then(()=>offer.sdp);
333
+ });
334
+ }
335
+ sendOffer(offer) {
336
+ if ("running" !== this.state) throw new Error("closed");
337
+ return fetch(this.conf.url, {
338
+ method: "POST",
339
+ headers: {
340
+ ...this.authHeader(),
341
+ "Content-Type": "application/sdp"
342
+ },
343
+ body: offer
344
+ }).then((res)=>{
345
+ switch(res.status){
346
+ case 201:
347
+ break;
348
+ case 404:
349
+ throw new Error("stream not found");
350
+ case 400:
351
+ return res.json().then((body)=>{
352
+ throw new Error(body.error ?? "bad request");
353
+ });
354
+ default:
355
+ throw new Error(`bad status code ${res.status}`);
356
+ }
357
+ const location = res.headers.get("location");
358
+ if (null === location) throw new Error("session location not present");
359
+ this.sessionUrl = new URL(location, this.conf.url).toString();
360
+ return res.text();
361
+ });
362
+ }
363
+ setAnswer(answer) {
364
+ if ("running" !== this.state) throw new Error("closed");
365
+ if (!this.pc) throw new Error("peer connection not present");
366
+ return this.pc.setRemoteDescription(new RTCSessionDescription({
367
+ type: "answer",
368
+ sdp: answer
369
+ })).then(()=>{
370
+ if ("running" !== this.state) return;
371
+ if (0 !== this.queuedCandidates.length) {
372
+ this.sendLocalCandidates(this.queuedCandidates);
373
+ this.queuedCandidates = [];
374
+ }
375
+ });
376
+ }
377
+ onLocalCandidate(evt) {
378
+ if ("running" !== this.state) return;
379
+ if (null !== evt.candidate) if (null === this.sessionUrl) this.queuedCandidates.push(evt.candidate);
380
+ else this.sendLocalCandidates([
381
+ evt.candidate
382
+ ]);
383
+ }
384
+ sendLocalCandidates(candidates) {
385
+ if (null === this.sessionUrl || null === this.offerData) return;
386
+ fetch(this.sessionUrl, {
387
+ method: "PATCH",
388
+ headers: {
389
+ "Content-Type": "application/trickle-ice-sdpfrag",
390
+ "If-Match": "*"
391
+ },
392
+ body: MediaMTXWebRTCReader.generateSdpFragment(this.offerData, candidates)
393
+ }).then((res)=>{
394
+ switch(res.status){
395
+ case 204:
396
+ break;
397
+ case 404:
398
+ throw new Error("stream not found");
399
+ default:
400
+ throw new Error(`bad status code ${res.status}`);
401
+ }
402
+ }).catch((err)=>this.handleError(String(err)));
403
+ }
404
+ onConnectionState() {
405
+ if ("running" !== this.state || !this.pc) return;
406
+ if ("failed" === this.pc.connectionState || "closed" === this.pc.connectionState) this.handleError("peer connection closed");
407
+ }
408
+ onTrack(evt) {
409
+ var _this_conf_onTrack, _this_conf;
410
+ null == (_this_conf_onTrack = (_this_conf = this.conf).onTrack) || _this_conf_onTrack.call(_this_conf, evt);
411
+ }
412
+ onDataChannel(evt) {
413
+ var _this_conf_onDataChannel, _this_conf;
414
+ null == (_this_conf_onDataChannel = (_this_conf = this.conf).onDataChannel) || _this_conf_onDataChannel.call(_this_conf, evt);
415
+ }
416
+ }
417
+ exports.MediaMTXWebRTCReader = __webpack_exports__.MediaMTXWebRTCReader;
418
+ for(var __webpack_i__ in __webpack_exports__)if (-1 === [
419
+ "MediaMTXWebRTCReader"
420
+ ].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
421
+ Object.defineProperty(exports, '__esModule', {
422
+ value: true
423
+ });
@@ -0,0 +1,46 @@
1
+ export interface MediaMTXWebRTCReaderConf {
2
+ url: string;
3
+ user?: string;
4
+ pass?: string;
5
+ token?: string;
6
+ onError?: (err: string) => void;
7
+ onTrack?: (evt: RTCTrackEvent) => void;
8
+ onDataChannel?: (evt: RTCDataChannelEvent) => void;
9
+ }
10
+ export declare class MediaMTXWebRTCReader {
11
+ private readonly retryPause;
12
+ private readonly conf;
13
+ private state;
14
+ private restartTimeout;
15
+ private pc;
16
+ private offerData;
17
+ private sessionUrl;
18
+ private queuedCandidates;
19
+ private nonAdvertisedCodecs;
20
+ constructor(conf: MediaMTXWebRTCReaderConf);
21
+ close(): void;
22
+ private static supportsNonAdvertisedCodec;
23
+ private static unquoteCredential;
24
+ private static linkToIceServers;
25
+ private static parseOffer;
26
+ private static reservePayloadType;
27
+ private static enableStereoPcmau;
28
+ private static enableMultichannelOpus;
29
+ private static enableL16;
30
+ private static enableStereoOpus;
31
+ private static editOffer;
32
+ private static generateSdpFragment;
33
+ private handleError;
34
+ private getNonAdvertisedCodecs;
35
+ private start;
36
+ private authHeader;
37
+ private requestICEServers;
38
+ private setupPeerConnection;
39
+ private sendOffer;
40
+ private setAnswer;
41
+ private onLocalCandidate;
42
+ private sendLocalCandidates;
43
+ private onConnectionState;
44
+ private onTrack;
45
+ private onDataChannel;
46
+ }