@mantequilla-soft/3speak-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,400 @@
1
+ /** Video source with CDN fallback chain */
2
+ interface VideoSource {
3
+ /** Primary HLS URL (.m3u8) */
4
+ url: string;
5
+ /** Fallback HLS URLs in priority order */
6
+ fallbacks?: string[];
7
+ /** Poster/thumbnail image URL */
8
+ poster?: string;
9
+ }
10
+ /** Video metadata returned by the 3Speak API */
11
+ interface VideoMetadata {
12
+ owner: string;
13
+ permlink: string;
14
+ title: string;
15
+ status: string;
16
+ videoUrl: string;
17
+ videoUrlFallback1: string | null;
18
+ videoUrlFallback2: string | null;
19
+ videoUrlFallback3: string | null;
20
+ thumbnail: string | null;
21
+ duration: number;
22
+ views: number;
23
+ short: boolean;
24
+ isPlaceholder: boolean;
25
+ }
26
+ /** Player state snapshot */
27
+ interface PlayerState {
28
+ currentTime: number;
29
+ duration: number;
30
+ paused: boolean;
31
+ muted: boolean;
32
+ volume: number;
33
+ ready: boolean;
34
+ loading: boolean;
35
+ isVertical: boolean | null;
36
+ videoWidth: number;
37
+ videoHeight: number;
38
+ /** Buffered progress (0-1) */
39
+ buffered: number;
40
+ /** Picture-in-Picture active */
41
+ pip: boolean;
42
+ /** Fullscreen active */
43
+ fullscreen: boolean;
44
+ /** Audio-only mode active */
45
+ audioOnly: boolean;
46
+ }
47
+ /** HLS quality level (hls.js only) */
48
+ interface QualityLevel {
49
+ index: number;
50
+ height: number;
51
+ width: number;
52
+ bitrate: number;
53
+ }
54
+ /** Events emitted by the player */
55
+ interface PlayerEvents {
56
+ ready: (state: {
57
+ isVertical: boolean;
58
+ width: number;
59
+ height: number;
60
+ }) => void;
61
+ play: () => void;
62
+ pause: () => void;
63
+ ended: () => void;
64
+ timeupdate: (state: {
65
+ currentTime: number;
66
+ duration: number;
67
+ paused: boolean;
68
+ }) => void;
69
+ error: (error: {
70
+ message: string;
71
+ code?: number;
72
+ fatal: boolean;
73
+ }) => void;
74
+ /** Fired when falling back to an alternate CDN source */
75
+ fallback: (info: {
76
+ url: string;
77
+ index: number;
78
+ }) => void;
79
+ /** Fired when video dimensions are known */
80
+ resize: (info: {
81
+ width: number;
82
+ height: number;
83
+ isVertical: boolean;
84
+ }) => void;
85
+ /** Loading state changed */
86
+ loading: (isLoading: boolean) => void;
87
+ /** Buffered progress changed (0-1) */
88
+ buffered: (progress: number) => void;
89
+ /** Picture-in-Picture state changed */
90
+ pip: (active: boolean) => void;
91
+ /** Fullscreen state changed */
92
+ fullscreen: (active: boolean) => void;
93
+ /** Quality level changed (hls.js only) */
94
+ qualitychange: (level: QualityLevel) => void;
95
+ /** Video element visibility changed (IntersectionObserver) */
96
+ visibility: (visible: boolean) => void;
97
+ /** Playback resumed from saved position */
98
+ resume: (info: {
99
+ time: number;
100
+ ref: string;
101
+ }) => void;
102
+ }
103
+ /** Configuration for creating a player instance */
104
+ interface PlayerConfig {
105
+ /** 3Speak player API base URL (default: https://play.3speak.tv) */
106
+ apiBase?: string;
107
+ /** Enable debug logging */
108
+ debug?: boolean;
109
+ /** Start muted (required for autoplay on most browsers) */
110
+ muted?: boolean;
111
+ /** Loop playback */
112
+ loop?: boolean;
113
+ /** Show poster/thumbnail image during loading (default: true) */
114
+ poster?: boolean;
115
+ /** hls.js configuration overrides */
116
+ hlsConfig?: Record<string, unknown>;
117
+ /** Start in audio-only mode */
118
+ audioOnly?: boolean;
119
+ /** Auto-pause when video scrolls out of viewport (IntersectionObserver) */
120
+ autopause?: boolean;
121
+ /** Resume playback from last position (uses localStorage) */
122
+ resume?: boolean;
123
+ }
124
+ /** Platform detection results */
125
+ interface PlatformInfo {
126
+ isIOS: boolean;
127
+ isSafari: boolean;
128
+ supportsNativeHLS: boolean;
129
+ supportsMSE: boolean;
130
+ supportsHlsJs: boolean;
131
+ }
132
+ /** Type-safe event emitter */
133
+ type EventHandler<T extends keyof PlayerEvents> = PlayerEvents[T];
134
+ type EventUnsubscribe = () => void;
135
+
136
+ /**
137
+ * 3Speak video API client.
138
+ * Fetches video metadata and HLS URLs from the snapie player API.
139
+ */
140
+ declare class ThreeSpeakApi {
141
+ private apiBase;
142
+ private debug;
143
+ constructor(apiBase?: string, debug?: boolean);
144
+ private log;
145
+ /**
146
+ * Fetch full video metadata from the embed API.
147
+ * @param author - Hive account name
148
+ * @param permlink - 3Speak video permlink
149
+ */
150
+ fetchVideoMetadata(author: string, permlink: string): Promise<VideoMetadata>;
151
+ /**
152
+ * Fetch just the HLS source URLs (convenience wrapper).
153
+ * Returns a VideoSource ready to pass to the player.
154
+ */
155
+ fetchSource(author: string, permlink: string): Promise<VideoSource>;
156
+ /**
157
+ * Prefetch an HLS manifest AND its first video segment to warm CDN + browser cache.
158
+ * This means when hls.js actually starts playback, the first segment is already cached.
159
+ */
160
+ prefetchManifest(hlsUrl: string): Promise<void>;
161
+ /**
162
+ * Parse a media playlist and prefetch its first .ts/.m4s segment.
163
+ */
164
+ private prefetchFirstSegment;
165
+ /**
166
+ * Increment view count for a video.
167
+ */
168
+ recordView(owner: string, permlink: string, type?: string): Promise<void>;
169
+ }
170
+ /**
171
+ * Convert API metadata to a VideoSource with fallback chain.
172
+ */
173
+ declare function metadataToSource(meta: VideoMetadata): VideoSource;
174
+
175
+ /**
176
+ * 3Speak HLS Video Player.
177
+ *
178
+ * Framework-agnostic — works with any <video> element.
179
+ * Handles HLS playback via native Safari HLS or hls.js (Chrome/Firefox/Edge).
180
+ *
181
+ * @example
182
+ * ```js
183
+ * import { Player } from '@mantequilla-soft/3speak-player';
184
+ *
185
+ * const player = new Player({ muted: true, loop: true });
186
+ * player.attach(document.querySelector('video'));
187
+ * player.load('@author/permlink');
188
+ * player.on('ready', ({ isVertical }) => console.log('vertical?', isVertical));
189
+ * ```
190
+ */
191
+ declare class Player {
192
+ private config;
193
+ private api;
194
+ private platform;
195
+ private video;
196
+ private hls;
197
+ private listeners;
198
+ private fallbackIndex;
199
+ private fallbacks;
200
+ private _ready;
201
+ private _destroyed;
202
+ private _audioOnly;
203
+ private _currentRef;
204
+ private _resumeSaveTimer;
205
+ private _observer;
206
+ private cleanupFns;
207
+ constructor(config?: PlayerConfig);
208
+ private log;
209
+ /**
210
+ * Subscribe to a player event.
211
+ * @returns Unsubscribe function
212
+ */
213
+ on<T extends keyof PlayerEvents>(event: T, handler: EventHandler<T>): EventUnsubscribe;
214
+ /** Unsubscribe from a player event. */
215
+ off<T extends keyof PlayerEvents>(event: T, handler: EventHandler<T>): void;
216
+ /** Subscribe to a player event, auto-unsubscribe after first call. */
217
+ once<T extends keyof PlayerEvents>(event: T, handler: EventHandler<T>): EventUnsubscribe;
218
+ private emit;
219
+ /**
220
+ * Attach the player to a <video> element.
221
+ * Sets required attributes (playsinline, etc.) automatically.
222
+ */
223
+ attach(element: HTMLVideoElement): this;
224
+ /**
225
+ * Detach from the current video element and clean up HLS.
226
+ */
227
+ detach(): this;
228
+ /**
229
+ * Load a video by author/permlink (fetches HLS URL from 3Speak API).
230
+ * @param ref - Either "author/permlink" or "@author/permlink"
231
+ */
232
+ load(ref: string): Promise<this>;
233
+ /**
234
+ * Load a video from a direct VideoSource.
235
+ */
236
+ load(source: VideoSource): Promise<this>;
237
+ play(): Promise<void>;
238
+ pause(): void;
239
+ togglePlay(): void;
240
+ seek(time: number): void;
241
+ /** Set muted state */
242
+ setMuted(muted: boolean): void;
243
+ /** Set volume (0-1) */
244
+ setVolume(volume: number): void;
245
+ /** Set loop mode */
246
+ setLoop(loop: boolean): void;
247
+ /** Set playback rate */
248
+ setPlaybackRate(rate: number): void;
249
+ /** Toggle Picture-in-Picture mode */
250
+ togglePip(): Promise<void>;
251
+ /** Toggle fullscreen mode */
252
+ toggleFullscreen(): Promise<void>;
253
+ /** Get available quality levels (hls.js only, empty for native HLS) */
254
+ getQualities(): QualityLevel[];
255
+ /** Set quality level (-1 for auto, hls.js only) */
256
+ setQuality(index: number): void;
257
+ /** Get current quality level index (-1 = auto, hls.js only) */
258
+ getCurrentQuality(): number;
259
+ /**
260
+ * Get thumbnail at a given time. Currently returns the poster image.
261
+ * Reserved for future sprite sheet support.
262
+ */
263
+ getThumbnailAt(_time: number): string | null;
264
+ /** Set audio-only mode (hides video, keeps audio playing) */
265
+ setAudioOnly(enabled: boolean): void;
266
+ /** Enable auto-pause when video scrolls out of viewport */
267
+ enableAutopause(): void;
268
+ /** Disable auto-pause on scroll out */
269
+ disableAutopause(): void;
270
+ /** Clear saved resume position for a video ref. Clears current video if no ref given. */
271
+ clearResumePosition(ref?: string): void;
272
+ /** Get current player state snapshot */
273
+ getState(): PlayerState;
274
+ /** Whether the player has loaded metadata and is ready to play */
275
+ get ready(): boolean;
276
+ /** Whether this player instance has been destroyed */
277
+ get destroyed(): boolean;
278
+ /** The underlying <video> element (if attached) */
279
+ get element(): HTMLVideoElement | null;
280
+ /** Access the 3Speak API client */
281
+ get apiClient(): ThreeSpeakApi;
282
+ /**
283
+ * Destroy the player and release all resources.
284
+ * Cannot be used after this.
285
+ */
286
+ destroy(): void;
287
+ private loadSource;
288
+ private tryFallback;
289
+ private destroyHls;
290
+ private setupAutopause;
291
+ private destroyAutopause;
292
+ private saveResumePosition;
293
+ private restoreResumePosition;
294
+ private bindVideoEvents;
295
+ }
296
+
297
+ /**
298
+ * Manages a pool of Player instances for feed/shorts-style UIs.
299
+ *
300
+ * Handles:
301
+ * - Creating/recycling players for visible videos
302
+ * - Pausing all except the active player
303
+ * - Manifest prefetching for upcoming videos on iOS
304
+ * - Cleaning up off-screen players
305
+ *
306
+ * @example
307
+ * ```js
308
+ * const pool = new PlayerPool({ muted: true, loop: true });
309
+ *
310
+ * // Add videos as they enter the viewport
311
+ * pool.add('vid-1', videoElement1, { url: 'https://...' });
312
+ * pool.add('vid-2', videoElement2, { url: 'https://...' });
313
+ *
314
+ * // Activate the current video (pauses all others)
315
+ * pool.activate('vid-1');
316
+ *
317
+ * // Remove when scrolled out of range
318
+ * pool.remove('vid-2');
319
+ *
320
+ * // Clean up
321
+ * pool.destroy();
322
+ * ```
323
+ */
324
+ declare class PlayerPool {
325
+ private players;
326
+ private activeId;
327
+ private config;
328
+ private api;
329
+ private platform;
330
+ constructor(config?: PlayerConfig);
331
+ /**
332
+ * Add a player to the pool.
333
+ * @param id - Unique identifier for this video slot
334
+ * @param element - The <video> element to attach to
335
+ * @param source - Video source (optional — can call load() later)
336
+ */
337
+ add(id: string, element: HTMLVideoElement, source?: VideoSource): Player;
338
+ /**
339
+ * Add a player by 3Speak author/permlink (auto-fetches HLS URL).
340
+ */
341
+ addByRef(id: string, element: HTMLVideoElement, author: string, permlink: string): Promise<Player>;
342
+ /**
343
+ * Get a player by id.
344
+ */
345
+ get(id: string): Player | undefined;
346
+ /**
347
+ * Remove a player from the pool and destroy it.
348
+ */
349
+ remove(id: string): void;
350
+ /**
351
+ * Activate a player (play it, pause all others).
352
+ */
353
+ activate(id: string): void;
354
+ /** Pause all players. */
355
+ pauseAll(): void;
356
+ /** Set muted state on all players. */
357
+ setAllMuted(muted: boolean): void;
358
+ /** Set loop on all players. */
359
+ setAllLoop(loop: boolean): void;
360
+ /** Get the currently active player. */
361
+ getActive(): Player | undefined;
362
+ /** Get the active player's id. */
363
+ get activePlayerId(): string | null;
364
+ /** Number of players in the pool. */
365
+ get size(): number;
366
+ /** All player ids in the pool. */
367
+ get ids(): string[];
368
+ /**
369
+ * Prefetch an HLS manifest (warms CDN, ~1-5KB).
370
+ * Especially useful on iOS where we can't preload actual video data.
371
+ */
372
+ prefetch(hlsUrl: string): Promise<void>;
373
+ /**
374
+ * Prefetch a manifest by author/permlink.
375
+ */
376
+ prefetchByRef(author: string, permlink: string): Promise<void>;
377
+ /**
378
+ * Retain only the given ids, destroy all others.
379
+ * Useful for keeping a sliding window of players around the current index.
380
+ */
381
+ retainOnly(ids: Set<string> | string[]): void;
382
+ /**
383
+ * Destroy all players and the pool.
384
+ */
385
+ destroy(): void;
386
+ }
387
+
388
+ /**
389
+ * Detect platform capabilities for HLS playback strategy.
390
+ * Results are cached after first call.
391
+ */
392
+ declare function detectPlatform(): PlatformInfo;
393
+ /**
394
+ * Detect whether the browser allows autoplay.
395
+ * @param muted - Test muted autoplay (default: true). Unmuted autoplay is blocked on most browsers.
396
+ * @returns Promise resolving to true if autoplay is allowed.
397
+ */
398
+ declare function canAutoplay(muted?: boolean): Promise<boolean>;
399
+
400
+ export { type EventHandler, type EventUnsubscribe, type PlatformInfo, Player, type PlayerConfig, type PlayerEvents, PlayerPool, type PlayerState, type QualityLevel, ThreeSpeakApi, type VideoMetadata, type VideoSource, canAutoplay, detectPlatform, metadataToSource };