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