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/README.md +103 -57
- package/dist/MSEVideoStream.d.ts +1 -0
- 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 +377 -93
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +376 -91
- 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,13 +28,112 @@ 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
|
-
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 ?
|
|
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);
|
|
39
138
|
var stateRef = useRef({
|
|
40
139
|
ws: 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
|
|
55
|
-
var
|
|
56
|
-
var _o = useState(false); _o[0]; var setHasReceivedData = _o[1];
|
|
57
|
-
var _p = useState(false), isPlaying = _p[0], setIsPlaying = _p[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;
|
|
@@ -162,16 +237,25 @@ var MSEVideoStream = function (_a) {
|
|
|
162
237
|
setHasReceivedData(false);
|
|
163
238
|
setIsPlaying(false);
|
|
164
239
|
};
|
|
165
|
-
var reconnect = function () {
|
|
240
|
+
var reconnect = function (reason) {
|
|
166
241
|
if (!state.isMounted || state.isReconnecting)
|
|
167
242
|
return;
|
|
243
|
+
if (debug)
|
|
244
|
+
console.log("[MSEVideoStream] Starting reconnect sequence. Reason: ".concat(reason));
|
|
168
245
|
state.isReconnecting = true;
|
|
169
246
|
cleanup();
|
|
170
247
|
updateStatus("reconnecting");
|
|
171
248
|
state.reconnectTimer = setTimeout(function () {
|
|
172
249
|
state.isReconnecting = false;
|
|
173
250
|
if (state.isMounted) {
|
|
174
|
-
|
|
251
|
+
try {
|
|
252
|
+
connect();
|
|
253
|
+
}
|
|
254
|
+
catch (e) {
|
|
255
|
+
if (debug)
|
|
256
|
+
console.error("[MSEVideoStream] Reconnect failed synchronously:", e);
|
|
257
|
+
reconnect("SyncError");
|
|
258
|
+
}
|
|
175
259
|
}
|
|
176
260
|
}, 2000);
|
|
177
261
|
};
|
|
@@ -179,26 +263,23 @@ var MSEVideoStream = function (_a) {
|
|
|
179
263
|
if (state.stalledCheckTimer)
|
|
180
264
|
clearInterval(state.stalledCheckTimer);
|
|
181
265
|
state.stalledCheckTimer = setInterval(function () {
|
|
182
|
-
// Only check for stalls if we've received at least one data packet
|
|
183
266
|
if (!state.hasReceivedData || !state.lastDataTime)
|
|
184
267
|
return;
|
|
185
268
|
var now = Date.now();
|
|
186
|
-
// If background, use lenient 15s threshold to allow for browser throttling
|
|
187
|
-
// If foreground, use standard timeout (default 3s)
|
|
188
269
|
var threshold = document.hidden ? 15000 : dataTimeout;
|
|
189
270
|
if (now - state.lastDataTime > threshold) {
|
|
190
|
-
|
|
191
|
-
|
|
271
|
+
if (debug)
|
|
272
|
+
console.warn("[MSEVideoStream] Stall detected! Time since last data: ".concat(now - state.lastDataTime, "ms, Threshold: ").concat(threshold, "ms"));
|
|
273
|
+
reconnect("Stall");
|
|
192
274
|
}
|
|
193
275
|
}, 1000);
|
|
194
276
|
};
|
|
195
277
|
var onMSE = function (ms, codecString) {
|
|
196
278
|
if (!state.ms)
|
|
197
|
-
return;
|
|
279
|
+
return;
|
|
198
280
|
var sb = ms.addSourceBuffer(codecString);
|
|
199
281
|
sb.mode = "segments";
|
|
200
282
|
state.sbUpdateHandler = function () {
|
|
201
|
-
// 1. Append pending data
|
|
202
283
|
if (!sb.updating && state.buffer.length > 0) {
|
|
203
284
|
try {
|
|
204
285
|
var data = state.buffer.data.subarray(0, state.buffer.length);
|
|
@@ -209,7 +290,6 @@ var MSEVideoStream = function (_a) {
|
|
|
209
290
|
/* ignore */
|
|
210
291
|
}
|
|
211
292
|
}
|
|
212
|
-
// 2. Buffer management and smooth playback sync (VideoRTC logic)
|
|
213
293
|
if (!sb.updating &&
|
|
214
294
|
sb.buffered &&
|
|
215
295
|
sb.buffered.length > 0 &&
|
|
@@ -218,28 +298,20 @@ var MSEVideoStream = function (_a) {
|
|
|
218
298
|
var end = sb.buffered.end(sb.buffered.length - 1);
|
|
219
299
|
var start = end - 5;
|
|
220
300
|
var start0 = sb.buffered.start(0);
|
|
221
|
-
// Trim everything older than 5 seconds from the end
|
|
222
301
|
if (start > start0) {
|
|
223
302
|
try {
|
|
224
303
|
sb.remove(start0, start);
|
|
225
|
-
// Set live seekable range so the browser knows where we are
|
|
226
304
|
if (ms.setLiveSeekableRange) {
|
|
227
305
|
ms.setLiveSeekableRange(start, end);
|
|
228
306
|
}
|
|
229
307
|
}
|
|
230
308
|
catch (e) { }
|
|
231
309
|
}
|
|
232
|
-
// Jump forward if we fell behind the buffer window
|
|
233
310
|
if (video.currentTime < start) {
|
|
234
311
|
video.currentTime = start;
|
|
235
312
|
}
|
|
236
|
-
// Smooth playrate adjustment
|
|
237
313
|
var gap = end - video.currentTime;
|
|
238
|
-
// "gap > 0.1 ? gap : 0.1" logic from VideoRTC
|
|
239
|
-
// This effectively slows down playback if we are too close to end (to avoid stall)
|
|
240
|
-
// And speeds up playback to match gap if we are behind.
|
|
241
314
|
video.playbackRate = gap > 0.1 ? gap : 0.1;
|
|
242
|
-
// Ensure we are playing
|
|
243
315
|
if (video.paused && !video.ended && video.readyState > 2) {
|
|
244
316
|
video.play().catch(function () { });
|
|
245
317
|
}
|
|
@@ -248,7 +320,6 @@ var MSEVideoStream = function (_a) {
|
|
|
248
320
|
sb.addEventListener("updateend", state.sbUpdateHandler);
|
|
249
321
|
state.sb = sb;
|
|
250
322
|
updateStatus("streaming");
|
|
251
|
-
// Start checking for stalls once we have the source buffer set up
|
|
252
323
|
state.lastDataTime = Date.now();
|
|
253
324
|
startStalledCheck();
|
|
254
325
|
};
|
|
@@ -298,22 +369,27 @@ var MSEVideoStream = function (_a) {
|
|
|
298
369
|
videoRef.current.src = URL.createObjectURL(state.ms);
|
|
299
370
|
videoRef.current.srcObject = null;
|
|
300
371
|
}
|
|
301
|
-
// Ensure play is called
|
|
302
372
|
videoRef.current.play().catch(function () { });
|
|
303
373
|
}
|
|
304
374
|
};
|
|
305
375
|
var connect = function () {
|
|
306
376
|
updateStatus("connecting");
|
|
307
377
|
connectTSRef.current = Date.now();
|
|
308
|
-
state.lastDataTime = Date.now();
|
|
309
|
-
var wsURL = src;
|
|
310
|
-
if (
|
|
311
|
-
|
|
378
|
+
state.lastDataTime = Date.now();
|
|
379
|
+
var wsURL = toWsUrl(src);
|
|
380
|
+
if (debug)
|
|
381
|
+
console.log("[MSEVideoStream] Connecting to:", wsURL);
|
|
382
|
+
var ws;
|
|
383
|
+
try {
|
|
384
|
+
ws = new WebSocket(wsURL);
|
|
312
385
|
}
|
|
313
|
-
|
|
314
|
-
|
|
386
|
+
catch (err) {
|
|
387
|
+
if (debug)
|
|
388
|
+
console.error("[MSEVideoStream] WebSocket creation failed synchronously:", err);
|
|
389
|
+
updateError(err);
|
|
390
|
+
reconnect("WSCreationFail");
|
|
391
|
+
return;
|
|
315
392
|
}
|
|
316
|
-
var ws = new WebSocket(wsURL);
|
|
317
393
|
ws.binaryType = "arraybuffer";
|
|
318
394
|
state.ws = ws;
|
|
319
395
|
ws.onopen = function () {
|
|
@@ -328,24 +404,30 @@ var MSEVideoStream = function (_a) {
|
|
|
328
404
|
onMSE(state.ms, msg.value);
|
|
329
405
|
}
|
|
330
406
|
else if (msg.type === "error") {
|
|
331
|
-
|
|
407
|
+
if (debug)
|
|
408
|
+
console.warn("MSE Error:", msg.value);
|
|
332
409
|
updateError(msg.value);
|
|
333
|
-
reconnect();
|
|
410
|
+
reconnect("ServerMsgError");
|
|
334
411
|
}
|
|
335
412
|
}
|
|
336
413
|
else {
|
|
337
|
-
|
|
414
|
+
try {
|
|
415
|
+
appendData(ev.data);
|
|
416
|
+
}
|
|
417
|
+
catch (e) {
|
|
418
|
+
if (debug)
|
|
419
|
+
console.error("AppendData error", e);
|
|
420
|
+
}
|
|
338
421
|
}
|
|
339
422
|
};
|
|
340
423
|
ws.onclose = function () {
|
|
341
424
|
if (state.ws === ws) {
|
|
342
425
|
state.ws = null;
|
|
343
426
|
updateStatus("closed");
|
|
344
|
-
reconnect();
|
|
427
|
+
reconnect("WSClosed");
|
|
345
428
|
}
|
|
346
429
|
};
|
|
347
430
|
ws.onerror = function () {
|
|
348
|
-
// VideoRTC mostly handles onclose for reconnect logic
|
|
349
431
|
updateError("Connection failed");
|
|
350
432
|
};
|
|
351
433
|
};
|
|
@@ -362,38 +444,240 @@ var MSEVideoStream = function (_a) {
|
|
|
362
444
|
var isLoading = status === "connecting" ||
|
|
363
445
|
status === "reconnecting" ||
|
|
364
446
|
(status === "streaming" && !isPlaying);
|
|
365
|
-
return (React.createElement(
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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 }));
|
|
396
680
|
};
|
|
397
681
|
|
|
398
|
-
export { MSEVideoStream, MSEVideoStream as default };
|
|
682
|
+
export { MSEVideoStream, WebRTCVideoStream, MSEVideoStream as default };
|
|
399
683
|
//# sourceMappingURL=index.esm.js.map
|