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.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 ?
|
|
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
|
|
59
|
-
var
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
195
|
-
|
|
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;
|
|
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();
|
|
313
|
-
var wsURL = src;
|
|
314
|
-
if (
|
|
315
|
-
|
|
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
|
-
|
|
318
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
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
|