@livepeer-frameworks/player-core 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/core/ABRController.js +456 -0
- package/dist/cjs/core/ABRController.js.map +1 -0
- package/dist/cjs/core/CodecUtils.js +195 -0
- package/dist/cjs/core/CodecUtils.js.map +1 -0
- package/dist/cjs/core/ErrorClassifier.js +410 -0
- package/dist/cjs/core/ErrorClassifier.js.map +1 -0
- package/dist/cjs/core/EventEmitter.js +108 -0
- package/dist/cjs/core/EventEmitter.js.map +1 -0
- package/dist/cjs/core/GatewayClient.js +342 -0
- package/dist/cjs/core/GatewayClient.js.map +1 -0
- package/dist/cjs/core/InteractionController.js +606 -0
- package/dist/cjs/core/InteractionController.js.map +1 -0
- package/dist/cjs/core/LiveDurationProxy.js +186 -0
- package/dist/cjs/core/LiveDurationProxy.js.map +1 -0
- package/dist/cjs/core/MetaTrackManager.js +624 -0
- package/dist/cjs/core/MetaTrackManager.js.map +1 -0
- package/dist/cjs/core/MistReporter.js +449 -0
- package/dist/cjs/core/MistReporter.js.map +1 -0
- package/dist/cjs/core/MistSignaling.js +264 -0
- package/dist/cjs/core/MistSignaling.js.map +1 -0
- package/dist/cjs/core/PlayerController.js +2658 -0
- package/dist/cjs/core/PlayerController.js.map +1 -0
- package/dist/cjs/core/PlayerInterface.js +269 -0
- package/dist/cjs/core/PlayerInterface.js.map +1 -0
- package/dist/cjs/core/PlayerManager.js +806 -0
- package/dist/cjs/core/PlayerManager.js.map +1 -0
- package/dist/cjs/core/PlayerRegistry.js +270 -0
- package/dist/cjs/core/PlayerRegistry.js.map +1 -0
- package/dist/cjs/core/QualityMonitor.js +474 -0
- package/dist/cjs/core/QualityMonitor.js.map +1 -0
- package/dist/cjs/core/SeekingUtils.js +292 -0
- package/dist/cjs/core/SeekingUtils.js.map +1 -0
- package/dist/cjs/core/StreamStateClient.js +381 -0
- package/dist/cjs/core/StreamStateClient.js.map +1 -0
- package/dist/cjs/core/SubtitleManager.js +227 -0
- package/dist/cjs/core/SubtitleManager.js.map +1 -0
- package/dist/cjs/core/TelemetryReporter.js +258 -0
- package/dist/cjs/core/TelemetryReporter.js.map +1 -0
- package/dist/cjs/core/TimeFormat.js +176 -0
- package/dist/cjs/core/TimeFormat.js.map +1 -0
- package/dist/cjs/core/TimerManager.js +176 -0
- package/dist/cjs/core/TimerManager.js.map +1 -0
- package/dist/cjs/core/UrlUtils.js +160 -0
- package/dist/cjs/core/UrlUtils.js.map +1 -0
- package/dist/cjs/core/detector.js +293 -0
- package/dist/cjs/core/detector.js.map +1 -0
- package/dist/cjs/core/scorer.js +443 -0
- package/dist/cjs/core/scorer.js.map +1 -0
- package/dist/cjs/index.js +121 -20134
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/lib/utils.js +11 -0
- package/dist/cjs/lib/utils.js.map +1 -0
- package/dist/cjs/node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.js +6 -0
- package/dist/cjs/node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.js.map +1 -0
- package/dist/cjs/node_modules/.pnpm/tailwind-merge@3.4.0/node_modules/tailwind-merge/dist/bundle-mjs.js +3042 -0
- package/dist/cjs/node_modules/.pnpm/tailwind-merge@3.4.0/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -0
- package/dist/cjs/players/DashJsPlayer.js +638 -0
- package/dist/cjs/players/DashJsPlayer.js.map +1 -0
- package/dist/cjs/players/HlsJsPlayer.js +482 -0
- package/dist/cjs/players/HlsJsPlayer.js.map +1 -0
- package/dist/cjs/players/MewsWsPlayer/SourceBufferManager.js +522 -0
- package/dist/cjs/players/MewsWsPlayer/SourceBufferManager.js.map +1 -0
- package/dist/cjs/players/MewsWsPlayer/WebSocketManager.js +215 -0
- package/dist/cjs/players/MewsWsPlayer/WebSocketManager.js.map +1 -0
- package/dist/cjs/players/MewsWsPlayer/index.js +987 -0
- package/dist/cjs/players/MewsWsPlayer/index.js.map +1 -0
- package/dist/cjs/players/MistPlayer.js +185 -0
- package/dist/cjs/players/MistPlayer.js.map +1 -0
- package/dist/cjs/players/MistWebRTCPlayer/index.js +635 -0
- package/dist/cjs/players/MistWebRTCPlayer/index.js.map +1 -0
- package/dist/cjs/players/NativePlayer.js +762 -0
- package/dist/cjs/players/NativePlayer.js.map +1 -0
- package/dist/cjs/players/VideoJsPlayer.js +585 -0
- package/dist/cjs/players/VideoJsPlayer.js.map +1 -0
- package/dist/cjs/players/WebCodecsPlayer/JitterBuffer.js +236 -0
- package/dist/cjs/players/WebCodecsPlayer/JitterBuffer.js.map +1 -0
- package/dist/cjs/players/WebCodecsPlayer/LatencyProfiles.js +143 -0
- package/dist/cjs/players/WebCodecsPlayer/LatencyProfiles.js.map +1 -0
- package/dist/cjs/players/WebCodecsPlayer/RawChunkParser.js +96 -0
- package/dist/cjs/players/WebCodecsPlayer/RawChunkParser.js.map +1 -0
- package/dist/cjs/players/WebCodecsPlayer/SyncController.js +359 -0
- package/dist/cjs/players/WebCodecsPlayer/SyncController.js.map +1 -0
- package/dist/cjs/players/WebCodecsPlayer/WebSocketController.js +460 -0
- package/dist/cjs/players/WebCodecsPlayer/WebSocketController.js.map +1 -0
- package/dist/cjs/players/WebCodecsPlayer/index.js +1467 -0
- package/dist/cjs/players/WebCodecsPlayer/index.js.map +1 -0
- package/dist/cjs/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.js +320 -0
- package/dist/cjs/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.js.map +1 -0
- package/dist/cjs/styles/index.js +57 -0
- package/dist/cjs/styles/index.js.map +1 -0
- package/dist/cjs/vanilla/FrameWorksPlayer.js +269 -0
- package/dist/cjs/vanilla/FrameWorksPlayer.js.map +1 -0
- package/dist/cjs/vanilla.js +11 -0
- package/dist/cjs/vanilla.js.map +1 -0
- package/dist/esm/core/ABRController.js +454 -0
- package/dist/esm/core/ABRController.js.map +1 -0
- package/dist/esm/core/CodecUtils.js +193 -0
- package/dist/esm/core/CodecUtils.js.map +1 -0
- package/dist/esm/core/ErrorClassifier.js +408 -0
- package/dist/esm/core/ErrorClassifier.js.map +1 -0
- package/dist/esm/core/EventEmitter.js +106 -0
- package/dist/esm/core/EventEmitter.js.map +1 -0
- package/dist/esm/core/GatewayClient.js +340 -0
- package/dist/esm/core/GatewayClient.js.map +1 -0
- package/dist/esm/core/InteractionController.js +604 -0
- package/dist/esm/core/InteractionController.js.map +1 -0
- package/dist/esm/core/LiveDurationProxy.js +184 -0
- package/dist/esm/core/LiveDurationProxy.js.map +1 -0
- package/dist/esm/core/MetaTrackManager.js +622 -0
- package/dist/esm/core/MetaTrackManager.js.map +1 -0
- package/dist/esm/core/MistReporter.js +447 -0
- package/dist/esm/core/MistReporter.js.map +1 -0
- package/dist/esm/core/MistSignaling.js +262 -0
- package/dist/esm/core/MistSignaling.js.map +1 -0
- package/dist/esm/core/PlayerController.js +2651 -0
- package/dist/esm/core/PlayerController.js.map +1 -0
- package/dist/esm/core/PlayerInterface.js +267 -0
- package/dist/esm/core/PlayerInterface.js.map +1 -0
- package/dist/esm/core/PlayerManager.js +804 -0
- package/dist/esm/core/PlayerManager.js.map +1 -0
- package/dist/esm/core/PlayerRegistry.js +264 -0
- package/dist/esm/core/PlayerRegistry.js.map +1 -0
- package/dist/esm/core/QualityMonitor.js +471 -0
- package/dist/esm/core/QualityMonitor.js.map +1 -0
- package/dist/esm/core/SeekingUtils.js +280 -0
- package/dist/esm/core/SeekingUtils.js.map +1 -0
- package/dist/esm/core/StreamStateClient.js +379 -0
- package/dist/esm/core/StreamStateClient.js.map +1 -0
- package/dist/esm/core/SubtitleManager.js +225 -0
- package/dist/esm/core/SubtitleManager.js.map +1 -0
- package/dist/esm/core/TelemetryReporter.js +256 -0
- package/dist/esm/core/TelemetryReporter.js.map +1 -0
- package/dist/esm/core/TimeFormat.js +169 -0
- package/dist/esm/core/TimeFormat.js.map +1 -0
- package/dist/esm/core/TimerManager.js +174 -0
- package/dist/esm/core/TimerManager.js.map +1 -0
- package/dist/esm/core/UrlUtils.js +151 -0
- package/dist/esm/core/UrlUtils.js.map +1 -0
- package/dist/esm/core/detector.js +279 -0
- package/dist/esm/core/detector.js.map +1 -0
- package/dist/esm/core/scorer.js +422 -0
- package/dist/esm/core/scorer.js.map +1 -0
- package/dist/esm/index.js +26 -20043
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/lib/utils.js +9 -0
- package/dist/esm/lib/utils.js.map +1 -0
- package/dist/esm/node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.js +4 -0
- package/dist/esm/node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.js.map +1 -0
- package/dist/esm/node_modules/.pnpm/tailwind-merge@3.4.0/node_modules/tailwind-merge/dist/bundle-mjs.js +3036 -0
- package/dist/esm/node_modules/.pnpm/tailwind-merge@3.4.0/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -0
- package/dist/esm/players/DashJsPlayer.js +636 -0
- package/dist/esm/players/DashJsPlayer.js.map +1 -0
- package/dist/esm/players/HlsJsPlayer.js +480 -0
- package/dist/esm/players/HlsJsPlayer.js.map +1 -0
- package/dist/esm/players/MewsWsPlayer/SourceBufferManager.js +520 -0
- package/dist/esm/players/MewsWsPlayer/SourceBufferManager.js.map +1 -0
- package/dist/esm/players/MewsWsPlayer/WebSocketManager.js +213 -0
- package/dist/esm/players/MewsWsPlayer/WebSocketManager.js.map +1 -0
- package/dist/esm/players/MewsWsPlayer/index.js +985 -0
- package/dist/esm/players/MewsWsPlayer/index.js.map +1 -0
- package/dist/esm/players/MistPlayer.js +183 -0
- package/dist/esm/players/MistPlayer.js.map +1 -0
- package/dist/esm/players/MistWebRTCPlayer/index.js +633 -0
- package/dist/esm/players/MistWebRTCPlayer/index.js.map +1 -0
- package/dist/esm/players/NativePlayer.js +759 -0
- package/dist/esm/players/NativePlayer.js.map +1 -0
- package/dist/esm/players/VideoJsPlayer.js +583 -0
- package/dist/esm/players/VideoJsPlayer.js.map +1 -0
- package/dist/esm/players/WebCodecsPlayer/JitterBuffer.js +233 -0
- package/dist/esm/players/WebCodecsPlayer/JitterBuffer.js.map +1 -0
- package/dist/esm/players/WebCodecsPlayer/LatencyProfiles.js +134 -0
- package/dist/esm/players/WebCodecsPlayer/LatencyProfiles.js.map +1 -0
- package/dist/esm/players/WebCodecsPlayer/RawChunkParser.js +91 -0
- package/dist/esm/players/WebCodecsPlayer/RawChunkParser.js.map +1 -0
- package/dist/esm/players/WebCodecsPlayer/SyncController.js +357 -0
- package/dist/esm/players/WebCodecsPlayer/SyncController.js.map +1 -0
- package/dist/esm/players/WebCodecsPlayer/WebSocketController.js +458 -0
- package/dist/esm/players/WebCodecsPlayer/WebSocketController.js.map +1 -0
- package/dist/esm/players/WebCodecsPlayer/index.js +1458 -0
- package/dist/esm/players/WebCodecsPlayer/index.js.map +1 -0
- package/dist/esm/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.js +315 -0
- package/dist/esm/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.js.map +1 -0
- package/dist/esm/styles/index.js +54 -0
- package/dist/esm/styles/index.js.map +1 -0
- package/dist/esm/vanilla/FrameWorksPlayer.js +264 -0
- package/dist/esm/vanilla/FrameWorksPlayer.js.map +1 -0
- package/dist/esm/vanilla.js +2 -0
- package/dist/esm/vanilla.js.map +1 -0
- package/dist/player.css +4 -1
- package/dist/types/core/ABRController.d.ts +4 -4
- package/dist/types/core/CodecUtils.d.ts +1 -1
- package/dist/types/core/ErrorClassifier.d.ts +77 -0
- package/dist/types/core/GatewayClient.d.ts +4 -4
- package/dist/types/core/MetaTrackManager.d.ts +2 -2
- package/dist/types/core/MistReporter.d.ts +3 -3
- package/dist/types/core/MistSignaling.d.ts +12 -12
- package/dist/types/core/PlayerController.d.ts +19 -14
- package/dist/types/core/PlayerInterface.d.ts +100 -2
- package/dist/types/core/PlayerManager.d.ts +36 -9
- package/dist/types/core/PlayerRegistry.d.ts +11 -11
- package/dist/types/core/QualityMonitor.d.ts +2 -2
- package/dist/types/core/SeekingUtils.d.ts +2 -2
- package/dist/types/core/StreamStateClient.d.ts +2 -2
- package/dist/types/core/TelemetryReporter.d.ts +1 -1
- package/dist/types/core/TimerManager.d.ts +1 -1
- package/dist/types/core/detector.d.ts +1 -1
- package/dist/types/core/index.d.ts +44 -44
- package/dist/types/core/scorer.d.ts +1 -1
- package/dist/types/core/selector.d.ts +2 -2
- package/dist/types/index.d.ts +35 -34
- package/dist/types/players/DashJsPlayer.d.ts +3 -3
- package/dist/types/players/HlsJsPlayer.d.ts +3 -3
- package/dist/types/players/MewsWsPlayer/SourceBufferManager.d.ts +1 -1
- package/dist/types/players/MewsWsPlayer/WebSocketManager.d.ts +1 -1
- package/dist/types/players/MewsWsPlayer/index.d.ts +2 -2
- package/dist/types/players/MewsWsPlayer/types.d.ts +15 -15
- package/dist/types/players/MistPlayer.d.ts +2 -2
- package/dist/types/players/MistWebRTCPlayer/index.d.ts +3 -3
- package/dist/types/players/NativePlayer.d.ts +3 -3
- package/dist/types/players/VideoJsPlayer.d.ts +3 -3
- package/dist/types/players/WebCodecsPlayer/JitterBuffer.d.ts +3 -3
- package/dist/types/players/WebCodecsPlayer/LatencyProfiles.d.ts +1 -1
- package/dist/types/players/WebCodecsPlayer/RawChunkParser.d.ts +2 -2
- package/dist/types/players/WebCodecsPlayer/SyncController.d.ts +2 -2
- package/dist/types/players/WebCodecsPlayer/WebSocketController.d.ts +3 -3
- package/dist/types/players/WebCodecsPlayer/index.d.ts +9 -9
- package/dist/types/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.d.ts +1 -1
- package/dist/types/players/WebCodecsPlayer/types.d.ts +49 -49
- package/dist/types/players/WebCodecsPlayer/worker/types.d.ts +31 -31
- package/dist/types/players/index.d.ts +5 -8
- package/dist/types/types.d.ts +15 -15
- package/dist/types/vanilla/FrameWorksPlayer.d.ts +2 -2
- package/dist/types/vanilla/index.d.ts +4 -4
- package/dist/workers/decoder.worker.js +129 -122
- package/dist/workers/decoder.worker.js.map +1 -1
- package/package.json +31 -15
- package/src/core/ErrorClassifier.ts +499 -0
- package/src/core/PlayerController.ts +17 -2
- package/src/core/PlayerInterface.ts +109 -0
- package/src/core/PlayerManager.ts +290 -46
- package/src/core/PlayerRegistry.ts +221 -87
- package/src/core/TelemetryReporter.ts +4 -1
- package/src/index.ts +13 -4
- package/src/players/WebCodecsPlayer/index.ts +2 -2
- package/src/players/index.ts +5 -16
- package/src/styles/player.css +4 -1
- package/src/vanilla/FrameWorksPlayer.ts +2 -5
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TimerManager - Centralized timer management for memory leak prevention
|
|
3
|
+
*
|
|
4
|
+
* Tracks all setTimeout/setInterval calls and provides bulk cleanup.
|
|
5
|
+
* Based on MistMetaPlayer's MistVideo.timers pattern.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ```ts
|
|
9
|
+
* const timers = new TimerManager();
|
|
10
|
+
*
|
|
11
|
+
* // Start a timeout
|
|
12
|
+
* const id = timers.start(() => console.log('fired'), 1000);
|
|
13
|
+
*
|
|
14
|
+
* // Start an interval
|
|
15
|
+
* const intervalId = timers.startInterval(() => console.log('tick'), 500);
|
|
16
|
+
*
|
|
17
|
+
* // Stop a specific timer
|
|
18
|
+
* timers.stop(id);
|
|
19
|
+
*
|
|
20
|
+
* // Stop all timers (on cleanup/destroy)
|
|
21
|
+
* timers.stopAll();
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
class TimerManager {
|
|
25
|
+
constructor(options) {
|
|
26
|
+
this.timers = new Map();
|
|
27
|
+
this.nextId = 1;
|
|
28
|
+
this.debug = options?.debug ?? false;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Start a timeout
|
|
32
|
+
* @param callback Function to call after delay
|
|
33
|
+
* @param delay Delay in milliseconds
|
|
34
|
+
* @param label Optional label for debugging
|
|
35
|
+
* @returns Timer ID (internal, not the native timeout ID)
|
|
36
|
+
*/
|
|
37
|
+
start(callback, delay, label) {
|
|
38
|
+
const internalId = this.nextId++;
|
|
39
|
+
const endTime = Date.now() + delay;
|
|
40
|
+
const nativeId = setTimeout(() => {
|
|
41
|
+
this.timers.delete(internalId);
|
|
42
|
+
try {
|
|
43
|
+
callback();
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
console.error("[TimerManager] Callback error:", e);
|
|
47
|
+
}
|
|
48
|
+
}, delay);
|
|
49
|
+
this.timers.set(internalId, {
|
|
50
|
+
id: nativeId,
|
|
51
|
+
endTime,
|
|
52
|
+
isInterval: false,
|
|
53
|
+
label,
|
|
54
|
+
});
|
|
55
|
+
if (this.debug) {
|
|
56
|
+
console.debug(`[TimerManager] Started timeout ${internalId}${label ? ` (${label})` : ""} for ${delay}ms`);
|
|
57
|
+
}
|
|
58
|
+
return internalId;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Start an interval
|
|
62
|
+
* @param callback Function to call repeatedly
|
|
63
|
+
* @param interval Interval in milliseconds
|
|
64
|
+
* @param label Optional label for debugging
|
|
65
|
+
* @returns Timer ID (internal, not the native interval ID)
|
|
66
|
+
*/
|
|
67
|
+
startInterval(callback, interval, label) {
|
|
68
|
+
const internalId = this.nextId++;
|
|
69
|
+
const nativeId = setInterval(() => {
|
|
70
|
+
try {
|
|
71
|
+
callback();
|
|
72
|
+
}
|
|
73
|
+
catch (e) {
|
|
74
|
+
console.error("[TimerManager] Interval callback error:", e);
|
|
75
|
+
}
|
|
76
|
+
}, interval);
|
|
77
|
+
this.timers.set(internalId, {
|
|
78
|
+
id: nativeId,
|
|
79
|
+
endTime: Infinity, // Intervals don't have an end time
|
|
80
|
+
isInterval: true,
|
|
81
|
+
label,
|
|
82
|
+
});
|
|
83
|
+
if (this.debug) {
|
|
84
|
+
console.debug(`[TimerManager] Started interval ${internalId}${label ? ` (${label})` : ""} every ${interval}ms`);
|
|
85
|
+
}
|
|
86
|
+
return internalId;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Stop a specific timer
|
|
90
|
+
* @param internalId The timer ID returned by start() or startInterval()
|
|
91
|
+
*/
|
|
92
|
+
stop(internalId) {
|
|
93
|
+
const entry = this.timers.get(internalId);
|
|
94
|
+
if (!entry) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
if (entry.isInterval) {
|
|
98
|
+
clearInterval(entry.id);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
clearTimeout(entry.id);
|
|
102
|
+
}
|
|
103
|
+
this.timers.delete(internalId);
|
|
104
|
+
if (this.debug) {
|
|
105
|
+
console.debug(`[TimerManager] Stopped ${entry.isInterval ? "interval" : "timeout"} ${internalId}${entry.label ? ` (${entry.label})` : ""}`);
|
|
106
|
+
}
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Stop all active timers
|
|
111
|
+
* Call this on component unmount/destroy to prevent memory leaks
|
|
112
|
+
*/
|
|
113
|
+
stopAll() {
|
|
114
|
+
const count = this.timers.size;
|
|
115
|
+
for (const [_internalId, entry] of this.timers) {
|
|
116
|
+
if (entry.isInterval) {
|
|
117
|
+
clearInterval(entry.id);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
clearTimeout(entry.id);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
this.timers.clear();
|
|
124
|
+
if (this.debug && count > 0) {
|
|
125
|
+
console.debug(`[TimerManager] Stopped all ${count} timers`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Get count of active timers
|
|
130
|
+
*/
|
|
131
|
+
get activeCount() {
|
|
132
|
+
return this.timers.size;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Check if a timer is active
|
|
136
|
+
*/
|
|
137
|
+
isActive(internalId) {
|
|
138
|
+
return this.timers.has(internalId);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Get remaining time for a timeout (0 for intervals or expired)
|
|
142
|
+
*/
|
|
143
|
+
getRemainingTime(internalId) {
|
|
144
|
+
const entry = this.timers.get(internalId);
|
|
145
|
+
if (!entry || entry.isInterval) {
|
|
146
|
+
return 0;
|
|
147
|
+
}
|
|
148
|
+
return Math.max(0, entry.endTime - Date.now());
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Get debug info about all active timers
|
|
152
|
+
*/
|
|
153
|
+
getDebugInfo() {
|
|
154
|
+
const info = [];
|
|
155
|
+
for (const [internalId, entry] of this.timers) {
|
|
156
|
+
info.push({
|
|
157
|
+
id: internalId,
|
|
158
|
+
type: entry.isInterval ? "interval" : "timeout",
|
|
159
|
+
label: entry.label,
|
|
160
|
+
remainingMs: entry.isInterval ? undefined : Math.max(0, entry.endTime - Date.now()),
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
return info;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Cleanup - alias for stopAll()
|
|
167
|
+
*/
|
|
168
|
+
destroy() {
|
|
169
|
+
this.stopAll();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export { TimerManager };
|
|
174
|
+
//# sourceMappingURL=TimerManager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TimerManager.js","sources":["../../../../src/core/TimerManager.ts"],"sourcesContent":["/**\n * TimerManager - Centralized timer management for memory leak prevention\n *\n * Tracks all setTimeout/setInterval calls and provides bulk cleanup.\n * Based on MistMetaPlayer's MistVideo.timers pattern.\n *\n * Usage:\n * ```ts\n * const timers = new TimerManager();\n *\n * // Start a timeout\n * const id = timers.start(() => console.log('fired'), 1000);\n *\n * // Start an interval\n * const intervalId = timers.startInterval(() => console.log('tick'), 500);\n *\n * // Stop a specific timer\n * timers.stop(id);\n *\n * // Stop all timers (on cleanup/destroy)\n * timers.stopAll();\n * ```\n */\n\ninterface TimerEntry {\n /** Timer ID from setTimeout/setInterval */\n id: ReturnType<typeof setTimeout>;\n /** Expected end time (for timeouts) */\n endTime: number;\n /** Whether this is an interval */\n isInterval: boolean;\n /** Optional label for debugging */\n label?: string;\n}\n\nexport class TimerManager {\n private timers: Map<number, TimerEntry> = new Map();\n private nextId = 1;\n private debug: boolean;\n\n constructor(options?: { debug?: boolean }) {\n this.debug = options?.debug ?? false;\n }\n\n /**\n * Start a timeout\n * @param callback Function to call after delay\n * @param delay Delay in milliseconds\n * @param label Optional label for debugging\n * @returns Timer ID (internal, not the native timeout ID)\n */\n start(callback: () => void, delay: number, label?: string): number {\n const internalId = this.nextId++;\n const endTime = Date.now() + delay;\n\n const nativeId = setTimeout(() => {\n this.timers.delete(internalId);\n try {\n callback();\n } catch (e) {\n console.error(\"[TimerManager] Callback error:\", e);\n }\n }, delay);\n\n this.timers.set(internalId, {\n id: nativeId,\n endTime,\n isInterval: false,\n label,\n });\n\n if (this.debug) {\n console.debug(\n `[TimerManager] Started timeout ${internalId}${label ? ` (${label})` : \"\"} for ${delay}ms`\n );\n }\n\n return internalId;\n }\n\n /**\n * Start an interval\n * @param callback Function to call repeatedly\n * @param interval Interval in milliseconds\n * @param label Optional label for debugging\n * @returns Timer ID (internal, not the native interval ID)\n */\n startInterval(callback: () => void, interval: number, label?: string): number {\n const internalId = this.nextId++;\n\n const nativeId = setInterval(() => {\n try {\n callback();\n } catch (e) {\n console.error(\"[TimerManager] Interval callback error:\", e);\n }\n }, interval);\n\n this.timers.set(internalId, {\n id: nativeId,\n endTime: Infinity, // Intervals don't have an end time\n isInterval: true,\n label,\n });\n\n if (this.debug) {\n console.debug(\n `[TimerManager] Started interval ${internalId}${label ? ` (${label})` : \"\"} every ${interval}ms`\n );\n }\n\n return internalId;\n }\n\n /**\n * Stop a specific timer\n * @param internalId The timer ID returned by start() or startInterval()\n */\n stop(internalId: number): boolean {\n const entry = this.timers.get(internalId);\n if (!entry) {\n return false;\n }\n\n if (entry.isInterval) {\n clearInterval(entry.id);\n } else {\n clearTimeout(entry.id);\n }\n\n this.timers.delete(internalId);\n\n if (this.debug) {\n console.debug(\n `[TimerManager] Stopped ${entry.isInterval ? \"interval\" : \"timeout\"} ${internalId}${entry.label ? ` (${entry.label})` : \"\"}`\n );\n }\n\n return true;\n }\n\n /**\n * Stop all active timers\n * Call this on component unmount/destroy to prevent memory leaks\n */\n stopAll(): void {\n const count = this.timers.size;\n\n for (const [_internalId, entry] of this.timers) {\n if (entry.isInterval) {\n clearInterval(entry.id);\n } else {\n clearTimeout(entry.id);\n }\n }\n\n this.timers.clear();\n\n if (this.debug && count > 0) {\n console.debug(`[TimerManager] Stopped all ${count} timers`);\n }\n }\n\n /**\n * Get count of active timers\n */\n get activeCount(): number {\n return this.timers.size;\n }\n\n /**\n * Check if a timer is active\n */\n isActive(internalId: number): boolean {\n return this.timers.has(internalId);\n }\n\n /**\n * Get remaining time for a timeout (0 for intervals or expired)\n */\n getRemainingTime(internalId: number): number {\n const entry = this.timers.get(internalId);\n if (!entry || entry.isInterval) {\n return 0;\n }\n return Math.max(0, entry.endTime - Date.now());\n }\n\n /**\n * Get debug info about all active timers\n */\n getDebugInfo(): Array<{\n id: number;\n type: \"timeout\" | \"interval\";\n label?: string;\n remainingMs?: number;\n }> {\n const info: Array<{\n id: number;\n type: \"timeout\" | \"interval\";\n label?: string;\n remainingMs?: number;\n }> = [];\n\n for (const [internalId, entry] of this.timers) {\n info.push({\n id: internalId,\n type: entry.isInterval ? \"interval\" : \"timeout\",\n label: entry.label,\n remainingMs: entry.isInterval ? undefined : Math.max(0, entry.endTime - Date.now()),\n });\n }\n\n return info;\n }\n\n /**\n * Cleanup - alias for stopAll()\n */\n destroy(): void {\n this.stopAll();\n }\n}\n\nexport default TimerManager;\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UrlUtils - URL manipulation utilities
|
|
3
|
+
*
|
|
4
|
+
* Based on MistMetaPlayer's urlappend functionality.
|
|
5
|
+
* Provides helpers for appending query parameters to URLs.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Append query parameters to a URL
|
|
9
|
+
* Handles URLs that already have query parameters
|
|
10
|
+
*
|
|
11
|
+
* @param url - Base URL
|
|
12
|
+
* @param params - Parameters to append (string or object)
|
|
13
|
+
* @returns URL with appended parameters
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* appendUrlParams('https://example.com/video.m3u8', 'token=abc&session=123')
|
|
18
|
+
* // => 'https://example.com/video.m3u8?token=abc&session=123'
|
|
19
|
+
*
|
|
20
|
+
* appendUrlParams('https://example.com/video.m3u8?existing=param', 'token=abc')
|
|
21
|
+
* // => 'https://example.com/video.m3u8?existing=param&token=abc'
|
|
22
|
+
*
|
|
23
|
+
* appendUrlParams('https://example.com/video.m3u8', { token: 'abc', session: '123' })
|
|
24
|
+
* // => 'https://example.com/video.m3u8?token=abc&session=123'
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
function appendUrlParams(url, params) {
|
|
28
|
+
if (!params) {
|
|
29
|
+
return url;
|
|
30
|
+
}
|
|
31
|
+
// Convert object to query string
|
|
32
|
+
let queryString;
|
|
33
|
+
if (typeof params === "object") {
|
|
34
|
+
const entries = Object.entries(params)
|
|
35
|
+
.filter(([, value]) => value !== undefined && value !== null)
|
|
36
|
+
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
|
|
37
|
+
if (entries.length === 0) {
|
|
38
|
+
return url;
|
|
39
|
+
}
|
|
40
|
+
queryString = entries.join("&");
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
queryString = params;
|
|
44
|
+
// Strip leading ? or & if present
|
|
45
|
+
if (queryString.startsWith("?") || queryString.startsWith("&")) {
|
|
46
|
+
queryString = queryString.slice(1);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (!queryString) {
|
|
50
|
+
return url;
|
|
51
|
+
}
|
|
52
|
+
// Determine separator (? or &)
|
|
53
|
+
const separator = url.includes("?") ? "&" : "?";
|
|
54
|
+
return `${url}${separator}${queryString}`;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Parse query parameters from a URL
|
|
58
|
+
*
|
|
59
|
+
* @param url - URL to parse
|
|
60
|
+
* @returns Object with query parameters
|
|
61
|
+
*/
|
|
62
|
+
function parseUrlParams(url) {
|
|
63
|
+
const params = {};
|
|
64
|
+
try {
|
|
65
|
+
const urlObj = new URL(url);
|
|
66
|
+
urlObj.searchParams.forEach((value, key) => {
|
|
67
|
+
params[key] = value;
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// If URL parsing fails, try manual parsing
|
|
72
|
+
const queryIndex = url.indexOf("?");
|
|
73
|
+
if (queryIndex === -1) {
|
|
74
|
+
return params;
|
|
75
|
+
}
|
|
76
|
+
const queryString = url.slice(queryIndex + 1);
|
|
77
|
+
const pairs = queryString.split("&");
|
|
78
|
+
for (const pair of pairs) {
|
|
79
|
+
const [key, value] = pair.split("=");
|
|
80
|
+
if (key) {
|
|
81
|
+
params[decodeURIComponent(key)] = value ? decodeURIComponent(value) : "";
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return params;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Remove query parameters from a URL
|
|
89
|
+
*
|
|
90
|
+
* @param url - URL to strip
|
|
91
|
+
* @returns URL without query parameters
|
|
92
|
+
*/
|
|
93
|
+
function stripUrlParams(url) {
|
|
94
|
+
const queryIndex = url.indexOf("?");
|
|
95
|
+
return queryIndex === -1 ? url : url.slice(0, queryIndex);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Build a URL with query parameters
|
|
99
|
+
*
|
|
100
|
+
* @param baseUrl - Base URL
|
|
101
|
+
* @param params - Query parameters
|
|
102
|
+
* @returns Complete URL
|
|
103
|
+
*/
|
|
104
|
+
function buildUrl(baseUrl, params) {
|
|
105
|
+
return appendUrlParams(stripUrlParams(baseUrl), params);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Check if URL uses secure protocol (https/wss)
|
|
109
|
+
*/
|
|
110
|
+
function isSecureUrl(url) {
|
|
111
|
+
return url.startsWith("https://") || url.startsWith("wss://");
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Convert HTTP URL to WebSocket URL
|
|
115
|
+
* http:// -> ws://
|
|
116
|
+
* https:// -> wss://
|
|
117
|
+
*/
|
|
118
|
+
function httpToWs(url) {
|
|
119
|
+
return url.replace(/^http/, "ws");
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Convert WebSocket URL to HTTP URL
|
|
123
|
+
* ws:// -> http://
|
|
124
|
+
* wss:// -> https://
|
|
125
|
+
*/
|
|
126
|
+
function wsToHttp(url) {
|
|
127
|
+
return url.replace(/^ws/, "http");
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Ensure URL uses the same protocol as the current page
|
|
131
|
+
* Useful for avoiding mixed content issues
|
|
132
|
+
*/
|
|
133
|
+
function matchPageProtocol(url) {
|
|
134
|
+
if (typeof window === "undefined") {
|
|
135
|
+
return url;
|
|
136
|
+
}
|
|
137
|
+
const pageIsSecure = window.location.protocol === "https:";
|
|
138
|
+
const urlIsSecure = isSecureUrl(url);
|
|
139
|
+
if (pageIsSecure && !urlIsSecure) {
|
|
140
|
+
// Upgrade to secure
|
|
141
|
+
return url.replace(/^http:/, "https:").replace(/^ws:/, "wss:");
|
|
142
|
+
}
|
|
143
|
+
if (!pageIsSecure && urlIsSecure) {
|
|
144
|
+
// Downgrade to insecure (not recommended, but avoids issues)
|
|
145
|
+
return url.replace(/^https:/, "http:").replace(/^wss:/, "ws:");
|
|
146
|
+
}
|
|
147
|
+
return url;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export { appendUrlParams, buildUrl, httpToWs, isSecureUrl, matchPageProtocol, parseUrlParams, stripUrlParams, wsToHttp };
|
|
151
|
+
//# sourceMappingURL=UrlUtils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UrlUtils.js","sources":["../../../../src/core/UrlUtils.ts"],"sourcesContent":["/**\n * UrlUtils - URL manipulation utilities\n *\n * Based on MistMetaPlayer's urlappend functionality.\n * Provides helpers for appending query parameters to URLs.\n */\n\n/**\n * Append query parameters to a URL\n * Handles URLs that already have query parameters\n *\n * @param url - Base URL\n * @param params - Parameters to append (string or object)\n * @returns URL with appended parameters\n *\n * @example\n * ```ts\n * appendUrlParams('https://example.com/video.m3u8', 'token=abc&session=123')\n * // => 'https://example.com/video.m3u8?token=abc&session=123'\n *\n * appendUrlParams('https://example.com/video.m3u8?existing=param', 'token=abc')\n * // => 'https://example.com/video.m3u8?existing=param&token=abc'\n *\n * appendUrlParams('https://example.com/video.m3u8', { token: 'abc', session: '123' })\n * // => 'https://example.com/video.m3u8?token=abc&session=123'\n * ```\n */\nexport function appendUrlParams(\n url: string,\n params: string | Record<string, string | number | boolean | undefined | null>\n): string {\n if (!params) {\n return url;\n }\n\n // Convert object to query string\n let queryString: string;\n if (typeof params === \"object\") {\n const entries = Object.entries(params)\n .filter(([, value]) => value !== undefined && value !== null)\n .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);\n\n if (entries.length === 0) {\n return url;\n }\n queryString = entries.join(\"&\");\n } else {\n queryString = params;\n // Strip leading ? or & if present\n if (queryString.startsWith(\"?\") || queryString.startsWith(\"&\")) {\n queryString = queryString.slice(1);\n }\n }\n\n if (!queryString) {\n return url;\n }\n\n // Determine separator (? or &)\n const separator = url.includes(\"?\") ? \"&\" : \"?\";\n return `${url}${separator}${queryString}`;\n}\n\n/**\n * Parse query parameters from a URL\n *\n * @param url - URL to parse\n * @returns Object with query parameters\n */\nexport function parseUrlParams(url: string): Record<string, string> {\n const params: Record<string, string> = {};\n\n try {\n const urlObj = new URL(url);\n urlObj.searchParams.forEach((value, key) => {\n params[key] = value;\n });\n } catch {\n // If URL parsing fails, try manual parsing\n const queryIndex = url.indexOf(\"?\");\n if (queryIndex === -1) {\n return params;\n }\n\n const queryString = url.slice(queryIndex + 1);\n const pairs = queryString.split(\"&\");\n for (const pair of pairs) {\n const [key, value] = pair.split(\"=\");\n if (key) {\n params[decodeURIComponent(key)] = value ? decodeURIComponent(value) : \"\";\n }\n }\n }\n\n return params;\n}\n\n/**\n * Remove query parameters from a URL\n *\n * @param url - URL to strip\n * @returns URL without query parameters\n */\nexport function stripUrlParams(url: string): string {\n const queryIndex = url.indexOf(\"?\");\n return queryIndex === -1 ? url : url.slice(0, queryIndex);\n}\n\n/**\n * Build a URL with query parameters\n *\n * @param baseUrl - Base URL\n * @param params - Query parameters\n * @returns Complete URL\n */\nexport function buildUrl(\n baseUrl: string,\n params: Record<string, string | number | boolean | undefined | null>\n): string {\n return appendUrlParams(stripUrlParams(baseUrl), params);\n}\n\n/**\n * Check if URL uses secure protocol (https/wss)\n */\nexport function isSecureUrl(url: string): boolean {\n return url.startsWith(\"https://\") || url.startsWith(\"wss://\");\n}\n\n/**\n * Convert HTTP URL to WebSocket URL\n * http:// -> ws://\n * https:// -> wss://\n */\nexport function httpToWs(url: string): string {\n return url.replace(/^http/, \"ws\");\n}\n\n/**\n * Convert WebSocket URL to HTTP URL\n * ws:// -> http://\n * wss:// -> https://\n */\nexport function wsToHttp(url: string): string {\n return url.replace(/^ws/, \"http\");\n}\n\n/**\n * Ensure URL uses the same protocol as the current page\n * Useful for avoiding mixed content issues\n */\nexport function matchPageProtocol(url: string): string {\n if (typeof window === \"undefined\") {\n return url;\n }\n\n const pageIsSecure = window.location.protocol === \"https:\";\n const urlIsSecure = isSecureUrl(url);\n\n if (pageIsSecure && !urlIsSecure) {\n // Upgrade to secure\n return url.replace(/^http:/, \"https:\").replace(/^ws:/, \"wss:\");\n }\n\n if (!pageIsSecure && urlIsSecure) {\n // Downgrade to insecure (not recommended, but avoids issues)\n return url.replace(/^https:/, \"http:\").replace(/^wss:/, \"ws:\");\n }\n\n return url;\n}\n\nexport default {\n appendUrlParams,\n parseUrlParams,\n stripUrlParams,\n buildUrl,\n isSecureUrl,\n httpToWs,\n wsToHttp,\n matchPageProtocol,\n};\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { translateCodec } from './CodecUtils.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Browser and Codec Detection
|
|
5
|
+
* Ported from MistMetaPlayer v3.1.0
|
|
6
|
+
*
|
|
7
|
+
* Detects browser capabilities and codec support
|
|
8
|
+
* Removes legacy IE/Flash detection code
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Detect browser information
|
|
12
|
+
*/
|
|
13
|
+
function getBrowserInfo() {
|
|
14
|
+
const ua = navigator.userAgent.toLowerCase();
|
|
15
|
+
return {
|
|
16
|
+
isChrome: /chrome|crios/.test(ua) && !/edge|edg/.test(ua),
|
|
17
|
+
isFirefox: /firefox/.test(ua),
|
|
18
|
+
isSafari: /safari/.test(ua) && !/chrome|crios/.test(ua),
|
|
19
|
+
isEdge: /edge|edg/.test(ua),
|
|
20
|
+
isAndroid: /android/.test(ua),
|
|
21
|
+
isIOS: /iphone|ipad|ipod/.test(ua),
|
|
22
|
+
isMobile: /mobile|android|iphone|ipad|ipod/.test(ua),
|
|
23
|
+
supportsMediaSource: "MediaSource" in window,
|
|
24
|
+
supportsWebRTC: "RTCPeerConnection" in window,
|
|
25
|
+
supportsWebSocket: "WebSocket" in window,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Test codec support using MediaSource API
|
|
30
|
+
*/
|
|
31
|
+
function testCodecSupport(mimeType, codec) {
|
|
32
|
+
if (!("MediaSource" in window)) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
if (!MediaSource.isTypeSupported) {
|
|
36
|
+
return true; // Can't test, assume it works
|
|
37
|
+
}
|
|
38
|
+
const fullType = `${mimeType};codecs="${codec}"`;
|
|
39
|
+
return MediaSource.isTypeSupported(fullType);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Get comprehensive codec support info
|
|
43
|
+
*/
|
|
44
|
+
function getCodecSupport() {
|
|
45
|
+
return {
|
|
46
|
+
h264: testCodecSupport("video/mp4", "avc1.42E01E"),
|
|
47
|
+
h265: testCodecSupport("video/mp4", "hev1.1.6.L93.90"),
|
|
48
|
+
vp8: testCodecSupport("video/webm", "vp8"),
|
|
49
|
+
vp9: testCodecSupport("video/webm", "vp09.00.10.08"),
|
|
50
|
+
av1: testCodecSupport("video/mp4", "av01.0.04M.08"),
|
|
51
|
+
aac: testCodecSupport("video/mp4", "mp4a.40.2"),
|
|
52
|
+
mp3: testCodecSupport("audio/mpeg", "mp3"),
|
|
53
|
+
opus: testCodecSupport("audio/webm", "opus"),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Check if tracks are playable by testing codecs
|
|
58
|
+
*/
|
|
59
|
+
function checkTrackPlayability(tracks, containerType) {
|
|
60
|
+
const playable = [];
|
|
61
|
+
const supported = [];
|
|
62
|
+
const tracksByType = {};
|
|
63
|
+
// Group tracks by type
|
|
64
|
+
for (const track of tracks) {
|
|
65
|
+
if (track.type === "meta")
|
|
66
|
+
continue;
|
|
67
|
+
if (!tracksByType[track.type]) {
|
|
68
|
+
tracksByType[track.type] = [];
|
|
69
|
+
}
|
|
70
|
+
tracksByType[track.type].push(track);
|
|
71
|
+
}
|
|
72
|
+
// Test each track type
|
|
73
|
+
for (const [trackType, typeTracks] of Object.entries(tracksByType)) {
|
|
74
|
+
let hasPlayableTrack = false;
|
|
75
|
+
for (const track of typeTracks) {
|
|
76
|
+
const codecString = translateCodec(track);
|
|
77
|
+
if (testCodecSupport(containerType, codecString)) {
|
|
78
|
+
supported.push(track.codec);
|
|
79
|
+
hasPlayableTrack = true;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (hasPlayableTrack) {
|
|
83
|
+
playable.push(trackType);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return { playable, supported };
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Check protocol/scheme mismatch (http/https)
|
|
90
|
+
*/
|
|
91
|
+
function checkProtocolMismatch(sourceUrl) {
|
|
92
|
+
const pageProtocol = window.location.protocol;
|
|
93
|
+
const sourceProtocol = new URL(sourceUrl).protocol;
|
|
94
|
+
// Allow file:// to access http://
|
|
95
|
+
if (pageProtocol === "file:" && sourceProtocol === "http:") {
|
|
96
|
+
return false; // No mismatch
|
|
97
|
+
}
|
|
98
|
+
return pageProtocol !== sourceProtocol;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Check if current page is loaded over file://
|
|
102
|
+
*/
|
|
103
|
+
function isFileProtocol() {
|
|
104
|
+
return window.location.protocol === "file:";
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Get Android version (returns null if not Android)
|
|
108
|
+
*/
|
|
109
|
+
function getAndroidVersion() {
|
|
110
|
+
const match = navigator.userAgent.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i);
|
|
111
|
+
if (!match)
|
|
112
|
+
return null;
|
|
113
|
+
const major = parseInt(match[1], 10);
|
|
114
|
+
const minor = match[2] ? parseInt(match[2], 10) : 0;
|
|
115
|
+
return major + minor / 10;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Browser-specific compatibility checks
|
|
119
|
+
*/
|
|
120
|
+
function getBrowserCompatibility() {
|
|
121
|
+
const browser = getBrowserInfo();
|
|
122
|
+
const android = getAndroidVersion();
|
|
123
|
+
return {
|
|
124
|
+
// Native HLS support
|
|
125
|
+
supportsNativeHLS: browser.isSafari || browser.isIOS || (android && android >= 7),
|
|
126
|
+
// MSE support
|
|
127
|
+
supportsMSE: browser.supportsMediaSource,
|
|
128
|
+
// WebSocket support
|
|
129
|
+
supportsWebSocket: browser.supportsWebSocket,
|
|
130
|
+
// WebRTC support
|
|
131
|
+
supportsWebRTC: browser.supportsWebRTC && "RTCRtpReceiver" in window,
|
|
132
|
+
// Specific player recommendations
|
|
133
|
+
preferVideoJs: android && android < 7, // VideoJS better for older Android
|
|
134
|
+
avoidMEWSOnMac: browser.isSafari, // MEWS breaks often on Safari/macOS
|
|
135
|
+
// File protocol limitations
|
|
136
|
+
fileProtocolLimitations: isFileProtocol(),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
// ============================================================================
|
|
140
|
+
// WebRTC Codec Compatibility
|
|
141
|
+
// ============================================================================
|
|
142
|
+
/**
|
|
143
|
+
* Codecs that are compatible with WebRTC (WHEP/MistServer native)
|
|
144
|
+
* These are the only codecs that can be used in RTP streams
|
|
145
|
+
*/
|
|
146
|
+
const WEBRTC_COMPATIBLE_CODECS = {
|
|
147
|
+
video: ["H264", "VP8", "VP9", "AV1"],
|
|
148
|
+
// Note: AAC is NOT natively supported in browser WebRTC - OPUS is standard
|
|
149
|
+
// MistServer may transcode to OPUS for WebRTC output
|
|
150
|
+
audio: ["OPUS", "PCMU", "PCMA", "G711", "G722"],
|
|
151
|
+
};
|
|
152
|
+
/**
|
|
153
|
+
* Codecs that are explicitly incompatible with WebRTC
|
|
154
|
+
*/
|
|
155
|
+
const WEBRTC_INCOMPATIBLE_CODECS = {
|
|
156
|
+
video: ["HEVC", "H265", "THEORA", "MPEG2"],
|
|
157
|
+
audio: ["AC3", "EAC3", "EC3", "MP3", "FLAC", "VORBIS", "DTS"],
|
|
158
|
+
};
|
|
159
|
+
/**
|
|
160
|
+
* Check if browser supports a codec via WebRTC
|
|
161
|
+
* Uses RTCRtpReceiver.getCapabilities() for dynamic browser detection (reference: webrtc.js:38-46)
|
|
162
|
+
* Falls back to static list if API unavailable
|
|
163
|
+
*/
|
|
164
|
+
function isBrowserWebRTCCodecSupported(type, codec) {
|
|
165
|
+
// Try dynamic browser API first (reference: webrtc.js:39)
|
|
166
|
+
if (typeof RTCRtpReceiver !== "undefined" && RTCRtpReceiver.getCapabilities) {
|
|
167
|
+
try {
|
|
168
|
+
const capabilities = RTCRtpReceiver.getCapabilities(type);
|
|
169
|
+
if (capabilities?.codecs) {
|
|
170
|
+
const targetMime = `${type}/${codec}`.toLowerCase();
|
|
171
|
+
return capabilities.codecs.some((c) => c.mimeType.toLowerCase() === targetMime);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
// Fall through to static list
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// Fallback to static list for older browsers/SSR
|
|
179
|
+
const compatibleCodecs = WEBRTC_COMPATIBLE_CODECS[type];
|
|
180
|
+
return compatibleCodecs.includes(codec.toUpperCase());
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Check if stream tracks are compatible with WebRTC playback
|
|
184
|
+
*
|
|
185
|
+
* Uses RTCRtpReceiver.getCapabilities() to dynamically query browser support,
|
|
186
|
+
* matching the reference MistServer implementation (webrtc.js:38-46).
|
|
187
|
+
*
|
|
188
|
+
* @param tracks - Array of track metadata from MistServer
|
|
189
|
+
* @returns Compatibility assessment
|
|
190
|
+
*/
|
|
191
|
+
function checkWebRTCCodecCompatibility(tracks) {
|
|
192
|
+
const videoTracks = tracks.filter((t) => t.type === "video");
|
|
193
|
+
const audioTracks = tracks.filter((t) => t.type === "audio");
|
|
194
|
+
const compatibleVideoCodecs = [];
|
|
195
|
+
const compatibleAudioCodecs = [];
|
|
196
|
+
const incompatibleCodecs = [];
|
|
197
|
+
// Check video tracks using dynamic browser detection
|
|
198
|
+
for (const track of videoTracks) {
|
|
199
|
+
if (isBrowserWebRTCCodecSupported("video", track.codec)) {
|
|
200
|
+
compatibleVideoCodecs.push(track.codec);
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
incompatibleCodecs.push(`video:${track.codec}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// Check audio tracks using dynamic browser detection
|
|
207
|
+
for (const track of audioTracks) {
|
|
208
|
+
if (isBrowserWebRTCCodecSupported("audio", track.codec)) {
|
|
209
|
+
compatibleAudioCodecs.push(track.codec);
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
incompatibleCodecs.push(`audio:${track.codec}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// Video is compatible if there's at least one compatible video codec
|
|
216
|
+
// (or no video tracks at all - audio-only streams are fine)
|
|
217
|
+
const videoCompatible = videoTracks.length === 0 || compatibleVideoCodecs.length > 0;
|
|
218
|
+
// Audio is compatible if there's at least one compatible audio codec
|
|
219
|
+
// (or no audio tracks at all - video-only streams are fine)
|
|
220
|
+
const audioCompatible = audioTracks.length === 0 || compatibleAudioCodecs.length > 0;
|
|
221
|
+
return {
|
|
222
|
+
videoCompatible,
|
|
223
|
+
audioCompatible,
|
|
224
|
+
compatible: videoCompatible && audioCompatible,
|
|
225
|
+
incompatibleCodecs,
|
|
226
|
+
details: {
|
|
227
|
+
videoTracks: videoTracks.length,
|
|
228
|
+
audioTracks: audioTracks.length,
|
|
229
|
+
compatibleVideoCodecs,
|
|
230
|
+
compatibleAudioCodecs,
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Check if stream tracks are compatible with MSE playback in this browser
|
|
236
|
+
*
|
|
237
|
+
* Unlike WebRTC, MSE compatibility varies by browser. HEVC works in Safari
|
|
238
|
+
* but not Firefox. This function actually tests MediaSource.isTypeSupported().
|
|
239
|
+
*
|
|
240
|
+
* @param tracks - Array of track metadata
|
|
241
|
+
* @param containerType - MIME type (e.g., 'video/mp4')
|
|
242
|
+
*/
|
|
243
|
+
function checkMSECodecCompatibility(tracks, containerType = "video/mp4") {
|
|
244
|
+
const videoTracks = tracks.filter((t) => t.type === "video");
|
|
245
|
+
const audioTracks = tracks.filter((t) => t.type === "audio");
|
|
246
|
+
const unsupportedCodecs = [];
|
|
247
|
+
let hasCompatibleVideo = videoTracks.length === 0;
|
|
248
|
+
let hasCompatibleAudio = audioTracks.length === 0;
|
|
249
|
+
// Test each video track
|
|
250
|
+
for (const track of videoTracks) {
|
|
251
|
+
const codecString = translateCodec(track);
|
|
252
|
+
if (testCodecSupport(containerType, codecString)) {
|
|
253
|
+
hasCompatibleVideo = true;
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
unsupportedCodecs.push(`video:${track.codec}`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
// Test each audio track
|
|
260
|
+
for (const track of audioTracks) {
|
|
261
|
+
const codecString = translateCodec(track);
|
|
262
|
+
const audioContainer = track.codec === "MP3" ? "audio/mpeg" : containerType;
|
|
263
|
+
if (testCodecSupport(audioContainer, codecString)) {
|
|
264
|
+
hasCompatibleAudio = true;
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
unsupportedCodecs.push(`audio:${track.codec}`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return {
|
|
271
|
+
videoCompatible: hasCompatibleVideo,
|
|
272
|
+
audioCompatible: hasCompatibleAudio,
|
|
273
|
+
compatible: hasCompatibleVideo && hasCompatibleAudio,
|
|
274
|
+
unsupportedCodecs,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export { WEBRTC_COMPATIBLE_CODECS, WEBRTC_INCOMPATIBLE_CODECS, checkMSECodecCompatibility, checkProtocolMismatch, checkTrackPlayability, checkWebRTCCodecCompatibility, getAndroidVersion, getBrowserCompatibility, getBrowserInfo, getCodecSupport, isFileProtocol, testCodecSupport, translateCodec };
|
|
279
|
+
//# sourceMappingURL=detector.js.map
|