react-mse-player 1.0.5 → 1.1.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/index.esm.js CHANGED
@@ -1,4 +1,4 @@
1
- import React, { useRef, useState, useEffect } from 'react';
1
+ import React, { useState, useRef, useEffect } from 'react';
2
2
 
3
3
  /******************************************************************************
4
4
  Copyright (c) Microsoft Corporation.
@@ -28,13 +28,112 @@ var __assign = function() {
28
28
  return __assign.apply(this, arguments);
29
29
  };
30
30
 
31
+ function __awaiter(thisArg, _arguments, P, generator) {
32
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
33
+ return new (P || (P = Promise))(function (resolve, reject) {
34
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
35
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
36
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
37
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
38
+ });
39
+ }
40
+
41
+ function __generator(thisArg, body) {
42
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
43
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
44
+ function verb(n) { return function (v) { return step([n, v]); }; }
45
+ function step(op) {
46
+ if (f) throw new TypeError("Generator is already executing.");
47
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
48
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
49
+ if (y = 0, t) op = [op[0] & 2, t.value];
50
+ switch (op[0]) {
51
+ case 0: case 1: t = op; break;
52
+ case 4: _.label++; return { value: op[1], done: false };
53
+ case 5: _.label++; y = op[1]; op = [0]; continue;
54
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
55
+ default:
56
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
57
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
58
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
59
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
60
+ if (t[2]) _.ops.pop();
61
+ _.trys.pop(); continue;
62
+ }
63
+ op = body.call(thisArg, _);
64
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
65
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
66
+ }
67
+ }
68
+
31
69
  typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
32
70
  var e = new Error(message);
33
71
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
34
72
  };
35
73
 
74
+ var VideoShell = function (_a) {
75
+ var videoRef = _a.videoRef, _b = _a.width, width = _b === void 0 ? "100%" : _b, _c = _a.height, height = _c === void 0 ? "100%" : _c, _d = _a.controls, controls = _d === void 0 ? false : _d, _e = _a.autoPlay, autoPlay = _e === void 0 ? true : _e, _f = _a.objectFit, objectFit = _f === void 0 ? "contain" : _f, _g = _a.className, className = _g === void 0 ? "" : _g, _h = _a.style, style = _h === void 0 ? {} : _h, isLoading = _a.isLoading, status = _a.status, error = _a.error;
76
+ return (React.createElement("div", { className: className, style: __assign({ position: "relative", width: width, height: height }, style) },
77
+ React.createElement("video", { ref: videoRef, controls: controls, playsInline: true, muted: true, autoPlay: autoPlay, style: {
78
+ display: "block",
79
+ width: "100%",
80
+ height: "100%",
81
+ backgroundColor: "black",
82
+ objectFit: objectFit,
83
+ } }),
84
+ (isLoading || status === "error") && (React.createElement("div", { style: {
85
+ position: "absolute",
86
+ top: "50%",
87
+ left: "50%",
88
+ transform: "translate(-50%, -50%)",
89
+ display: "flex",
90
+ flexDirection: "column",
91
+ alignItems: "center",
92
+ gap: 12,
93
+ } },
94
+ error && error.toString().toLowerCase().includes("stream not found") ? (React.createElement("div", { style: { color: "white", fontSize: 16 } }, "Stream not found")) : error && error.toString().toLowerCase().includes("connection failed") ? (React.createElement("div", { style: { color: "white", fontSize: 16 } }, "Connection failed")) : status === "error" ? (React.createElement("div", { style: { color: "white", fontSize: 16 } }, error)) : (React.createElement(React.Fragment, null,
95
+ React.createElement("div", { style: {
96
+ width: 40,
97
+ height: 40,
98
+ border: "4px solid rgba(255,255,255,0.3)",
99
+ borderTop: "4px solid white",
100
+ borderRadius: "50%",
101
+ animation: "spin 1s linear infinite",
102
+ } }),
103
+ status === "reconnecting" && (React.createElement("div", { style: { color: "white", fontSize: 16 } }, "Reconnecting...")))),
104
+ React.createElement("style", null, "@keyframes spin { to { transform: rotate(360deg); } }")))));
105
+ };
106
+
107
+ function toWsUrl(src) {
108
+ if (src.startsWith("http"))
109
+ return "ws" + src.substring(4);
110
+ if (src.startsWith("/"))
111
+ return "ws" + window.location.origin.substring(4) + src;
112
+ return src;
113
+ }
114
+ function useVideoPlayer(onStatus, onError) {
115
+ var _a = useState("connecting"), status = _a[0], setStatus = _a[1];
116
+ var _b = useState(null), error = _b[0], setError = _b[1];
117
+ var _c = useState(false), isPlaying = _c[0], setIsPlaying = _c[1];
118
+ var onStatusRef = useRef(onStatus);
119
+ var onErrorRef = useRef(onError);
120
+ useEffect(function () { onStatusRef.current = onStatus; }, [onStatus]);
121
+ useEffect(function () { onErrorRef.current = onError; }, [onError]);
122
+ var updateStatus = function (s) {
123
+ var _a;
124
+ setStatus(s);
125
+ (_a = onStatusRef.current) === null || _a === void 0 ? void 0 : _a.call(onStatusRef, s);
126
+ };
127
+ var updateError = function (e) {
128
+ var _a;
129
+ setError(e);
130
+ (_a = onErrorRef.current) === null || _a === void 0 ? void 0 : _a.call(onErrorRef, e);
131
+ };
132
+ return { status: status, error: error, isPlaying: isPlaying, setIsPlaying: setIsPlaying, updateStatus: updateStatus, updateError: updateError };
133
+ }
134
+
36
135
  var MSEVideoStream = function (_a) {
37
- var src = _a.src, _b = _a.width, width = _b === void 0 ? "100%" : _b, _c = _a.height, height = _c === void 0 ? "100%" : _c, _d = _a.controls, controls = _d === void 0 ? false : _d, _e = _a.autoPlay, autoPlay = _e === void 0 ? true : _e, _f = _a.media, media = _f === void 0 ? "video,audio" : _f, onStatus = _a.onStatus, onError = _a.onError, _g = _a.className, className = _g === void 0 ? "" : _g, _h = _a.style, style = _h === void 0 ? {} : _h, _j = _a.dataTimeout, dataTimeout = _j === void 0 ? 3000 : _j, _k = _a.objectFit, objectFit = _k === void 0 ? "contain" : _k;
136
+ var src = _a.src, _b = _a.width, width = _b === void 0 ? "100%" : _b, _c = _a.height, height = _c === void 0 ? "100%" : _c, _d = _a.controls, controls = _d === void 0 ? false : _d, _e = _a.autoPlay, autoPlay = _e === void 0 ? true : _e, _f = _a.media, media = _f === void 0 ? "video,audio" : _f, onStatus = _a.onStatus, onError = _a.onError, _g = _a.className, className = _g === void 0 ? "" : _g, _h = _a.style, style = _h === void 0 ? {} : _h, _j = _a.dataTimeout, dataTimeout = _j === void 0 ? 10000 : _j, _k = _a.objectFit, objectFit = _k === void 0 ? "contain" : _k, _l = _a.debug, debug = _l === void 0 ? false : _l;
38
137
  var videoRef = useRef(null);
39
138
  var stateRef = useRef({
40
139
  ws: null,
@@ -51,28 +150,8 @@ var MSEVideoStream = function (_a) {
51
150
  });
52
151
  // Track connection start time for smart reconnect delay
53
152
  var connectTSRef = useRef(0);
54
- var _l = useState("connecting"), status = _l[0], setStatus = _l[1];
55
- var _m = useState(null), error = _m[0], setError = _m[1];
56
- var _o = useState(false); _o[0]; var setHasReceivedData = _o[1];
57
- var _p = useState(false), isPlaying = _p[0], setIsPlaying = _p[1];
58
- var onStatusRef = useRef(onStatus);
59
- var onErrorRef = useRef(onError);
60
- useEffect(function () {
61
- onStatusRef.current = onStatus;
62
- }, [onStatus]);
63
- useEffect(function () {
64
- onErrorRef.current = onError;
65
- }, [onError]);
66
- var updateStatus = function (s) {
67
- var _a;
68
- setStatus(s);
69
- (_a = onStatusRef.current) === null || _a === void 0 ? void 0 : _a.call(onStatusRef, s);
70
- };
71
- var updateError = function (e) {
72
- var _a;
73
- setError(e);
74
- (_a = onErrorRef.current) === null || _a === void 0 ? void 0 : _a.call(onErrorRef, e);
75
- };
153
+ var _m = useState(false), setHasReceivedData = _m[1];
154
+ var _o = useVideoPlayer(onStatus, onError), status = _o.status, error = _o.error, isPlaying = _o.isPlaying, setIsPlaying = _o.setIsPlaying, updateStatus = _o.updateStatus, updateError = _o.updateError;
76
155
  // Codecs list from VideoRTC
77
156
  var codecsRef = useRef([
78
157
  "avc1.640029", // H.264 high 4.1 (Chromecast 1st and 2nd Gen)
@@ -107,10 +186,7 @@ var MSEVideoStream = function (_a) {
107
186
  videoRef.current.play().catch(function () { });
108
187
  }
109
188
  };
110
- // Track when video actually starts playing to hide spinner
111
- var handlePlaying = function () {
112
- setIsPlaying(true);
113
- };
189
+ var handlePlaying = function () { return setIsPlaying(true); };
114
190
  videoRef.current.addEventListener("pause", handlePause);
115
191
  videoRef.current.addEventListener("playing", handlePlaying);
116
192
  var cleanup = function () {
@@ -130,7 +206,6 @@ var MSEVideoStream = function (_a) {
130
206
  state.sbUpdateHandler = null;
131
207
  }
132
208
  if (state.ws) {
133
- // Prevent onclose iteration if we are manually closing
134
209
  state.ws.onclose = null;
135
210
  state.ws.close();
136
211
  state.ws = null;
@@ -162,16 +237,25 @@ var MSEVideoStream = function (_a) {
162
237
  setHasReceivedData(false);
163
238
  setIsPlaying(false);
164
239
  };
165
- var reconnect = function () {
240
+ var reconnect = function (reason) {
166
241
  if (!state.isMounted || state.isReconnecting)
167
242
  return;
243
+ if (debug)
244
+ console.log("[MSEVideoStream] Starting reconnect sequence. Reason: ".concat(reason));
168
245
  state.isReconnecting = true;
169
246
  cleanup();
170
247
  updateStatus("reconnecting");
171
248
  state.reconnectTimer = setTimeout(function () {
172
249
  state.isReconnecting = false;
173
250
  if (state.isMounted) {
174
- connect();
251
+ try {
252
+ connect();
253
+ }
254
+ catch (e) {
255
+ if (debug)
256
+ console.error("[MSEVideoStream] Reconnect failed synchronously:", e);
257
+ reconnect("SyncError");
258
+ }
175
259
  }
176
260
  }, 2000);
177
261
  };
@@ -179,26 +263,23 @@ var MSEVideoStream = function (_a) {
179
263
  if (state.stalledCheckTimer)
180
264
  clearInterval(state.stalledCheckTimer);
181
265
  state.stalledCheckTimer = setInterval(function () {
182
- // Only check for stalls if we've received at least one data packet
183
266
  if (!state.hasReceivedData || !state.lastDataTime)
184
267
  return;
185
268
  var now = Date.now();
186
- // If background, use lenient 15s threshold to allow for browser throttling
187
- // If foreground, use standard timeout (default 3s)
188
269
  var threshold = document.hidden ? 15000 : dataTimeout;
189
270
  if (now - state.lastDataTime > threshold) {
190
- // console.warn("Stall detected", now - state.lastDataTime);
191
- reconnect();
271
+ if (debug)
272
+ console.warn("[MSEVideoStream] Stall detected! Time since last data: ".concat(now - state.lastDataTime, "ms, Threshold: ").concat(threshold, "ms"));
273
+ reconnect("Stall");
192
274
  }
193
275
  }, 1000);
194
276
  };
195
277
  var onMSE = function (ms, codecString) {
196
278
  if (!state.ms)
197
- return; // Cleanup happened?
279
+ return;
198
280
  var sb = ms.addSourceBuffer(codecString);
199
281
  sb.mode = "segments";
200
282
  state.sbUpdateHandler = function () {
201
- // 1. Append pending data
202
283
  if (!sb.updating && state.buffer.length > 0) {
203
284
  try {
204
285
  var data = state.buffer.data.subarray(0, state.buffer.length);
@@ -209,7 +290,6 @@ var MSEVideoStream = function (_a) {
209
290
  /* ignore */
210
291
  }
211
292
  }
212
- // 2. Buffer management and smooth playback sync (VideoRTC logic)
213
293
  if (!sb.updating &&
214
294
  sb.buffered &&
215
295
  sb.buffered.length > 0 &&
@@ -218,28 +298,20 @@ var MSEVideoStream = function (_a) {
218
298
  var end = sb.buffered.end(sb.buffered.length - 1);
219
299
  var start = end - 5;
220
300
  var start0 = sb.buffered.start(0);
221
- // Trim everything older than 5 seconds from the end
222
301
  if (start > start0) {
223
302
  try {
224
303
  sb.remove(start0, start);
225
- // Set live seekable range so the browser knows where we are
226
304
  if (ms.setLiveSeekableRange) {
227
305
  ms.setLiveSeekableRange(start, end);
228
306
  }
229
307
  }
230
308
  catch (e) { }
231
309
  }
232
- // Jump forward if we fell behind the buffer window
233
310
  if (video.currentTime < start) {
234
311
  video.currentTime = start;
235
312
  }
236
- // Smooth playrate adjustment
237
313
  var gap = end - video.currentTime;
238
- // "gap > 0.1 ? gap : 0.1" logic from VideoRTC
239
- // This effectively slows down playback if we are too close to end (to avoid stall)
240
- // And speeds up playback to match gap if we are behind.
241
314
  video.playbackRate = gap > 0.1 ? gap : 0.1;
242
- // Ensure we are playing
243
315
  if (video.paused && !video.ended && video.readyState > 2) {
244
316
  video.play().catch(function () { });
245
317
  }
@@ -248,7 +320,6 @@ var MSEVideoStream = function (_a) {
248
320
  sb.addEventListener("updateend", state.sbUpdateHandler);
249
321
  state.sb = sb;
250
322
  updateStatus("streaming");
251
- // Start checking for stalls once we have the source buffer set up
252
323
  state.lastDataTime = Date.now();
253
324
  startStalledCheck();
254
325
  };
@@ -298,22 +369,27 @@ var MSEVideoStream = function (_a) {
298
369
  videoRef.current.src = URL.createObjectURL(state.ms);
299
370
  videoRef.current.srcObject = null;
300
371
  }
301
- // Ensure play is called
302
372
  videoRef.current.play().catch(function () { });
303
373
  }
304
374
  };
305
375
  var connect = function () {
306
376
  updateStatus("connecting");
307
377
  connectTSRef.current = Date.now();
308
- state.lastDataTime = Date.now(); // Initialize to avoid immediate stall detection
309
- var wsURL = src;
310
- if (wsURL.startsWith("http")) {
311
- wsURL = "ws" + wsURL.substring(4);
378
+ state.lastDataTime = Date.now();
379
+ var wsURL = toWsUrl(src);
380
+ if (debug)
381
+ console.log("[MSEVideoStream] Connecting to:", wsURL);
382
+ var ws;
383
+ try {
384
+ ws = new WebSocket(wsURL);
312
385
  }
313
- else if (wsURL.startsWith("/")) {
314
- wsURL = "ws" + window.location.origin.substring(4) + wsURL;
386
+ catch (err) {
387
+ if (debug)
388
+ console.error("[MSEVideoStream] WebSocket creation failed synchronously:", err);
389
+ updateError(err);
390
+ reconnect("WSCreationFail");
391
+ return;
315
392
  }
316
- var ws = new WebSocket(wsURL);
317
393
  ws.binaryType = "arraybuffer";
318
394
  state.ws = ws;
319
395
  ws.onopen = function () {
@@ -328,24 +404,30 @@ var MSEVideoStream = function (_a) {
328
404
  onMSE(state.ms, msg.value);
329
405
  }
330
406
  else if (msg.type === "error") {
331
- console.warn("MSE Error:", msg.value);
407
+ if (debug)
408
+ console.warn("MSE Error:", msg.value);
332
409
  updateError(msg.value);
333
- reconnect();
410
+ reconnect("ServerMsgError");
334
411
  }
335
412
  }
336
413
  else {
337
- appendData(ev.data);
414
+ try {
415
+ appendData(ev.data);
416
+ }
417
+ catch (e) {
418
+ if (debug)
419
+ console.error("AppendData error", e);
420
+ }
338
421
  }
339
422
  };
340
423
  ws.onclose = function () {
341
424
  if (state.ws === ws) {
342
425
  state.ws = null;
343
426
  updateStatus("closed");
344
- reconnect();
427
+ reconnect("WSClosed");
345
428
  }
346
429
  };
347
430
  ws.onerror = function () {
348
- // VideoRTC mostly handles onclose for reconnect logic
349
431
  updateError("Connection failed");
350
432
  };
351
433
  };
@@ -362,38 +444,240 @@ var MSEVideoStream = function (_a) {
362
444
  var isLoading = status === "connecting" ||
363
445
  status === "reconnecting" ||
364
446
  (status === "streaming" && !isPlaying);
365
- return (React.createElement("div", { className: className, style: __assign({ position: "relative", width: width, height: height }, style) },
366
- React.createElement("video", { ref: videoRef, controls: controls, playsInline: true, muted: true, autoPlay: autoPlay, style: {
367
- display: "block",
368
- width: "100%",
369
- height: "100%",
370
- backgroundColor: "black",
371
- objectFit: objectFit,
372
- } }),
373
- (isLoading || status === "error") && (React.createElement("div", { style: {
374
- position: "absolute",
375
- top: "50%",
376
- left: "50%",
377
- transform: "translate(-50%, -50%)",
378
- display: "flex",
379
- flexDirection: "column",
380
- alignItems: "center",
381
- gap: 12,
382
- } },
383
- error &&
384
- error.toString().toLowerCase().includes("stream not found") ? (React.createElement("div", { style: { color: "white", fontSize: 16 } }, "Stream not found")) : error &&
385
- error.toString().toLowerCase().includes("connection failed") ? (React.createElement("div", { style: { color: "white", fontSize: 16 } }, "Connection failed")) : status === "error" ? (React.createElement("div", { style: { color: "white", fontSize: 16 } }, error)) : (React.createElement(React.Fragment, null,
386
- React.createElement("div", { style: {
387
- width: 40,
388
- height: 40,
389
- border: "4px solid rgba(255,255,255,0.3)",
390
- borderTop: "4px solid white",
391
- borderRadius: "50%",
392
- animation: "spin 1s linear infinite",
393
- } }),
394
- status === "reconnecting" && (React.createElement("div", { style: { color: "white", fontSize: 16 } }, "Reconnecting...")))),
395
- React.createElement("style", null, "@keyframes spin { to { transform: rotate(360deg); } }")))));
447
+ return (React.createElement(VideoShell, { videoRef: videoRef, width: width, height: height, controls: controls, autoPlay: autoPlay, objectFit: objectFit, className: className, style: style, isLoading: isLoading, status: status, error: error }));
448
+ };
449
+
450
+ var DEFAULT_PC_CONFIG = {
451
+ bundlePolicy: "max-bundle",
452
+ iceServers: [
453
+ {
454
+ urls: ["stun:stun.cloudflare.com:3478", "stun:stun.l.google.com:19302"],
455
+ },
456
+ ],
457
+ };
458
+ var RECONNECT_TIMEOUT = 15000;
459
+ var RECONNECT_DELAY = 2000;
460
+ var WebRTCVideoStream = function (_a) {
461
+ var src = _a.src, _b = _a.width, width = _b === void 0 ? "100%" : _b, _c = _a.height, height = _c === void 0 ? "100%" : _c, _d = _a.controls, controls = _d === void 0 ? false : _d, _e = _a.autoPlay, autoPlay = _e === void 0 ? true : _e, _f = _a.media, media = _f === void 0 ? "video,audio" : _f, _g = _a.mode, mode = _g === void 0 ? "webrtc" : _g, pcConfig = _a.pcConfig, onStatus = _a.onStatus, onError = _a.onError, _h = _a.className, className = _h === void 0 ? "" : _h, _j = _a.style, style = _j === void 0 ? {} : _j, _k = _a.objectFit, objectFit = _k === void 0 ? "contain" : _k, _l = _a.debug, debug = _l === void 0 ? false : _l;
462
+ var videoRef = useRef(null);
463
+ var stateRef = useRef({
464
+ ws: null,
465
+ pc: null,
466
+ isMounted: true,
467
+ isReconnecting: false,
468
+ reconnectTimer: null,
469
+ connectTS: 0,
470
+ });
471
+ var _m = useVideoPlayer(onStatus, onError), status = _m.status, error = _m.error, isPlaying = _m.isPlaying, setIsPlaying = _m.setIsPlaying, updateStatus = _m.updateStatus, updateError = _m.updateError;
472
+ useEffect(function () {
473
+ if (!src || !videoRef.current)
474
+ return;
475
+ var state = stateRef.current;
476
+ state.isMounted = true;
477
+ state.isReconnecting = false;
478
+ var effectPcConfig = pcConfig !== null && pcConfig !== void 0 ? pcConfig : DEFAULT_PC_CONFIG;
479
+ var handlePlaying = function () { return setIsPlaying(true); };
480
+ videoRef.current.addEventListener("playing", handlePlaying);
481
+ var cleanup = function () {
482
+ if (state.reconnectTimer) {
483
+ clearTimeout(state.reconnectTimer);
484
+ state.reconnectTimer = null;
485
+ }
486
+ if (state.pc) {
487
+ state.pc.getSenders().forEach(function (sender) { var _a; return (_a = sender.track) === null || _a === void 0 ? void 0 : _a.stop(); });
488
+ state.pc.close();
489
+ state.pc = null;
490
+ }
491
+ if (state.ws) {
492
+ state.ws.onclose = null;
493
+ state.ws.close();
494
+ state.ws = null;
495
+ }
496
+ if (videoRef.current) {
497
+ videoRef.current.srcObject = null;
498
+ }
499
+ };
500
+ var reconnect = function (reason) {
501
+ if (!state.isMounted || state.isReconnecting)
502
+ return;
503
+ if (debug)
504
+ console.log("[WebRTCVideoStream] Reconnecting. Reason: ".concat(reason));
505
+ state.isReconnecting = true;
506
+ cleanup();
507
+ updateStatus("reconnecting");
508
+ setIsPlaying(false);
509
+ var elapsed = Date.now() - state.connectTS;
510
+ var delay = Math.max(RECONNECT_DELAY, RECONNECT_TIMEOUT - elapsed);
511
+ state.reconnectTimer = setTimeout(function () {
512
+ state.isReconnecting = false;
513
+ if (state.isMounted)
514
+ connect();
515
+ }, delay);
516
+ };
517
+ var createOffer = function (pc) { return __awaiter(void 0, void 0, void 0, function () {
518
+ var stream, e_1, _i, _a, kind, offer;
519
+ return __generator(this, function (_b) {
520
+ switch (_b.label) {
521
+ case 0:
522
+ if (!media.includes("microphone")) return [3 /*break*/, 4];
523
+ _b.label = 1;
524
+ case 1:
525
+ _b.trys.push([1, 3, , 4]);
526
+ return [4 /*yield*/, navigator.mediaDevices.getUserMedia({ audio: true })];
527
+ case 2:
528
+ stream = _b.sent();
529
+ stream.getTracks().forEach(function (track) {
530
+ return pc.addTransceiver(track, { direction: "sendonly" });
531
+ });
532
+ return [3 /*break*/, 4];
533
+ case 3:
534
+ e_1 = _b.sent();
535
+ console.warn("[WebRTCVideoStream] Microphone access denied:", e_1);
536
+ return [3 /*break*/, 4];
537
+ case 4:
538
+ for (_i = 0, _a = ["video", "audio"]; _i < _a.length; _i++) {
539
+ kind = _a[_i];
540
+ if (media.includes(kind)) {
541
+ pc.addTransceiver(kind, { direction: "recvonly" });
542
+ }
543
+ }
544
+ return [4 /*yield*/, pc.createOffer()];
545
+ case 5:
546
+ offer = _b.sent();
547
+ return [4 /*yield*/, pc.setLocalDescription(offer)];
548
+ case 6:
549
+ _b.sent();
550
+ return [2 /*return*/, offer];
551
+ }
552
+ });
553
+ }); };
554
+ var connect = function () {
555
+ updateStatus("connecting");
556
+ updateError(null);
557
+ state.connectTS = Date.now();
558
+ var wsURL = toWsUrl(src);
559
+ if (debug)
560
+ console.log("[WebRTCVideoStream] Connecting to:", wsURL);
561
+ var ws;
562
+ try {
563
+ ws = new WebSocket(wsURL);
564
+ }
565
+ catch (err) {
566
+ if (debug)
567
+ console.error("[WebRTCVideoStream] WebSocket creation failed:", err);
568
+ updateError(err);
569
+ reconnect("WSCreationFail");
570
+ return;
571
+ }
572
+ state.ws = ws;
573
+ ws.onopen = function () {
574
+ if (debug)
575
+ console.log("[WebRTCVideoStream] WebSocket open, setting up PeerConnection");
576
+ updateStatus("open");
577
+ var pc = new RTCPeerConnection(effectPcConfig);
578
+ state.pc = pc;
579
+ pc.addEventListener("icecandidate", function (ev) {
580
+ if (ev.candidate && mode === "webrtc/tcp" && ev.candidate.protocol === "udp")
581
+ return;
582
+ var candidate = ev.candidate ? ev.candidate.toJSON().candidate : "";
583
+ ws.send(JSON.stringify({ type: "webrtc/candidate", value: candidate }));
584
+ });
585
+ pc.addEventListener("connectionstatechange", function () {
586
+ var _a;
587
+ if (debug)
588
+ console.log("[WebRTCVideoStream] PC state:", pc.connectionState);
589
+ if (pc.connectionState === "connected") {
590
+ updateStatus("connected");
591
+ var tracks = pc
592
+ .getTransceivers()
593
+ .filter(function (tr) { return tr.currentDirection === "recvonly"; })
594
+ .map(function (tr) { return tr.receiver.track; });
595
+ if (videoRef.current && tracks.length > 0) {
596
+ videoRef.current.srcObject = new MediaStream(tracks);
597
+ videoRef.current.play().catch(function () {
598
+ if (videoRef.current && !videoRef.current.muted) {
599
+ videoRef.current.muted = true;
600
+ videoRef.current.play().catch(function (e) { return console.warn(e); });
601
+ }
602
+ });
603
+ }
604
+ // WebRTC is up — close the WS (signalling only)
605
+ (_a = state.ws) === null || _a === void 0 ? void 0 : _a.close();
606
+ state.ws = null;
607
+ }
608
+ else if (pc.connectionState === "failed" ||
609
+ pc.connectionState === "disconnected") {
610
+ pc.close();
611
+ state.pc = null;
612
+ reconnect("PCState:".concat(pc.connectionState));
613
+ }
614
+ });
615
+ ws.onmessage = function (ev) {
616
+ if (typeof ev.data !== "string")
617
+ return;
618
+ var msg = JSON.parse(ev.data);
619
+ if (debug)
620
+ console.log("[WebRTCVideoStream] WS message:", msg.type);
621
+ switch (msg.type) {
622
+ case "webrtc/candidate":
623
+ if (mode === "webrtc/tcp" && msg.value.includes(" udp "))
624
+ return;
625
+ pc.addIceCandidate({ candidate: msg.value, sdpMid: "0" }).catch(function (e) {
626
+ return console.warn("[WebRTCVideoStream] addIceCandidate error:", e);
627
+ });
628
+ break;
629
+ case "webrtc/answer":
630
+ pc.setRemoteDescription({ type: "answer", sdp: msg.value }).catch(function (e) {
631
+ return console.warn("[WebRTCVideoStream] setRemoteDescription error:", e);
632
+ });
633
+ break;
634
+ case "error":
635
+ if (!msg.value.includes("webrtc"))
636
+ return;
637
+ updateError(msg.value);
638
+ pc.close();
639
+ state.pc = null;
640
+ reconnect("ServerError");
641
+ break;
642
+ }
643
+ };
644
+ createOffer(pc)
645
+ .then(function (offer) {
646
+ ws.send(JSON.stringify({ type: "webrtc/offer", value: offer.sdp }));
647
+ })
648
+ .catch(function (e) {
649
+ console.error("[WebRTCVideoStream] createOffer failed:", e);
650
+ updateError(e);
651
+ reconnect("OfferFailed");
652
+ });
653
+ };
654
+ ws.onclose = function () {
655
+ if (state.ws === ws) {
656
+ state.ws = null;
657
+ if (!state.pc || state.pc.connectionState !== "connected") {
658
+ updateStatus("closed");
659
+ reconnect("WSClosed");
660
+ }
661
+ }
662
+ };
663
+ ws.onerror = function () {
664
+ updateError("Connection failed");
665
+ };
666
+ };
667
+ connect();
668
+ return function () {
669
+ state.isMounted = false;
670
+ if (videoRef.current) {
671
+ videoRef.current.removeEventListener("playing", handlePlaying);
672
+ }
673
+ cleanup();
674
+ };
675
+ }, [src, media, mode]);
676
+ var isLoading = status === "connecting" ||
677
+ status === "reconnecting" ||
678
+ (status === "connected" && !isPlaying);
679
+ return (React.createElement(VideoShell, { videoRef: videoRef, width: width, height: height, controls: controls, autoPlay: autoPlay, objectFit: objectFit, className: className, style: style, isLoading: isLoading, status: status, error: error }));
396
680
  };
397
681
 
398
- export { MSEVideoStream, MSEVideoStream as default };
682
+ export { MSEVideoStream, WebRTCVideoStream, MSEVideoStream as default };
399
683
  //# sourceMappingURL=index.esm.js.map