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.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,11 +28,110 @@ 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
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);
@@ -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 _m = useState("connecting"), status = _m[0], setStatus = _m[1];
55
- var _o = useState(null), error = _o[0], setError = _o[1];
56
- var _p = useState(false); _p[0]; var setHasReceivedData = _p[1];
57
- var _q = useState(false), isPlaying = _q[0], setIsPlaying = _q[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;
@@ -188,12 +263,9 @@ var MSEVideoStream = function (_a) {
188
263
  if (state.stalledCheckTimer)
189
264
  clearInterval(state.stalledCheckTimer);
190
265
  state.stalledCheckTimer = setInterval(function () {
191
- // Only check for stalls if we've received at least one data packet
192
266
  if (!state.hasReceivedData || !state.lastDataTime)
193
267
  return;
194
268
  var now = Date.now();
195
- // If background, use lenient 15s threshold to allow for browser throttling
196
- // If foreground, use standard timeout (default 3s)
197
269
  var threshold = document.hidden ? 15000 : dataTimeout;
198
270
  if (now - state.lastDataTime > threshold) {
199
271
  if (debug)
@@ -204,11 +276,10 @@ var MSEVideoStream = function (_a) {
204
276
  };
205
277
  var onMSE = function (ms, codecString) {
206
278
  if (!state.ms)
207
- return; // Cleanup happened?
279
+ return;
208
280
  var sb = ms.addSourceBuffer(codecString);
209
281
  sb.mode = "segments";
210
282
  state.sbUpdateHandler = function () {
211
- // 1. Append pending data
212
283
  if (!sb.updating && state.buffer.length > 0) {
213
284
  try {
214
285
  var data = state.buffer.data.subarray(0, state.buffer.length);
@@ -219,7 +290,6 @@ var MSEVideoStream = function (_a) {
219
290
  /* ignore */
220
291
  }
221
292
  }
222
- // 2. Buffer management and smooth playback sync (VideoRTC logic)
223
293
  if (!sb.updating &&
224
294
  sb.buffered &&
225
295
  sb.buffered.length > 0 &&
@@ -228,28 +298,20 @@ var MSEVideoStream = function (_a) {
228
298
  var end = sb.buffered.end(sb.buffered.length - 1);
229
299
  var start = end - 5;
230
300
  var start0 = sb.buffered.start(0);
231
- // Trim everything older than 5 seconds from the end
232
301
  if (start > start0) {
233
302
  try {
234
303
  sb.remove(start0, start);
235
- // Set live seekable range so the browser knows where we are
236
304
  if (ms.setLiveSeekableRange) {
237
305
  ms.setLiveSeekableRange(start, end);
238
306
  }
239
307
  }
240
308
  catch (e) { }
241
309
  }
242
- // Jump forward if we fell behind the buffer window
243
310
  if (video.currentTime < start) {
244
311
  video.currentTime = start;
245
312
  }
246
- // Smooth playrate adjustment
247
313
  var gap = end - video.currentTime;
248
- // "gap > 0.1 ? gap : 0.1" logic from VideoRTC
249
- // This effectively slows down playback if we are too close to end (to avoid stall)
250
- // And speeds up playback to match gap if we are behind.
251
314
  video.playbackRate = gap > 0.1 ? gap : 0.1;
252
- // Ensure we are playing
253
315
  if (video.paused && !video.ended && video.readyState > 2) {
254
316
  video.play().catch(function () { });
255
317
  }
@@ -258,7 +320,6 @@ var MSEVideoStream = function (_a) {
258
320
  sb.addEventListener("updateend", state.sbUpdateHandler);
259
321
  state.sb = sb;
260
322
  updateStatus("streaming");
261
- // Start checking for stalls once we have the source buffer set up
262
323
  state.lastDataTime = Date.now();
263
324
  startStalledCheck();
264
325
  };
@@ -308,21 +369,14 @@ var MSEVideoStream = function (_a) {
308
369
  videoRef.current.src = URL.createObjectURL(state.ms);
309
370
  videoRef.current.srcObject = null;
310
371
  }
311
- // Ensure play is called
312
372
  videoRef.current.play().catch(function () { });
313
373
  }
314
374
  };
315
375
  var connect = function () {
316
376
  updateStatus("connecting");
317
377
  connectTSRef.current = Date.now();
318
- state.lastDataTime = Date.now(); // Initialize to avoid immediate stall detection
319
- var wsURL = src;
320
- if (wsURL.startsWith("http")) {
321
- wsURL = "ws" + wsURL.substring(4);
322
- }
323
- else if (wsURL.startsWith("/")) {
324
- wsURL = "ws" + window.location.origin.substring(4) + wsURL;
325
- }
378
+ state.lastDataTime = Date.now();
379
+ var wsURL = toWsUrl(src);
326
380
  if (debug)
327
381
  console.log("[MSEVideoStream] Connecting to:", wsURL);
328
382
  var ws;
@@ -374,7 +428,6 @@ var MSEVideoStream = function (_a) {
374
428
  }
375
429
  };
376
430
  ws.onerror = function () {
377
- // VideoRTC mostly handles onclose for reconnect logic
378
431
  updateError("Connection failed");
379
432
  };
380
433
  };
@@ -391,38 +444,240 @@ var MSEVideoStream = function (_a) {
391
444
  var isLoading = status === "connecting" ||
392
445
  status === "reconnecting" ||
393
446
  (status === "streaming" && !isPlaying);
394
- return (React.createElement("div", { className: className, style: __assign({ position: "relative", width: width, height: height }, style) },
395
- React.createElement("video", { ref: videoRef, controls: controls, playsInline: true, muted: true, autoPlay: autoPlay, style: {
396
- display: "block",
397
- width: "100%",
398
- height: "100%",
399
- backgroundColor: "black",
400
- objectFit: objectFit,
401
- } }),
402
- (isLoading || status === "error") && (React.createElement("div", { style: {
403
- position: "absolute",
404
- top: "50%",
405
- left: "50%",
406
- transform: "translate(-50%, -50%)",
407
- display: "flex",
408
- flexDirection: "column",
409
- alignItems: "center",
410
- gap: 12,
411
- } },
412
- error &&
413
- error.toString().toLowerCase().includes("stream not found") ? (React.createElement("div", { style: { color: "white", fontSize: 16 } }, "Stream not found")) : error &&
414
- 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,
415
- React.createElement("div", { style: {
416
- width: 40,
417
- height: 40,
418
- border: "4px solid rgba(255,255,255,0.3)",
419
- borderTop: "4px solid white",
420
- borderRadius: "50%",
421
- animation: "spin 1s linear infinite",
422
- } }),
423
- status === "reconnecting" && (React.createElement("div", { style: { color: "white", fontSize: 16 } }, "Reconnecting...")))),
424
- 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 }));
425
680
  };
426
681
 
427
- export { MSEVideoStream, MSEVideoStream as default };
682
+ export { MSEVideoStream, WebRTCVideoStream, MSEVideoStream as default };
428
683
  //# sourceMappingURL=index.esm.js.map