@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,122 @@
1
+ /**
2
+ * Frame Scheduler
3
+ *
4
+ * Manages frame buffering and synchronization between stream framerate
5
+ * and display refresh rate.
6
+ */
7
+ /**
8
+ * Frame timing information for latency tracking
9
+ */
10
+ export interface FrameTiming {
11
+ /** When encoded data arrived (performance.now()) */
12
+ arrivalTime: number;
13
+ /** When decode completed (performance.now()) */
14
+ decodeTime: number;
15
+ /** When frame was displayed (performance.now()) */
16
+ displayTime?: number;
17
+ }
18
+ /**
19
+ * Latency statistics
20
+ */
21
+ export interface LatencyStats {
22
+ /** Time from arrival to decode completion (ms) */
23
+ decodeLatency: number;
24
+ /** Time from decode to display (ms) */
25
+ bufferLatency: number;
26
+ /** Total time from arrival to display (ms) */
27
+ totalLatency: number;
28
+ /** Average latencies over recent frames */
29
+ avgDecodeLatency: number;
30
+ avgBufferLatency: number;
31
+ avgTotalLatency: number;
32
+ }
33
+ export interface SchedulerStatus {
34
+ currentBufferSize: number;
35
+ currentBufferMs: number;
36
+ avgBufferMs: number;
37
+ targetBufferMs: number;
38
+ streamFrameDurationUs: number | null;
39
+ droppedFrames: number;
40
+ totalEnqueuedFrames: number;
41
+ totalDequeuedFrames: number;
42
+ driftCorrections: number;
43
+ latency: LatencyStats | null;
44
+ }
45
+ export interface SchedulerConfig<T> {
46
+ /** Target buffer delay in milliseconds (0 = bypass mode, always return latest) */
47
+ bufferDelayMs?: number;
48
+ /** Maximum buffer size in frames before overflow */
49
+ maxBufferSize?: number;
50
+ /** How often to check drift (every N dequeues) */
51
+ driftCheckInterval?: number;
52
+ /** Drift threshold in milliseconds before correction */
53
+ driftCorrectionThresholdMs?: number;
54
+ /** Logger function */
55
+ logger?: (message: string) => void;
56
+ /** Callback when frame is dropped */
57
+ onFrameDropped?: (frame: T, reason: 'overflow' | 'skip') => void;
58
+ }
59
+ /**
60
+ * FrameScheduler - Simplified implementation
61
+ *
62
+ * Core algorithm:
63
+ * 1. On first dequeue with frames, record (realTime, streamTime) as start point
64
+ * 2. On each dequeue:
65
+ * - Calculate expected stream time = startStreamTime + (currentRealTime - startRealTime) - bufferDelay
66
+ * - Find frame with timestamp <= expectedStreamTime
67
+ * - Drop old frames, return best match
68
+ * 3. Periodically adjust start point to correct drift
69
+ */
70
+ export declare class FrameScheduler<T> {
71
+ private buffer;
72
+ private bufferDelayMs;
73
+ private maxBufferSize;
74
+ private startRealTimeUs;
75
+ private startStreamTimeUs;
76
+ private frameDurationUs;
77
+ private lastFrameTimestamp;
78
+ private bufferSizeHistory;
79
+ private driftCheckInterval;
80
+ private driftThresholdMs;
81
+ private dequeueCount;
82
+ private latencyHistory;
83
+ private latencyHistorySize;
84
+ private stats;
85
+ private logger;
86
+ private onFrameDropped?;
87
+ constructor(config?: SchedulerConfig<T>);
88
+ /** Buffer delay in microseconds */
89
+ private get bufferDelayUs();
90
+ /** Effective drift threshold - scales with buffer target for low-latency mode */
91
+ private get effectiveDriftThresholdMs();
92
+ /** Enqueue a decoded frame with timing information */
93
+ enqueue(frame: T, timestampUs: number, timing: FrameTiming): void;
94
+ /** Dequeue frame for rendering at given real time (milliseconds) */
95
+ dequeue(realTimeMs: number): T | null;
96
+ /** Bypass mode: return latest frame, drop rest */
97
+ private dequeueLatest;
98
+ /** Record latency for a frame */
99
+ private recordLatency;
100
+ /** Get current latency stats */
101
+ getLatencyStats(): LatencyStats | null;
102
+ /** Find index of last frame with timestamp <= target */
103
+ private findBestFrameIndex;
104
+ /** Drop N frames from front of buffer */
105
+ private dropFrames;
106
+ /** Track buffer size for drift detection */
107
+ private trackBufferSize;
108
+ /** Correct timing drift by adjusting start point */
109
+ private correctDrift;
110
+ /** Clear buffer */
111
+ clear(): void;
112
+ /** Set buffer delay in milliseconds */
113
+ setBufferDelay(delayMs: number): void;
114
+ /** Get current buffer delay in milliseconds */
115
+ getBufferDelay(): number;
116
+ /** Get status */
117
+ getStatus(): SchedulerStatus;
118
+ /** Log status */
119
+ logStatus(): void;
120
+ /** Reset statistics */
121
+ resetStats(): void;
122
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * FrameScheduler Tests
3
+ *
4
+ * Tests for the critical frame scheduling and buffering logic.
5
+ * The scheduler is responsible for:
6
+ * - Buffering frames to handle network jitter
7
+ * - Synchronizing stream time to real time
8
+ * - Detecting and correcting drift
9
+ * - Dropping frames appropriately when falling behind
10
+ */
11
+ export {};
@@ -0,0 +1,143 @@
1
+ /**
2
+ * MP4 File Source - Client-side MP4 demuxing using mp4box.js
3
+ *
4
+ * Loads MP4 files and extracts video/audio samples for decoding.
5
+ * Works entirely in the browser without a backend server.
6
+ */
7
+ /**
8
+ * File metadata extracted from MP4
9
+ */
10
+ export interface MP4FileInfo {
11
+ duration: number;
12
+ timescale: number;
13
+ width: number;
14
+ height: number;
15
+ videoCodec: string;
16
+ audioCodec?: string;
17
+ frameRate?: number;
18
+ bitrate?: number;
19
+ audioChannels?: number;
20
+ audioSampleRate?: number;
21
+ }
22
+ /**
23
+ * Sample ready for decoding
24
+ */
25
+ export interface DecodableSample {
26
+ data: Uint8Array;
27
+ timestamp: number;
28
+ duration: number;
29
+ isKeyframe: boolean;
30
+ type: 'video' | 'audio';
31
+ }
32
+ /**
33
+ * Event handlers for MP4FileSource
34
+ */
35
+ export interface MP4FileSourceEvents {
36
+ onReady?: (info: MP4FileInfo) => void;
37
+ onSamples?: (samples: DecodableSample[]) => void;
38
+ onError?: (error: Error) => void;
39
+ onProgress?: (loaded: number, total: number) => void;
40
+ onEnded?: () => void;
41
+ }
42
+ /**
43
+ * MP4 File Source - Demuxes MP4 files in the browser
44
+ */
45
+ export declare class MP4FileSource {
46
+ private mp4File;
47
+ private fileInfo;
48
+ private videoTrackId;
49
+ private audioTrackId;
50
+ private videoTrack;
51
+ private audioTrack;
52
+ private nextVideoSampleIndex;
53
+ private nextAudioSampleIndex;
54
+ private totalVideoSamples;
55
+ private totalAudioSamples;
56
+ private samplesRequested;
57
+ private fileSize;
58
+ private loadedBytes;
59
+ private videoDescription;
60
+ private audioDescription;
61
+ private events;
62
+ constructor(events?: MP4FileSourceEvents);
63
+ /**
64
+ * Get the extracted file info
65
+ */
66
+ getFileInfo(): MP4FileInfo | null;
67
+ /**
68
+ * Get the video codec description (for VideoDecoder.configure)
69
+ */
70
+ getVideoDescription(): Uint8Array | null;
71
+ /**
72
+ * Get the audio codec description (for AudioDecoder.configure)
73
+ */
74
+ getAudioDescription(): Uint8Array | null;
75
+ /**
76
+ * Load an MP4 file from a URL
77
+ */
78
+ loadFromUrl(url: string): Promise<MP4FileInfo>;
79
+ /**
80
+ * Load an MP4 file from a File object (e.g., from file input)
81
+ */
82
+ loadFromFile(file: File): Promise<MP4FileInfo>;
83
+ /**
84
+ * Load from a ReadableStreamDefaultReader (for progressive loading)
85
+ * Uses WritableStream pattern for proper mp4box integration.
86
+ */
87
+ private loadFromStream;
88
+ /**
89
+ * Load from a complete ArrayBuffer
90
+ */
91
+ private loadFromArrayBuffer;
92
+ /**
93
+ * Initialize the mp4box ISOFile
94
+ */
95
+ private initMP4File;
96
+ /**
97
+ * Handle file ready event
98
+ */
99
+ private handleFileReady;
100
+ /**
101
+ * Request samples to be extracted
102
+ */
103
+ private requestSamples;
104
+ /**
105
+ * Handle samples from mp4box
106
+ */
107
+ private handleSamples;
108
+ /**
109
+ * Seek to a specific time in seconds
110
+ * Returns the actual seek time (may be different due to keyframe alignment)
111
+ */
112
+ seek(timeSeconds: number): number;
113
+ /**
114
+ * Get current position info
115
+ */
116
+ getPosition(): {
117
+ currentSample: number;
118
+ totalSamples: number;
119
+ progress: number;
120
+ };
121
+ /**
122
+ * Stop extraction
123
+ */
124
+ stop(): void;
125
+ /**
126
+ * Resume extraction
127
+ */
128
+ start(): void;
129
+ /**
130
+ * Dispose and clean up
131
+ */
132
+ dispose(): void;
133
+ /**
134
+ * Serialize avcC box content for WebCodecs description
135
+ * Format: AVCDecoderConfigurationRecord
136
+ */
137
+ private serializeAvcC;
138
+ /**
139
+ * Serialize hvcC box content for WebCodecs description
140
+ * Format: HEVCDecoderConfigurationRecord
141
+ */
142
+ private serializeHvcC;
143
+ }
@@ -0,0 +1,44 @@
1
+ import { BaseStreamSource } from './stream-source';
2
+
3
+ /**
4
+ * Track configuration for standalone MoQ source
5
+ */
6
+ export interface StandaloneMoQTrack {
7
+ trackName: string;
8
+ priority?: number;
9
+ streamType: 'video' | 'audio' | 'data';
10
+ }
11
+ /**
12
+ * Configuration for standalone MoQ source
13
+ */
14
+ export interface StandaloneMoQConfig {
15
+ relayUrl: string;
16
+ namespace: string;
17
+ subscriptions: StandaloneMoQTrack[];
18
+ reconnectionDelay?: number;
19
+ }
20
+ /**
21
+ * Factory function to create a standalone MoQ source
22
+ */
23
+ export declare function createStandaloneMoQSource(config: StandaloneMoQConfig): StandaloneMoQSource;
24
+ /**
25
+ * Standalone MoQ stream source implementation
26
+ */
27
+ export declare class StandaloneMoQSource extends BaseStreamSource {
28
+ private config;
29
+ private session;
30
+ private trackTypeMap;
31
+ private connecting;
32
+ constructor(config: StandaloneMoQConfig);
33
+ /**
34
+ * Connect to the MoQ relay
35
+ */
36
+ connect(): Promise<void>;
37
+ /**
38
+ * Disconnect from the MoQ relay
39
+ */
40
+ disconnect(): Promise<void>;
41
+ private setupEventListeners;
42
+ private handleIncomingData;
43
+ dispose(): void;
44
+ }
@@ -0,0 +1,73 @@
1
+ import { ParsedData } from '../protocol/sesame-binary-protocol';
2
+
3
+ /**
4
+ * Event data emitted when stream data is received
5
+ */
6
+ export interface StreamDataEvent {
7
+ trackName: string;
8
+ streamType: 'video' | 'audio' | 'data';
9
+ data: ParsedData;
10
+ }
11
+ /**
12
+ * Handler type for stream data events
13
+ */
14
+ export type StreamDataHandler = (event: StreamDataEvent) => void;
15
+ /**
16
+ * Handler type for error events
17
+ */
18
+ export type StreamErrorHandler = (error: Error) => void;
19
+ /**
20
+ * Handler type for connection state events
21
+ */
22
+ export type StreamConnectionHandler = () => void;
23
+ /**
24
+ * Stream source event types
25
+ */
26
+ export type StreamSourceEvent = 'data' | 'error' | 'connected' | 'disconnected';
27
+ /**
28
+ * Interface for stream data sources.
29
+ * Implement this interface to provide video/audio data to the player
30
+ * from any transport mechanism.
31
+ */
32
+ export interface IStreamSource {
33
+ /**
34
+ * Subscribe to stream events
35
+ */
36
+ on(event: 'data', handler: StreamDataHandler): void;
37
+ on(event: 'error', handler: StreamErrorHandler): void;
38
+ on(event: 'connected', handler: StreamConnectionHandler): void;
39
+ on(event: 'disconnected', handler: StreamConnectionHandler): void;
40
+ /**
41
+ * Unsubscribe from stream events
42
+ */
43
+ off(event: StreamSourceEvent, handler: Function): void;
44
+ /**
45
+ * Current connection state (optional)
46
+ */
47
+ readonly connected?: boolean;
48
+ /**
49
+ * Request a keyframe from the stream (for live streams)
50
+ */
51
+ requestKeyframe?(): void;
52
+ /**
53
+ * Dispose the stream source and clean up resources
54
+ */
55
+ dispose?(): void;
56
+ }
57
+ /**
58
+ * Base class for implementing stream sources with event handling
59
+ */
60
+ export declare abstract class BaseStreamSource implements IStreamSource {
61
+ protected handlers: Map<string, Set<Function>>;
62
+ protected _connected: boolean;
63
+ get connected(): boolean;
64
+ on(event: 'data', handler: StreamDataHandler): void;
65
+ on(event: 'error', handler: StreamErrorHandler): void;
66
+ on(event: 'connected', handler: StreamConnectionHandler): void;
67
+ on(event: 'disconnected', handler: StreamConnectionHandler): void;
68
+ off(event: StreamSourceEvent, handler: Function): void;
69
+ protected emit(event: 'data', data: StreamDataEvent): void;
70
+ protected emit(event: 'error', error: Error): void;
71
+ protected emit(event: 'connected' | 'disconnected'): void;
72
+ dispose(): void;
73
+ }
@@ -0,0 +1,120 @@
1
+ import { BaseStreamSource } from './stream-source';
2
+
3
+ /**
4
+ * Configuration for WebSocket stream source
5
+ */
6
+ export interface WebSocketSourceConfig {
7
+ /** WebSocket URL (ws:// or wss://) */
8
+ url?: string;
9
+ /** Auto-construct URL from current page location */
10
+ useCurrentHost?: boolean;
11
+ /** API path for video endpoint */
12
+ apiPath?: string;
13
+ /** Client ID for the connection */
14
+ clientId?: string;
15
+ /** Connection timeout in milliseconds */
16
+ timeout?: number;
17
+ /** Enable automatic reconnection */
18
+ autoReconnect?: boolean;
19
+ /** Reconnection delay in milliseconds */
20
+ reconnectDelay?: number;
21
+ }
22
+ /**
23
+ * Video metadata returned from the server
24
+ */
25
+ export interface VideoMetadata {
26
+ width: number;
27
+ height: number;
28
+ frameRate?: number;
29
+ duration?: number;
30
+ codec?: string;
31
+ }
32
+ /**
33
+ * WebSocket-based stream source for live and file-based video playback.
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * const source = new WebSocketSource({ useCurrentHost: true });
38
+ * await source.connect();
39
+ * await source.loadLive('my-stream');
40
+ *
41
+ * source.on('data', (event) => {
42
+ * if (event.streamType === 'video') {
43
+ * // Handle video frame
44
+ * }
45
+ * });
46
+ * ```
47
+ */
48
+ export declare class WebSocketSource extends BaseStreamSource {
49
+ private webSocket;
50
+ private messageWaiters;
51
+ private requestId;
52
+ private timeoutCheckInterval;
53
+ private ignoreCmdsBelow;
54
+ private isLiveStream;
55
+ private config;
56
+ private lastKeyframeRequest;
57
+ private currentTrackName;
58
+ private reconnectTimeout;
59
+ private disposed;
60
+ constructor(config?: WebSocketSourceConfig);
61
+ /**
62
+ * Get the WebSocket URL based on configuration
63
+ */
64
+ private getWebSocketUrl;
65
+ /**
66
+ * Connect to the WebSocket server
67
+ */
68
+ connect(): Promise<void>;
69
+ /**
70
+ * Disconnect from the WebSocket server
71
+ */
72
+ disconnect(): void;
73
+ /**
74
+ * Load a live stream by ID
75
+ */
76
+ loadLive(streamId: string): Promise<VideoMetadata>;
77
+ /**
78
+ * Load a video file
79
+ */
80
+ loadFile(project: string, filename: string): Promise<VideoMetadata>;
81
+ /**
82
+ * Seek to a position in the video (file playback only)
83
+ */
84
+ seek(positionMs: number): Promise<void>;
85
+ /**
86
+ * Request more packets from the server (file playback)
87
+ */
88
+ read(packetCount: number): Promise<void>;
89
+ /**
90
+ * Unload the current stream
91
+ */
92
+ unload(): Promise<void>;
93
+ /**
94
+ * Request a keyframe (live streams only)
95
+ */
96
+ requestKeyframe(): void;
97
+ /**
98
+ * Flush pending requests (useful when seeking)
99
+ */
100
+ flush(): void;
101
+ /**
102
+ * Dispose the stream source
103
+ */
104
+ dispose(): void;
105
+ private request;
106
+ private waitForResponse;
107
+ private handleMessage;
108
+ private handleResponse;
109
+ private handleBinaryData;
110
+ private handleDisconnect;
111
+ private scheduleReconnect;
112
+ private stopReconnect;
113
+ private startTimeoutChecker;
114
+ private stopTimeoutChecker;
115
+ private clearWaiters;
116
+ }
117
+ /**
118
+ * Factory function to create a WebSocket stream source
119
+ */
120
+ export declare function createWebSocketSource(config?: WebSocketSourceConfig): WebSocketSource;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Shared type definitions for the video player library
3
+ */
4
+ /**
5
+ * YUV frame data from WASM decoder
6
+ */
7
+ export interface YUVFrame {
8
+ y: Uint8Array;
9
+ u: Uint8Array;
10
+ v: Uint8Array;
11
+ width: number;
12
+ height: number;
13
+ chromaStride: number;
14
+ chromaHeight: number;
15
+ timestamp: number;
16
+ /** Compatibility with VideoFrame interface */
17
+ close: () => void;
18
+ }
19
+ /**
20
+ * Stream metadata received from the source
21
+ */
22
+ export interface StreamMetadata {
23
+ width: number;
24
+ height: number;
25
+ frameRate?: number;
26
+ codec?: string;
27
+ bitDepth?: number;
28
+ }
29
+ /**
30
+ * Decoder type preference
31
+ */
32
+ export type PreferredDecoder = 'webcodecs-hw' | 'webcodecs-sw' | 'wasm';
33
+ /**
34
+ * Logger interface for customizable logging
35
+ */
36
+ export interface Logger {
37
+ debug(message: string): void;
38
+ info(message: string): void;
39
+ warn(message: string): void;
40
+ error(message: string): void;
41
+ }
42
+ /**
43
+ * Default console logger
44
+ */
45
+ export declare const consoleLogger: Logger;
46
+ /**
47
+ * Silent logger (no output)
48
+ */
49
+ export declare const silentLogger: Logger;
50
+ /**
51
+ * Augment WebCodecs VideoDecoderConfig with latencyMode
52
+ * (not yet in standard TypeScript DOM types)
53
+ */
54
+ declare global {
55
+ interface VideoDecoderConfig {
56
+ latencyMode?: 'quality' | 'realtime';
57
+ }
58
+ }
@@ -0,0 +1,2 @@
1
+ declare const _default: import('vite').UserConfig;
2
+ export default _default;