@stinkycomputing/web-live-player 0.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.
@@ -0,0 +1,54 @@
1
+ import { Logger } from '../types';
2
+
3
+ /**
4
+ * Base player configuration
5
+ */
6
+ export interface BasePlayerConfig {
7
+ /** Enable debug logging */
8
+ debugLogging?: boolean;
9
+ }
10
+ /**
11
+ * Create a logger with configurable debug output
12
+ */
13
+ export declare function createLogger(debugLogging: boolean): Logger;
14
+ /**
15
+ * Base class for video players providing common event handling and state management
16
+ */
17
+ export declare abstract class BasePlayer<TState extends string> {
18
+ protected eventHandlers: Map<string, Set<Function>>;
19
+ protected logger: Logger;
20
+ protected _state: TState;
21
+ constructor(initialState: TState, debugLogging?: boolean);
22
+ /**
23
+ * Current player state
24
+ */
25
+ get state(): TState;
26
+ /**
27
+ * Update player state and emit statechange event
28
+ */
29
+ protected setState(state: TState): void;
30
+ /**
31
+ * Enable/disable debug logging
32
+ */
33
+ setDebugLogging(enabled: boolean): void;
34
+ /**
35
+ * Subscribe to an event
36
+ */
37
+ on(event: string, handler: Function): void;
38
+ /**
39
+ * Unsubscribe from an event
40
+ */
41
+ off(event: string, handler: Function): void;
42
+ /**
43
+ * Emit an event to all subscribers
44
+ */
45
+ protected emit(event: string, ...args: any[]): void;
46
+ /**
47
+ * Clear all event handlers (called during dispose)
48
+ */
49
+ protected clearEventHandlers(): void;
50
+ /**
51
+ * Dispose the player and release resources
52
+ */
53
+ abstract dispose(): void;
54
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * BasePlayer Tests
3
+ *
4
+ * Tests for the shared player base class functionality:
5
+ * - Event handling (on/off/emit)
6
+ * - State management
7
+ */
8
+ export {};
@@ -0,0 +1,200 @@
1
+ import { MP4FileInfo } from '../sources/mp4-file-source';
2
+ import { PreferredDecoder } from '../types';
3
+ import { BasePlayer } from './base-player';
4
+
5
+ /**
6
+ * Play mode for file playback
7
+ */
8
+ export type FilePlayMode = 'once' | 'loop';
9
+ /**
10
+ * File player configuration
11
+ */
12
+ export interface FilePlayerConfig {
13
+ preferredDecoder?: PreferredDecoder;
14
+ /** Enable audio playback */
15
+ enableAudio?: boolean;
16
+ /** Audio context to use (creates one if not provided) */
17
+ audioContext?: AudioContext;
18
+ /** Enable debug logging */
19
+ debugLogging?: boolean;
20
+ /** Play mode: 'once' plays to end, 'loop' seamlessly loops (default: 'once') */
21
+ playMode?: FilePlayMode;
22
+ /** @deprecated Use playMode instead */
23
+ loop?: boolean;
24
+ }
25
+ /**
26
+ * File player state
27
+ */
28
+ export type FilePlayerState = 'idle' | 'loading' | 'ready' | 'playing' | 'paused' | 'ended' | 'error';
29
+ /**
30
+ * File player statistics
31
+ */
32
+ export interface FilePlayerStats {
33
+ duration: number;
34
+ position: number;
35
+ bufferSize: number;
36
+ width: number;
37
+ height: number;
38
+ frameRate: number;
39
+ codec: string;
40
+ state: FilePlayerState;
41
+ }
42
+ /**
43
+ * File Video Player
44
+ */
45
+ export declare class FileVideoPlayer extends BasePlayer<FilePlayerState> {
46
+ private config;
47
+ private fileSource;
48
+ private decoder;
49
+ private audioContext;
50
+ private audioPlayer;
51
+ private ownsAudioContext;
52
+ private audioInitialized;
53
+ private fileInfo;
54
+ private frameBuffer;
55
+ private minBufferSize;
56
+ private sampleQueue;
57
+ private audioSampleQueue;
58
+ private nextSampleIndex;
59
+ private nextAudioSampleIndex;
60
+ private maxDecoderQueue;
61
+ private bufferReadyResolve;
62
+ private playStartTime;
63
+ private playStartPosition;
64
+ private currentPosition;
65
+ private lastVideoFrame;
66
+ private isSeeking;
67
+ constructor(config?: FilePlayerConfig);
68
+ /**
69
+ * Get file info
70
+ */
71
+ getFileInfo(): MP4FileInfo | null;
72
+ /**
73
+ * Get current position in seconds
74
+ */
75
+ getPosition(): number;
76
+ /**
77
+ * Get duration in seconds
78
+ */
79
+ getDuration(): number;
80
+ /**
81
+ * Get player statistics
82
+ */
83
+ getStats(): FilePlayerStats;
84
+ /**
85
+ * Set play mode ('once' or 'loop')
86
+ */
87
+ setPlayMode(mode: FilePlayMode): void;
88
+ /**
89
+ * Get current play mode
90
+ */
91
+ getPlayMode(): FilePlayMode;
92
+ /**
93
+ * Set looping (convenience method, equivalent to setPlayMode)
94
+ * @deprecated Use setPlayMode instead
95
+ */
96
+ setLoop(loop: boolean): void;
97
+ /**
98
+ * Enable/disable debug logging
99
+ */
100
+ setDebugLogging(enabled: boolean): void;
101
+ /**
102
+ * Set audio volume (0-1)
103
+ */
104
+ setVolume(volume: number): void;
105
+ /**
106
+ * Get current audio volume (0-1)
107
+ */
108
+ getVolume(): number;
109
+ /**
110
+ * Load a video file from URL
111
+ */
112
+ loadFromUrl(url: string): Promise<MP4FileInfo>;
113
+ /**
114
+ * Load a video file from File object
115
+ */
116
+ loadFromFile(file: File): Promise<MP4FileInfo>;
117
+ /**
118
+ * Initialize the video decoder
119
+ */
120
+ private initDecoder;
121
+ /**
122
+ * Initialize the audio decoder
123
+ */
124
+ private initAudioDecoder;
125
+ /**
126
+ * Wait for the frame buffer to have enough frames for smooth playback
127
+ */
128
+ private waitForBuffer;
129
+ /**
130
+ * Handle samples from mp4box - queue them for gradual decoding
131
+ */
132
+ private handleSamples;
133
+ /**
134
+ * Feed video samples to decoder gradually (don't overflow the queue)
135
+ */
136
+ private feedDecoder;
137
+ private maxAudioBufferMs;
138
+ /**
139
+ * Feed audio samples to decoder - gradually based on playback position
140
+ */
141
+ private feedAudioDecoder;
142
+ /**
143
+ * Handle decoded frame from decoder
144
+ */
145
+ private handleDecodedFrame;
146
+ /**
147
+ * Handle source ended (all samples extracted)
148
+ */
149
+ private handleSourceEnded;
150
+ /**
151
+ * Perform a seamless loop back to the start
152
+ * This resets timing and decoder state for looping
153
+ */
154
+ private performSeamlessLoop;
155
+ /**
156
+ * Start playback
157
+ */
158
+ play(): void;
159
+ /**
160
+ * Pause playback
161
+ */
162
+ pause(): void;
163
+ /**
164
+ * Seek to a specific time in seconds
165
+ */
166
+ seek(timeSeconds: number): Promise<void>;
167
+ /**
168
+ * Clear the frame buffer
169
+ */
170
+ private clearFrameBuffer;
171
+ /**
172
+ * Get a video frame for rendering
173
+ *
174
+ * Call this in your render loop. Returns the appropriate frame for the current time.
175
+ */
176
+ getVideoFrame(): VideoFrame | null;
177
+ /**
178
+ * Subscribe to events (typed overloads)
179
+ */
180
+ on(event: 'statechange', handler: (state: FilePlayerState) => void): void;
181
+ on(event: 'ready', handler: (info: MP4FileInfo) => void): void;
182
+ on(event: 'progress', handler: (loaded: number, total: number) => void): void;
183
+ on(event: 'error', handler: (error: Error) => void): void;
184
+ on(event: 'ended', handler: () => void): void;
185
+ on(event: 'loop', handler: () => void): void;
186
+ on(event: 'seeked', handler: (time: number) => void): void;
187
+ /**
188
+ * Unsubscribe from events
189
+ */
190
+ off(event: string, handler: Function): void;
191
+ /**
192
+ * Dispose and clean up resources
193
+ * @param full - If true, also disposes the AudioContext (default: false for reload)
194
+ */
195
+ dispose(full?: boolean): void;
196
+ }
197
+ /**
198
+ * Factory function to create a file player
199
+ */
200
+ export declare function createFilePlayer(config?: FilePlayerConfig): FileVideoPlayer;
@@ -0,0 +1,219 @@
1
+ import { IStreamSource } from '../sources/stream-source';
2
+ import { PreferredDecoder } from '../types';
3
+ import { LatencyStats } from '../scheduling/frame-scheduler';
4
+ import { BasePlayer } from './base-player';
5
+
6
+ /**
7
+ * Player configuration
8
+ */
9
+ export interface PlayerConfig {
10
+ preferredDecoder?: PreferredDecoder;
11
+ /** Buffer delay in milliseconds (default: 100ms) */
12
+ bufferDelayMs?: number;
13
+ enableAudio?: boolean;
14
+ /** Video track name for MoQ streams (default: 'video'). Set to null to accept video from any track. */
15
+ videoTrackName?: string | null;
16
+ /** Audio track name for MoQ streams (default: 'audio'). Set to null to accept audio from any track. */
17
+ audioTrackName?: string | null;
18
+ debugLogging?: boolean;
19
+ }
20
+ /**
21
+ * Player state
22
+ */
23
+ export type PlayerState = 'idle' | 'playing' | 'paused' | 'error';
24
+ /**
25
+ * Player statistics
26
+ */
27
+ export interface PlayerStats {
28
+ bufferSize: number;
29
+ bufferMs: number;
30
+ avgBufferMs: number;
31
+ targetBufferMs: number;
32
+ droppedFrames: number;
33
+ totalFrames: number;
34
+ decoderState: string;
35
+ streamWidth: number;
36
+ streamHeight: number;
37
+ frameRate: number;
38
+ latency: LatencyStats | null;
39
+ }
40
+ /**
41
+ * Player event types
42
+ */
43
+ type PlayerEventMap = {
44
+ 'frame': (frame: VideoFrame) => void;
45
+ 'statechange': (state: PlayerState) => void;
46
+ 'error': (error: Error) => void;
47
+ 'metadata': (metadata: {
48
+ width: number;
49
+ height: number;
50
+ codec: string;
51
+ }) => void;
52
+ };
53
+ /**
54
+ * Factory function to create a player instance
55
+ */
56
+ export declare function createPlayer(config?: PlayerConfig): LiveVideoPlayer;
57
+ /**
58
+ * Live Video Player - Main class
59
+ */
60
+ export declare class LiveVideoPlayer extends BasePlayer<PlayerState> {
61
+ private config;
62
+ private streamSource;
63
+ private trackFilter;
64
+ private boundDataHandler;
65
+ private decoder;
66
+ private currentCodecData;
67
+ private useWasmDecoder;
68
+ private waitingForKeyframe;
69
+ private lastWaitingForKeyframeLog;
70
+ private lastKeyframeRequest;
71
+ private statusLogCounter;
72
+ private isConfiguring;
73
+ private pendingDuringConfig;
74
+ private frameScheduler;
75
+ private lastVideoFrame;
76
+ private consecutiveDrops;
77
+ private totalDrops;
78
+ private lastDropLogTime;
79
+ private streamWidth;
80
+ private streamHeight;
81
+ private estimatedFrameRate;
82
+ private audioContext;
83
+ private audioPlayer;
84
+ private ownsAudioContext;
85
+ private audioCodecData;
86
+ private arrivalTimes;
87
+ constructor(config?: PlayerConfig);
88
+ /**
89
+ * Enable or disable debug logging at runtime
90
+ */
91
+ setDebugLogging(enabled: boolean): void;
92
+ /**
93
+ * Set audio volume (0-1)
94
+ */
95
+ setVolume(volume: number): void;
96
+ /**
97
+ * Get current audio volume (0-1)
98
+ */
99
+ getVolume(): number;
100
+ /**
101
+ * Set the stream source (dependency injection)
102
+ */
103
+ setStreamSource(source: IStreamSource): void;
104
+ /**
105
+ * Set the track name to filter for
106
+ */
107
+ setTrackFilter(trackName: string): void;
108
+ /**
109
+ * Convenience method to connect to a MoQ-like session
110
+ *
111
+ * Note: For audio support, the MoQ session must also be subscribed to the audio track.
112
+ * When using StandaloneMoQSource, include both 'video' and 'audio' in subscriptions.
113
+ * When using Elmo's MoQSessionNode, add an audio track to the session config.
114
+ *
115
+ * @param session - MoQ session implementing IStreamSource (e.g., Elmo's MoQSessionNode)
116
+ * @param videoTrackName - Video track name (defaults to config.videoTrackName or 'video')
117
+ */
118
+ connectToMoQSession(session: IStreamSource, videoTrackName?: string): void;
119
+ /**
120
+ * Connect to a MoQ relay directly with video and optional audio tracks
121
+ *
122
+ * @param relayUrl - URL of the MoQ relay (e.g., 'https://relay.example.com/moq')
123
+ * @param namespace - MoQ namespace/broadcast name
124
+ * @param options - Optional configuration for track names
125
+ */
126
+ connectToMoQRelay(relayUrl: string, namespace: string, options?: {
127
+ videoTrack?: string;
128
+ audioTrack?: string | false;
129
+ }): Promise<void>;
130
+ /**
131
+ * Start playback
132
+ */
133
+ play(): void;
134
+ /**
135
+ * Pause playback
136
+ */
137
+ pause(): void;
138
+ /**
139
+ * Get a video frame for rendering
140
+ *
141
+ * Call this in your render loop with the current timestamp.
142
+ * The returned VideoFrame should be closed after use if you're done with it.
143
+ */
144
+ getVideoFrame(timestampMs: number): VideoFrame | null;
145
+ /**
146
+ * Set buffer delay in milliseconds (syncs both video and audio)
147
+ */
148
+ setBufferDelay(delayMs: number): void;
149
+ /**
150
+ * Get current buffer delay in milliseconds
151
+ */
152
+ getBufferDelay(): number;
153
+ /**
154
+ * Set preferred decoder type
155
+ * If decoder type changes while playing, dispose old decoder and request keyframe
156
+ */
157
+ setPreferredDecoder(type: PreferredDecoder): void;
158
+ /**
159
+ * Flush the player pipeline (decoder, frame buffer)
160
+ * Used to recover from queue overflow or when seeking
161
+ */
162
+ flush(): void;
163
+ /**
164
+ * Get player statistics
165
+ */
166
+ getStats(): PlayerStats;
167
+ /**
168
+ * Subscribe to player events (typed overload)
169
+ */
170
+ on<K extends keyof PlayerEventMap>(event: K, handler: PlayerEventMap[K]): void;
171
+ /**
172
+ * Unsubscribe from player events (typed overload)
173
+ */
174
+ off<K extends keyof PlayerEventMap>(event: K, handler: PlayerEventMap[K]): void;
175
+ /**
176
+ * Handle incoming stream data
177
+ */
178
+ private handleStreamData;
179
+ /**
180
+ * Handle incoming audio frame data
181
+ */
182
+ private handleAudioData;
183
+ /**
184
+ * Configure the decoder for a specific codec
185
+ */
186
+ private configureDecoder;
187
+ private getCodecName;
188
+ /**
189
+ * Handle decoded video frame (from WebCodecs)
190
+ */
191
+ private handleDecodedFrame;
192
+ /**
193
+ * Handle decoded YUV frame (from WASM decoder)
194
+ * Converts YUV to VideoFrame using canvas
195
+ */
196
+ private handleDecodedYUVFrame;
197
+ /**
198
+ * Convert YUV frame to VideoFrame using native I420 support
199
+ * Much faster than manual pixel-by-pixel conversion
200
+ *
201
+ * @param yuv - YUV frame data from decoder (may have padded dimensions)
202
+ * @param visibleWidth - Actual video width (unpadded)
203
+ * @param visibleHeight - Actual video height (unpadded)
204
+ */
205
+ private convertYUVToVideoFrame;
206
+ /**
207
+ * Handle decoder error
208
+ */
209
+ private handleDecoderError;
210
+ /**
211
+ * Handle decoder queue overflow - flush and request keyframe
212
+ */
213
+ private handleQueueOverflow;
214
+ /**
215
+ * Dispose the player and release resources
216
+ */
217
+ dispose(): void;
218
+ }
219
+ export {};
@@ -0,0 +1,25 @@
1
+ import { HeaderCodecData } from './sesame-binary-protocol';
2
+
3
+ /**
4
+ * Timebase structure for timestamp conversion
5
+ */
6
+ export interface Timebase {
7
+ num: number;
8
+ den: number;
9
+ }
10
+ /**
11
+ * Rescale a timestamp from one timebase to another
12
+ */
13
+ export declare function rescaleTime(pts: bigint, source: Timebase, target: Timebase): number;
14
+ /**
15
+ * Check if codec data has changed
16
+ */
17
+ export declare function codecDataChanged(current: HeaderCodecData | undefined, newData: HeaderCodecData | undefined): boolean;
18
+ /**
19
+ * Get human-readable codec name
20
+ */
21
+ export declare function getCodecName(codecType: number): string;
22
+ /**
23
+ * Get WebCodecs codec string for a given codec data
24
+ */
25
+ export declare function getCodecString(codecData: HeaderCodecData): string | null;
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Sesame Binary Protocol - TypeScript Implementation
3
+ *
4
+ * This module provides TypeScript implementation of the Sesame binary protocol
5
+ * for serializing and deserializing multimedia data packets.
6
+ */
7
+ export declare const PROTOCOL_MAGIC = 1297302867;
8
+ export declare const PROTOCOL_VERSION = 1;
9
+ export declare const HEADER_DATA_SIZE = 32;
10
+ export declare const HEADER_CODEC_DATA_SIZE = 24;
11
+ export declare const HEADER_METADATA_SIZE = 64;
12
+ export declare const FLAG_HAS_CODEC_DATA: number;
13
+ export declare const FLAG_HAS_METADATA: number;
14
+ export declare const FLAG_IS_KEYFRAME: number;
15
+ export declare enum PacketType {
16
+ VIDEO_FRAME = 1,
17
+ AUDIO_FRAME = 2,
18
+ RPC = 3,
19
+ MUXED_DATA = 4,
20
+ DECODER_DATA = 5
21
+ }
22
+ export declare enum CodecType {
23
+ VIDEO_VP8 = 1,
24
+ VIDEO_VP9 = 2,
25
+ VIDEO_AVC = 3,
26
+ VIDEO_HEVC = 4,
27
+ VIDEO_AV1 = 5,
28
+ AUDIO_OPUS = 64,
29
+ AUDIO_AAC = 65,
30
+ AUDIO_PCM = 66
31
+ }
32
+ export interface HeaderData {
33
+ magic: number;
34
+ flags: number;
35
+ pts: bigint;
36
+ id: bigint;
37
+ version: number;
38
+ header_size: number;
39
+ type: PacketType;
40
+ reserved: number;
41
+ }
42
+ export interface HeaderCodecData {
43
+ sample_rate: number;
44
+ timebase_num: number;
45
+ timebase_den: number;
46
+ codec_profile: number;
47
+ codec_level: number;
48
+ width: number;
49
+ height: number;
50
+ codec_type: CodecType;
51
+ channels: number;
52
+ bit_depth: number;
53
+ reserved: number;
54
+ }
55
+ export interface HeaderMetadata {
56
+ metadata: string;
57
+ }
58
+ export interface ParsedData {
59
+ valid: boolean;
60
+ header: HeaderData | null;
61
+ metadata: HeaderMetadata | null;
62
+ codec_data: HeaderCodecData | null;
63
+ payload: Uint8Array | null;
64
+ payload_size: number;
65
+ }
66
+ /**
67
+ * Main binary protocol class with static methods for serialization/deserialization
68
+ */
69
+ export declare class SesameBinaryProtocol {
70
+ /**
71
+ * Initialize a header data structure with proper defaults
72
+ */
73
+ static initHeader(type: PacketType, flags: number, pts: bigint, id: bigint): HeaderData;
74
+ /**
75
+ * Calculate the total header size based on flags
76
+ */
77
+ static calculateHeaderSize(flags: number): number;
78
+ /**
79
+ * Validate a header structure
80
+ */
81
+ static validateHeader(header: HeaderData, totalSize: number): boolean;
82
+ /**
83
+ * Serialize data into a Uint8Array buffer
84
+ */
85
+ static serialize(header: HeaderData, metadata?: HeaderMetadata | null, codecData?: HeaderCodecData | null, payload?: Uint8Array | null): Uint8Array | null;
86
+ /**
87
+ * Parse incoming binary data
88
+ */
89
+ static parseData(data: Uint8Array): ParsedData;
90
+ /**
91
+ * Helper: Convert string to fixed-size byte array (null-terminated)
92
+ */
93
+ private static stringToFixedBytes;
94
+ /**
95
+ * Helper: Convert fixed-size byte array to string (null-terminated)
96
+ */
97
+ private static fixedBytesToString;
98
+ }