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/README.md +103 -57
- package/dist/VideoShell.d.ts +16 -0
- package/dist/WebRTCVideoStream.d.ts +29 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.esm.js +340 -85
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +339 -83
- package/dist/index.js.map +1 -1
- package/dist/useVideoPlayer.d.ts +9 -0
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import 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(
|
|
55
|
-
var _o =
|
|
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
|
-
|
|
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;
|
|
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();
|
|
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(
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
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
|