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.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(
|
|
59
|
-
var _o =
|
|
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
|
-
|
|
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;
|
|
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();
|
|
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(
|
|
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
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
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
|