@livepeer-frameworks/player-core 0.0.3
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/index.js +19493 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/esm/index.js +19398 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/player.css +2140 -0
- package/dist/types/core/ABRController.d.ts +164 -0
- package/dist/types/core/CodecUtils.d.ts +54 -0
- package/dist/types/core/Disposable.d.ts +61 -0
- package/dist/types/core/EventEmitter.d.ts +73 -0
- package/dist/types/core/GatewayClient.d.ts +144 -0
- package/dist/types/core/InteractionController.d.ts +121 -0
- package/dist/types/core/LiveDurationProxy.d.ts +102 -0
- package/dist/types/core/MetaTrackManager.d.ts +220 -0
- package/dist/types/core/MistReporter.d.ts +163 -0
- package/dist/types/core/MistSignaling.d.ts +148 -0
- package/dist/types/core/PlayerController.d.ts +665 -0
- package/dist/types/core/PlayerInterface.d.ts +230 -0
- package/dist/types/core/PlayerManager.d.ts +182 -0
- package/dist/types/core/PlayerRegistry.d.ts +27 -0
- package/dist/types/core/QualityMonitor.d.ts +184 -0
- package/dist/types/core/ScreenWakeLockManager.d.ts +70 -0
- package/dist/types/core/SeekingUtils.d.ts +142 -0
- package/dist/types/core/StreamStateClient.d.ts +108 -0
- package/dist/types/core/SubtitleManager.d.ts +111 -0
- package/dist/types/core/TelemetryReporter.d.ts +79 -0
- package/dist/types/core/TimeFormat.d.ts +97 -0
- package/dist/types/core/TimerManager.d.ts +83 -0
- package/dist/types/core/UrlUtils.d.ts +81 -0
- package/dist/types/core/detector.d.ts +149 -0
- package/dist/types/core/index.d.ts +49 -0
- package/dist/types/core/scorer.d.ts +167 -0
- package/dist/types/core/selector.d.ts +9 -0
- package/dist/types/index.d.ts +45 -0
- package/dist/types/lib/utils.d.ts +2 -0
- package/dist/types/players/DashJsPlayer.d.ts +102 -0
- package/dist/types/players/HlsJsPlayer.d.ts +70 -0
- package/dist/types/players/MewsWsPlayer/SourceBufferManager.d.ts +119 -0
- package/dist/types/players/MewsWsPlayer/WebSocketManager.d.ts +60 -0
- package/dist/types/players/MewsWsPlayer/index.d.ts +220 -0
- package/dist/types/players/MewsWsPlayer/types.d.ts +89 -0
- package/dist/types/players/MistPlayer.d.ts +25 -0
- package/dist/types/players/MistWebRTCPlayer/index.d.ts +133 -0
- package/dist/types/players/NativePlayer.d.ts +143 -0
- package/dist/types/players/VideoJsPlayer.d.ts +59 -0
- package/dist/types/players/WebCodecsPlayer/JitterBuffer.d.ts +118 -0
- package/dist/types/players/WebCodecsPlayer/LatencyProfiles.d.ts +64 -0
- package/dist/types/players/WebCodecsPlayer/RawChunkParser.d.ts +63 -0
- package/dist/types/players/WebCodecsPlayer/SyncController.d.ts +174 -0
- package/dist/types/players/WebCodecsPlayer/WebSocketController.d.ts +164 -0
- package/dist/types/players/WebCodecsPlayer/index.d.ts +149 -0
- package/dist/types/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.d.ts +105 -0
- package/dist/types/players/WebCodecsPlayer/types.d.ts +395 -0
- package/dist/types/players/WebCodecsPlayer/worker/decoder.worker.d.ts +13 -0
- package/dist/types/players/WebCodecsPlayer/worker/types.d.ts +197 -0
- package/dist/types/players/index.d.ts +14 -0
- package/dist/types/styles/index.d.ts +11 -0
- package/dist/types/types.d.ts +363 -0
- package/dist/types/vanilla/FrameWorksPlayer.d.ts +143 -0
- package/dist/types/vanilla/index.d.ts +19 -0
- package/dist/workers/decoder.worker.js +989 -0
- package/dist/workers/decoder.worker.js.map +1 -0
- package/package.json +80 -0
- package/src/core/ABRController.ts +550 -0
- package/src/core/CodecUtils.ts +257 -0
- package/src/core/Disposable.ts +120 -0
- package/src/core/EventEmitter.ts +113 -0
- package/src/core/GatewayClient.ts +439 -0
- package/src/core/InteractionController.ts +712 -0
- package/src/core/LiveDurationProxy.ts +270 -0
- package/src/core/MetaTrackManager.ts +753 -0
- package/src/core/MistReporter.ts +543 -0
- package/src/core/MistSignaling.ts +346 -0
- package/src/core/PlayerController.ts +2829 -0
- package/src/core/PlayerInterface.ts +432 -0
- package/src/core/PlayerManager.ts +900 -0
- package/src/core/PlayerRegistry.ts +149 -0
- package/src/core/QualityMonitor.ts +597 -0
- package/src/core/ScreenWakeLockManager.ts +163 -0
- package/src/core/SeekingUtils.ts +364 -0
- package/src/core/StreamStateClient.ts +457 -0
- package/src/core/SubtitleManager.ts +297 -0
- package/src/core/TelemetryReporter.ts +308 -0
- package/src/core/TimeFormat.ts +205 -0
- package/src/core/TimerManager.ts +209 -0
- package/src/core/UrlUtils.ts +179 -0
- package/src/core/detector.ts +382 -0
- package/src/core/index.ts +140 -0
- package/src/core/scorer.ts +553 -0
- package/src/core/selector.ts +16 -0
- package/src/global.d.ts +11 -0
- package/src/index.ts +75 -0
- package/src/lib/utils.ts +6 -0
- package/src/players/DashJsPlayer.ts +642 -0
- package/src/players/HlsJsPlayer.ts +483 -0
- package/src/players/MewsWsPlayer/SourceBufferManager.ts +572 -0
- package/src/players/MewsWsPlayer/WebSocketManager.ts +241 -0
- package/src/players/MewsWsPlayer/index.ts +1065 -0
- package/src/players/MewsWsPlayer/types.ts +106 -0
- package/src/players/MistPlayer.ts +188 -0
- package/src/players/MistWebRTCPlayer/index.ts +703 -0
- package/src/players/NativePlayer.ts +820 -0
- package/src/players/VideoJsPlayer.ts +643 -0
- package/src/players/WebCodecsPlayer/JitterBuffer.ts +299 -0
- package/src/players/WebCodecsPlayer/LatencyProfiles.ts +151 -0
- package/src/players/WebCodecsPlayer/RawChunkParser.ts +151 -0
- package/src/players/WebCodecsPlayer/SyncController.ts +456 -0
- package/src/players/WebCodecsPlayer/WebSocketController.ts +564 -0
- package/src/players/WebCodecsPlayer/index.ts +1650 -0
- package/src/players/WebCodecsPlayer/polyfills/MediaStreamTrackGenerator.ts +379 -0
- package/src/players/WebCodecsPlayer/types.ts +542 -0
- package/src/players/WebCodecsPlayer/worker/decoder.worker.ts +1360 -0
- package/src/players/WebCodecsPlayer/worker/types.ts +276 -0
- package/src/players/index.ts +22 -0
- package/src/styles/animations.css +21 -0
- package/src/styles/index.ts +52 -0
- package/src/styles/player.css +2126 -0
- package/src/styles/tailwind.css +1015 -0
- package/src/types.ts +421 -0
- package/src/vanilla/FrameWorksPlayer.ts +367 -0
- package/src/vanilla/index.ts +22 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CodecUtils - Codec string translation utilities
|
|
3
|
+
*
|
|
4
|
+
* Based on MistMetaPlayer's MistUtil.tracks.translateCodec functionality.
|
|
5
|
+
* Translates MistServer codec names to browser-compatible codec strings.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface TrackInfo {
|
|
9
|
+
type: string; // 'video' | 'audio' | 'meta' - loosened for compatibility
|
|
10
|
+
codec: string;
|
|
11
|
+
init?: string;
|
|
12
|
+
codecstring?: string;
|
|
13
|
+
width?: number;
|
|
14
|
+
height?: number;
|
|
15
|
+
bps?: number;
|
|
16
|
+
fpks?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Translate a MistServer codec name to a browser-compatible codec string
|
|
21
|
+
*
|
|
22
|
+
* @param track - Track info from MistServer
|
|
23
|
+
* @returns Browser-compatible codec string (e.g., "avc1.64001f")
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* translateCodec({ codec: 'H264', type: 'video' })
|
|
28
|
+
* // => 'avc1.42E01E' (baseline profile, level 3.0 default)
|
|
29
|
+
*
|
|
30
|
+
* translateCodec({ codec: 'AAC', type: 'audio' })
|
|
31
|
+
* // => 'mp4a.40.2'
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export function translateCodec(track: TrackInfo): string {
|
|
35
|
+
const codec = track.codec.toUpperCase();
|
|
36
|
+
|
|
37
|
+
// Use codecstring if available (MistServer provides this for some tracks)
|
|
38
|
+
if (track.codecstring) {
|
|
39
|
+
return track.codecstring;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Audio codecs
|
|
43
|
+
if (track.type === 'audio') {
|
|
44
|
+
switch (codec) {
|
|
45
|
+
case 'AAC':
|
|
46
|
+
case 'MP4A':
|
|
47
|
+
return 'mp4a.40.2'; // AAC-LC
|
|
48
|
+
case 'MP3':
|
|
49
|
+
return 'mp4a.40.34'; // MP3 in MP4 container
|
|
50
|
+
case 'AC3':
|
|
51
|
+
case 'AC-3':
|
|
52
|
+
return 'ac-3';
|
|
53
|
+
case 'EAC3':
|
|
54
|
+
case 'EC3':
|
|
55
|
+
case 'E-AC3':
|
|
56
|
+
case 'EC-3':
|
|
57
|
+
return 'ec-3';
|
|
58
|
+
case 'OPUS':
|
|
59
|
+
return 'opus';
|
|
60
|
+
case 'VORBIS':
|
|
61
|
+
return 'vorbis';
|
|
62
|
+
case 'FLAC':
|
|
63
|
+
return 'flac';
|
|
64
|
+
case 'PCM':
|
|
65
|
+
case 'PCMS16LE':
|
|
66
|
+
return 'pcm';
|
|
67
|
+
default:
|
|
68
|
+
return codec.toLowerCase();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Video codecs
|
|
73
|
+
if (track.type === 'video') {
|
|
74
|
+
switch (codec) {
|
|
75
|
+
case 'H264':
|
|
76
|
+
case 'AVC':
|
|
77
|
+
case 'AVC1': {
|
|
78
|
+
// Try to extract profile/level from init data
|
|
79
|
+
const profileLevel = extractH264Profile(track.init);
|
|
80
|
+
return profileLevel || 'avc1.42E01E'; // Default: Baseline Profile, Level 3.0
|
|
81
|
+
}
|
|
82
|
+
case 'H265':
|
|
83
|
+
case 'HEVC':
|
|
84
|
+
case 'HEV1':
|
|
85
|
+
case 'HVC1': {
|
|
86
|
+
// Try to extract profile/level from init data
|
|
87
|
+
const profileLevel = extractHEVCProfile(track.init);
|
|
88
|
+
return profileLevel || 'hev1.1.6.L93.B0'; // Default: Main Profile, Level 3.1
|
|
89
|
+
}
|
|
90
|
+
case 'VP8':
|
|
91
|
+
return 'vp8';
|
|
92
|
+
case 'VP9':
|
|
93
|
+
return 'vp09.00.10.08'; // Profile 0, Level 1.0, 8-bit
|
|
94
|
+
case 'AV1':
|
|
95
|
+
return 'av01.0.01M.08'; // Main Profile, Level 2.1, 8-bit
|
|
96
|
+
case 'THEORA':
|
|
97
|
+
return 'theora';
|
|
98
|
+
default:
|
|
99
|
+
return codec.toLowerCase();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return codec.toLowerCase();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Extract H264 profile and level from init data (SPS)
|
|
108
|
+
* The init data contains the SPS NAL unit which has profile/level info
|
|
109
|
+
*
|
|
110
|
+
* @param init - Base64 encoded init data from MistServer
|
|
111
|
+
* @returns Codec string like "avc1.64001f" or null
|
|
112
|
+
*/
|
|
113
|
+
function extractH264Profile(init?: string): string | null {
|
|
114
|
+
if (!init) return null;
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
// Decode base64 init data
|
|
118
|
+
const bytes = base64ToBytes(init);
|
|
119
|
+
|
|
120
|
+
// Look for SPS NAL unit (starts with 0x67 for H264)
|
|
121
|
+
// Format: NAL type (1 byte) + profile_idc (1 byte) + constraint flags (1 byte) + level_idc (1 byte)
|
|
122
|
+
for (let i = 0; i < bytes.length - 4; i++) {
|
|
123
|
+
// Check for NAL start code (0x00 0x00 0x01 or 0x00 0x00 0x00 0x01)
|
|
124
|
+
if (bytes[i] === 0x00 && bytes[i + 1] === 0x00) {
|
|
125
|
+
let offset = i + 2;
|
|
126
|
+
if (bytes[offset] === 0x00) offset++;
|
|
127
|
+
if (bytes[offset] === 0x01) offset++;
|
|
128
|
+
|
|
129
|
+
// Check if this is SPS (NAL type 7)
|
|
130
|
+
const nalType = bytes[offset] & 0x1f;
|
|
131
|
+
if (nalType === 7 && offset + 3 < bytes.length) {
|
|
132
|
+
const profileIdc = bytes[offset + 1];
|
|
133
|
+
const constraintFlags = bytes[offset + 2];
|
|
134
|
+
const levelIdc = bytes[offset + 3];
|
|
135
|
+
|
|
136
|
+
return `avc1.${toHex(profileIdc)}${toHex(constraintFlags)}${toHex(levelIdc)}`;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// If no NAL start code found, try raw format
|
|
142
|
+
if (bytes.length >= 4) {
|
|
143
|
+
// Assume first bytes are profile/constraint/level
|
|
144
|
+
const profileIdc = bytes[0];
|
|
145
|
+
const constraintFlags = bytes[1];
|
|
146
|
+
const levelIdc = bytes[2];
|
|
147
|
+
|
|
148
|
+
// Validate reasonable values
|
|
149
|
+
if (profileIdc > 0 && profileIdc < 255 && levelIdc > 0 && levelIdc < 100) {
|
|
150
|
+
return `avc1.${toHex(profileIdc)}${toHex(constraintFlags)}${toHex(levelIdc)}`;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
} catch {
|
|
154
|
+
// Ignore parsing errors
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Extract HEVC profile and level from init data (VPS/SPS)
|
|
162
|
+
*
|
|
163
|
+
* @param init - Base64 encoded init data from MistServer
|
|
164
|
+
* @returns Codec string like "hev1.1.6.L93.B0" or null
|
|
165
|
+
*/
|
|
166
|
+
function extractHEVCProfile(init?: string): string | null {
|
|
167
|
+
if (!init) return null;
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
// Decode base64 init data
|
|
171
|
+
const bytes = base64ToBytes(init);
|
|
172
|
+
|
|
173
|
+
// HEVC profile/level extraction is more complex
|
|
174
|
+
// For now, return a sensible default based on data presence
|
|
175
|
+
if (bytes.length > 0) {
|
|
176
|
+
// Look for profile_tier_level in VPS/SPS
|
|
177
|
+
// This is a simplified extraction - full parsing would be more complex
|
|
178
|
+
for (let i = 0; i < bytes.length - 3; i++) {
|
|
179
|
+
// Look for general_profile_idc (usually in first few bytes after NAL header)
|
|
180
|
+
const profileIdc = bytes[i];
|
|
181
|
+
if (profileIdc >= 1 && profileIdc <= 5) {
|
|
182
|
+
// Valid profile IDC (1=Main, 2=Main10, 3=MainStill, 4=Range Extensions, 5=High Throughput)
|
|
183
|
+
const tierFlag = 0; // Assume main tier
|
|
184
|
+
const levelIdc = bytes[i + 1] || 93; // Default to level 3.1
|
|
185
|
+
|
|
186
|
+
// Format: hev1.{profile}.{tier_flag}{compatibility}.L{level}.{constraints}
|
|
187
|
+
return `hev1.${profileIdc}.6.L${levelIdc}.B0`;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
} catch {
|
|
192
|
+
// Ignore parsing errors
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Convert byte to 2-digit hex string
|
|
200
|
+
*/
|
|
201
|
+
function toHex(byte: number): string {
|
|
202
|
+
return byte.toString(16).padStart(2, '0').toUpperCase();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Decode base64 string to Uint8Array
|
|
207
|
+
*/
|
|
208
|
+
function base64ToBytes(base64: string): Uint8Array {
|
|
209
|
+
const binaryString = atob(base64);
|
|
210
|
+
const bytes = new Uint8Array(binaryString.length);
|
|
211
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
212
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
213
|
+
}
|
|
214
|
+
return bytes;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Check if a codec is supported by the browser via MediaSource
|
|
219
|
+
*
|
|
220
|
+
* @param codecString - Codec string to check
|
|
221
|
+
* @param containerType - Container type (default: 'video/mp4')
|
|
222
|
+
* @returns true if supported
|
|
223
|
+
*/
|
|
224
|
+
export function isCodecSupported(codecString: string, containerType = 'video/mp4'): boolean {
|
|
225
|
+
if (typeof MediaSource === 'undefined' || !MediaSource.isTypeSupported) {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const mimeType = `${containerType}; codecs="${codecString}"`;
|
|
230
|
+
return MediaSource.isTypeSupported(mimeType);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Get the best supported codec from a list of tracks
|
|
235
|
+
*
|
|
236
|
+
* @param tracks - Array of track info
|
|
237
|
+
* @param type - Track type to filter ('video' or 'audio')
|
|
238
|
+
* @returns Best supported track or null
|
|
239
|
+
*/
|
|
240
|
+
export function getBestSupportedTrack(tracks: TrackInfo[], type: 'video' | 'audio'): TrackInfo | null {
|
|
241
|
+
const filteredTracks = tracks.filter(t => t.type === type);
|
|
242
|
+
|
|
243
|
+
for (const track of filteredTracks) {
|
|
244
|
+
const codecString = translateCodec(track);
|
|
245
|
+
if (isCodecSupported(codecString)) {
|
|
246
|
+
return track;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export default {
|
|
254
|
+
translateCodec,
|
|
255
|
+
isCodecSupported,
|
|
256
|
+
getBestSupportedTrack,
|
|
257
|
+
};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Disposable interface for consistent cleanup across all core classes.
|
|
3
|
+
*
|
|
4
|
+
* All classes that manage resources (timers, event listeners, WebSockets, etc.)
|
|
5
|
+
* should implement this interface to ensure proper cleanup.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Interface for objects that need cleanup
|
|
10
|
+
*/
|
|
11
|
+
export interface Disposable {
|
|
12
|
+
/**
|
|
13
|
+
* Clean up all resources held by this object.
|
|
14
|
+
* Safe to call multiple times - subsequent calls should be no-ops.
|
|
15
|
+
*/
|
|
16
|
+
dispose(): void;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Whether this object has been disposed
|
|
20
|
+
*/
|
|
21
|
+
readonly disposed: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Base class for disposable objects that provides:
|
|
26
|
+
* - disposed flag tracking
|
|
27
|
+
* - Double-dispose protection
|
|
28
|
+
* - Template method for subclass cleanup
|
|
29
|
+
*/
|
|
30
|
+
export abstract class BaseDisposable implements Disposable {
|
|
31
|
+
private _disposed = false;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Whether this object has been disposed
|
|
35
|
+
*/
|
|
36
|
+
get disposed(): boolean {
|
|
37
|
+
return this._disposed;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Dispose of this object, releasing all resources.
|
|
42
|
+
* Safe to call multiple times.
|
|
43
|
+
*/
|
|
44
|
+
dispose(): void {
|
|
45
|
+
if (this._disposed) return;
|
|
46
|
+
this._disposed = true;
|
|
47
|
+
this.onDispose();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Subclasses implement this to clean up their resources.
|
|
52
|
+
* Called exactly once when dispose() is first called.
|
|
53
|
+
*/
|
|
54
|
+
protected abstract onDispose(): void;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Throw if this object has been disposed.
|
|
58
|
+
* Use at the start of methods that shouldn't run after disposal.
|
|
59
|
+
*/
|
|
60
|
+
protected throwIfDisposed(operation: string = 'operation'): void {
|
|
61
|
+
if (this._disposed) {
|
|
62
|
+
throw new Error(`Cannot perform ${operation} on disposed object`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Check if disposed without throwing - useful for conditional guards
|
|
68
|
+
*/
|
|
69
|
+
protected guardDisposed(): boolean {
|
|
70
|
+
return this._disposed;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Utility to dispose multiple disposables at once
|
|
76
|
+
*/
|
|
77
|
+
export function disposeAll(...disposables: (Disposable | null | undefined)[]): void {
|
|
78
|
+
for (const d of disposables) {
|
|
79
|
+
if (d && !d.disposed) {
|
|
80
|
+
try {
|
|
81
|
+
d.dispose();
|
|
82
|
+
} catch (err) {
|
|
83
|
+
console.warn('[Disposable] Error during disposal:', err);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Create a composite disposable that disposes multiple items
|
|
91
|
+
*/
|
|
92
|
+
export function createCompositeDisposable(
|
|
93
|
+
...disposables: (Disposable | (() => void))[]
|
|
94
|
+
): Disposable {
|
|
95
|
+
let disposed = false;
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
get disposed() {
|
|
99
|
+
return disposed;
|
|
100
|
+
},
|
|
101
|
+
dispose() {
|
|
102
|
+
if (disposed) return;
|
|
103
|
+
disposed = true;
|
|
104
|
+
|
|
105
|
+
for (const d of disposables) {
|
|
106
|
+
try {
|
|
107
|
+
if (typeof d === 'function') {
|
|
108
|
+
d();
|
|
109
|
+
} else if (d && !d.disposed) {
|
|
110
|
+
d.dispose();
|
|
111
|
+
}
|
|
112
|
+
} catch (err) {
|
|
113
|
+
console.warn('[CompositeDisposable] Error during disposal:', err);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export default BaseDisposable;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EventEmitter.ts
|
|
3
|
+
*
|
|
4
|
+
* A lightweight, typed event emitter for framework-agnostic components.
|
|
5
|
+
* Used by GatewayClient, StreamStateClient, and PlayerController.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
type Listener<T> = (data: T) => void;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Typed event emitter that provides type-safe event handling.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* interface MyEvents {
|
|
16
|
+
* stateChange: { state: string };
|
|
17
|
+
* error: { message: string };
|
|
18
|
+
* }
|
|
19
|
+
*
|
|
20
|
+
* class MyClass extends TypedEventEmitter<MyEvents> {
|
|
21
|
+
* doSomething() {
|
|
22
|
+
* this.emit('stateChange', { state: 'ready' });
|
|
23
|
+
* }
|
|
24
|
+
* }
|
|
25
|
+
*
|
|
26
|
+
* const instance = new MyClass();
|
|
27
|
+
* const unsub = instance.on('stateChange', ({ state }) => console.log(state));
|
|
28
|
+
* unsub(); // unsubscribe
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export class TypedEventEmitter<Events extends Record<string, any>> {
|
|
32
|
+
private listeners = new Map<keyof Events, Set<Function>>();
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Subscribe to an event.
|
|
36
|
+
* @param event - The event name to listen for
|
|
37
|
+
* @param listener - Callback function invoked when the event is emitted
|
|
38
|
+
* @returns Unsubscribe function
|
|
39
|
+
*/
|
|
40
|
+
on<K extends keyof Events>(event: K, listener: Listener<Events[K]>): () => void {
|
|
41
|
+
if (!this.listeners.has(event)) {
|
|
42
|
+
this.listeners.set(event, new Set());
|
|
43
|
+
}
|
|
44
|
+
this.listeners.get(event)!.add(listener);
|
|
45
|
+
|
|
46
|
+
// Return unsubscribe function
|
|
47
|
+
return () => this.off(event, listener);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Subscribe to an event only once.
|
|
52
|
+
* The listener is automatically removed after the first invocation.
|
|
53
|
+
* @param event - The event name to listen for
|
|
54
|
+
* @param listener - Callback function invoked when the event is emitted
|
|
55
|
+
* @returns Unsubscribe function
|
|
56
|
+
*/
|
|
57
|
+
once<K extends keyof Events>(event: K, listener: Listener<Events[K]>): () => void {
|
|
58
|
+
const onceListener = (data: Events[K]) => {
|
|
59
|
+
this.off(event, onceListener);
|
|
60
|
+
listener(data);
|
|
61
|
+
};
|
|
62
|
+
return this.on(event, onceListener);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Unsubscribe from an event.
|
|
67
|
+
* @param event - The event name
|
|
68
|
+
* @param listener - The callback to remove
|
|
69
|
+
*/
|
|
70
|
+
off<K extends keyof Events>(event: K, listener: Listener<Events[K]>): void {
|
|
71
|
+
this.listeners.get(event)?.delete(listener);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Emit an event to all subscribers.
|
|
76
|
+
* @param event - The event name
|
|
77
|
+
* @param data - The event payload
|
|
78
|
+
*/
|
|
79
|
+
protected emit<K extends keyof Events>(event: K, data: Events[K]): void {
|
|
80
|
+
this.listeners.get(event)?.forEach(listener => {
|
|
81
|
+
try {
|
|
82
|
+
listener(data);
|
|
83
|
+
} catch (e) {
|
|
84
|
+
console.error(`[EventEmitter] Error in ${String(event)} listener:`, e);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Remove all listeners for all events.
|
|
91
|
+
*/
|
|
92
|
+
removeAllListeners(): void {
|
|
93
|
+
this.listeners.clear();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Remove all listeners for a specific event.
|
|
98
|
+
* @param event - The event name
|
|
99
|
+
*/
|
|
100
|
+
removeListeners<K extends keyof Events>(event: K): void {
|
|
101
|
+
this.listeners.delete(event);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Check if there are any listeners for an event.
|
|
106
|
+
* @param event - The event name
|
|
107
|
+
*/
|
|
108
|
+
hasListeners<K extends keyof Events>(event: K): boolean {
|
|
109
|
+
return (this.listeners.get(event)?.size ?? 0) > 0;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export default TypedEventEmitter;
|