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.js CHANGED
@@ -32,13 +32,112 @@ 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
- 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;
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);
43
142
  var stateRef = React.useRef({
44
143
  ws: 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 _l = React.useState("connecting"), status = _l[0], setStatus = _l[1];
59
- var _m = React.useState(null), error = _m[0], setError = _m[1];
60
- var _o = React.useState(false); _o[0]; var setHasReceivedData = _o[1];
61
- var _p = React.useState(false), isPlaying = _p[0], setIsPlaying = _p[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;
@@ -166,16 +241,25 @@ var MSEVideoStream = function (_a) {
166
241
  setHasReceivedData(false);
167
242
  setIsPlaying(false);
168
243
  };
169
- var reconnect = function () {
244
+ var reconnect = function (reason) {
170
245
  if (!state.isMounted || state.isReconnecting)
171
246
  return;
247
+ if (debug)
248
+ console.log("[MSEVideoStream] Starting reconnect sequence. Reason: ".concat(reason));
172
249
  state.isReconnecting = true;
173
250
  cleanup();
174
251
  updateStatus("reconnecting");
175
252
  state.reconnectTimer = setTimeout(function () {
176
253
  state.isReconnecting = false;
177
254
  if (state.isMounted) {
178
- connect();
255
+ try {
256
+ connect();
257
+ }
258
+ catch (e) {
259
+ if (debug)
260
+ console.error("[MSEVideoStream] Reconnect failed synchronously:", e);
261
+ reconnect("SyncError");
262
+ }
179
263
  }
180
264
  }, 2000);
181
265
  };
@@ -183,26 +267,23 @@ var MSEVideoStream = function (_a) {
183
267
  if (state.stalledCheckTimer)
184
268
  clearInterval(state.stalledCheckTimer);
185
269
  state.stalledCheckTimer = setInterval(function () {
186
- // Only check for stalls if we've received at least one data packet
187
270
  if (!state.hasReceivedData || !state.lastDataTime)
188
271
  return;
189
272
  var now = Date.now();
190
- // If background, use lenient 15s threshold to allow for browser throttling
191
- // If foreground, use standard timeout (default 3s)
192
273
  var threshold = document.hidden ? 15000 : dataTimeout;
193
274
  if (now - state.lastDataTime > threshold) {
194
- // console.warn("Stall detected", now - state.lastDataTime);
195
- reconnect();
275
+ if (debug)
276
+ console.warn("[MSEVideoStream] Stall detected! Time since last data: ".concat(now - state.lastDataTime, "ms, Threshold: ").concat(threshold, "ms"));
277
+ reconnect("Stall");
196
278
  }
197
279
  }, 1000);
198
280
  };
199
281
  var onMSE = function (ms, codecString) {
200
282
  if (!state.ms)
201
- return; // Cleanup happened?
283
+ return;
202
284
  var sb = ms.addSourceBuffer(codecString);
203
285
  sb.mode = "segments";
204
286
  state.sbUpdateHandler = function () {
205
- // 1. Append pending data
206
287
  if (!sb.updating && state.buffer.length > 0) {
207
288
  try {
208
289
  var data = state.buffer.data.subarray(0, state.buffer.length);
@@ -213,7 +294,6 @@ var MSEVideoStream = function (_a) {
213
294
  /* ignore */
214
295
  }
215
296
  }
216
- // 2. Buffer management and smooth playback sync (VideoRTC logic)
217
297
  if (!sb.updating &&
218
298
  sb.buffered &&
219
299
  sb.buffered.length > 0 &&
@@ -222,28 +302,20 @@ var MSEVideoStream = function (_a) {
222
302
  var end = sb.buffered.end(sb.buffered.length - 1);
223
303
  var start = end - 5;
224
304
  var start0 = sb.buffered.start(0);
225
- // Trim everything older than 5 seconds from the end
226
305
  if (start > start0) {
227
306
  try {
228
307
  sb.remove(start0, start);
229
- // Set live seekable range so the browser knows where we are
230
308
  if (ms.setLiveSeekableRange) {
231
309
  ms.setLiveSeekableRange(start, end);
232
310
  }
233
311
  }
234
312
  catch (e) { }
235
313
  }
236
- // Jump forward if we fell behind the buffer window
237
314
  if (video.currentTime < start) {
238
315
  video.currentTime = start;
239
316
  }
240
- // Smooth playrate adjustment
241
317
  var gap = end - video.currentTime;
242
- // "gap > 0.1 ? gap : 0.1" logic from VideoRTC
243
- // This effectively slows down playback if we are too close to end (to avoid stall)
244
- // And speeds up playback to match gap if we are behind.
245
318
  video.playbackRate = gap > 0.1 ? gap : 0.1;
246
- // Ensure we are playing
247
319
  if (video.paused && !video.ended && video.readyState > 2) {
248
320
  video.play().catch(function () { });
249
321
  }
@@ -252,7 +324,6 @@ var MSEVideoStream = function (_a) {
252
324
  sb.addEventListener("updateend", state.sbUpdateHandler);
253
325
  state.sb = sb;
254
326
  updateStatus("streaming");
255
- // Start checking for stalls once we have the source buffer set up
256
327
  state.lastDataTime = Date.now();
257
328
  startStalledCheck();
258
329
  };
@@ -302,22 +373,27 @@ var MSEVideoStream = function (_a) {
302
373
  videoRef.current.src = URL.createObjectURL(state.ms);
303
374
  videoRef.current.srcObject = null;
304
375
  }
305
- // Ensure play is called
306
376
  videoRef.current.play().catch(function () { });
307
377
  }
308
378
  };
309
379
  var connect = function () {
310
380
  updateStatus("connecting");
311
381
  connectTSRef.current = Date.now();
312
- state.lastDataTime = Date.now(); // Initialize to avoid immediate stall detection
313
- var wsURL = src;
314
- if (wsURL.startsWith("http")) {
315
- wsURL = "ws" + wsURL.substring(4);
382
+ state.lastDataTime = Date.now();
383
+ var wsURL = toWsUrl(src);
384
+ if (debug)
385
+ console.log("[MSEVideoStream] Connecting to:", wsURL);
386
+ var ws;
387
+ try {
388
+ ws = new WebSocket(wsURL);
316
389
  }
317
- else if (wsURL.startsWith("/")) {
318
- wsURL = "ws" + window.location.origin.substring(4) + wsURL;
390
+ catch (err) {
391
+ if (debug)
392
+ console.error("[MSEVideoStream] WebSocket creation failed synchronously:", err);
393
+ updateError(err);
394
+ reconnect("WSCreationFail");
395
+ return;
319
396
  }
320
- var ws = new WebSocket(wsURL);
321
397
  ws.binaryType = "arraybuffer";
322
398
  state.ws = ws;
323
399
  ws.onopen = function () {
@@ -332,24 +408,30 @@ var MSEVideoStream = function (_a) {
332
408
  onMSE(state.ms, msg.value);
333
409
  }
334
410
  else if (msg.type === "error") {
335
- console.warn("MSE Error:", msg.value);
411
+ if (debug)
412
+ console.warn("MSE Error:", msg.value);
336
413
  updateError(msg.value);
337
- reconnect();
414
+ reconnect("ServerMsgError");
338
415
  }
339
416
  }
340
417
  else {
341
- appendData(ev.data);
418
+ try {
419
+ appendData(ev.data);
420
+ }
421
+ catch (e) {
422
+ if (debug)
423
+ console.error("AppendData error", e);
424
+ }
342
425
  }
343
426
  };
344
427
  ws.onclose = function () {
345
428
  if (state.ws === ws) {
346
429
  state.ws = null;
347
430
  updateStatus("closed");
348
- reconnect();
431
+ reconnect("WSClosed");
349
432
  }
350
433
  };
351
434
  ws.onerror = function () {
352
- // VideoRTC mostly handles onclose for reconnect logic
353
435
  updateError("Connection failed");
354
436
  };
355
437
  };
@@ -366,39 +448,242 @@ var MSEVideoStream = function (_a) {
366
448
  var isLoading = status === "connecting" ||
367
449
  status === "reconnecting" ||
368
450
  (status === "streaming" && !isPlaying);
369
- return (React.createElement("div", { className: className, style: __assign({ position: "relative", width: width, height: height }, style) },
370
- React.createElement("video", { ref: videoRef, controls: controls, playsInline: true, muted: true, autoPlay: autoPlay, style: {
371
- display: "block",
372
- width: "100%",
373
- height: "100%",
374
- backgroundColor: "black",
375
- objectFit: objectFit,
376
- } }),
377
- (isLoading || status === "error") && (React.createElement("div", { style: {
378
- position: "absolute",
379
- top: "50%",
380
- left: "50%",
381
- transform: "translate(-50%, -50%)",
382
- display: "flex",
383
- flexDirection: "column",
384
- alignItems: "center",
385
- gap: 12,
386
- } },
387
- error &&
388
- error.toString().toLowerCase().includes("stream not found") ? (React.createElement("div", { style: { color: "white", fontSize: 16 } }, "Stream not found")) : error &&
389
- 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,
390
- React.createElement("div", { style: {
391
- width: 40,
392
- height: 40,
393
- border: "4px solid rgba(255,255,255,0.3)",
394
- borderTop: "4px solid white",
395
- borderRadius: "50%",
396
- animation: "spin 1s linear infinite",
397
- } }),
398
- status === "reconnecting" && (React.createElement("div", { style: { color: "white", fontSize: 16 } }, "Reconnecting...")))),
399
- 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 }));
400
684
  };
401
685
 
402
686
  exports.MSEVideoStream = MSEVideoStream;
687
+ exports.WebRTCVideoStream = WebRTCVideoStream;
403
688
  exports.default = MSEVideoStream;
404
689
  //# sourceMappingURL=index.js.map