@vplayer/core 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,14 @@
1
+ export interface MediaCapabilitiesSnapshot {
2
+ mse: boolean;
3
+ nativeHls: boolean;
4
+ fullscreen: boolean;
5
+ pictureInPicture: boolean;
6
+ textTracks: boolean;
7
+ }
8
+ export declare function canUseMSE(): boolean;
9
+ export declare function canUseNativeHLS(video?: HTMLVideoElement | null): boolean;
10
+ export declare function canUseFullscreen(container?: HTMLElement | null): boolean;
11
+ export declare function canUsePictureInPicture(video?: HTMLVideoElement | null): boolean;
12
+ export declare function canUseTextTracks(video?: HTMLVideoElement | null): boolean;
13
+ export declare function getMediaCapabilities(video?: HTMLVideoElement | null, container?: HTMLElement | null): MediaCapabilitiesSnapshot;
14
+ export declare function isFiniteDuration(value: number): boolean;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * BaseMediaEngine — Shared boilerplate for MediaEngine implementations.
3
+ *
4
+ * All playback state is read from the underlying HTMLVideoElement.
5
+ * Subclasses override load() and destroy() for custom source handling
6
+ * (HLS.js, DASH.js, etc.) and use this.emit() for custom events.
7
+ */
8
+ import type { MediaEngine, MediaEngineError, MediaEngineEvent, MediaEngineEventHandler } from './types';
9
+ export declare abstract class BaseMediaEngine implements MediaEngine {
10
+ readonly element: HTMLVideoElement;
11
+ protected destroyed: boolean;
12
+ private listeners;
13
+ private domListeners;
14
+ constructor(video: HTMLVideoElement);
15
+ play(): Promise<void>;
16
+ pause(): void;
17
+ seek(time: number): void;
18
+ setVolume(volume: number): void;
19
+ setMuted(muted: boolean): void;
20
+ setPlaybackRate(rate: number): void;
21
+ setLooping(loop: boolean): void;
22
+ load(): void;
23
+ requestPictureInPicture(): Promise<void>;
24
+ exitPictureInPicture(): Promise<void>;
25
+ screenshot(): Promise<Blob | null>;
26
+ get currentTime(): number;
27
+ get duration(): number;
28
+ get volume(): number;
29
+ get muted(): boolean;
30
+ get playbackRate(): number;
31
+ get paused(): boolean;
32
+ get ended(): boolean;
33
+ get looping(): boolean;
34
+ get buffered(): TimeRanges;
35
+ get videoWidth(): number;
36
+ get videoHeight(): number;
37
+ get error(): MediaEngineError | null;
38
+ on(event: MediaEngineEvent, handler: MediaEngineEventHandler): () => void;
39
+ protected emit(event: MediaEngineEvent, ...args: any[]): void;
40
+ private attachDOMListeners;
41
+ private detachDOMListeners;
42
+ destroy(): void;
43
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * DashMediaEngine — MediaEngine backed by dash.js (MPEG-DASH).
3
+ *
4
+ * Uses dash.js to play DASH streams via MSE.
5
+ *
6
+ * Usage:
7
+ * ```ts
8
+ * const player = createPlayer({
9
+ * engine: (video) => new DashMediaEngine(video, {
10
+ * src: 'https://example.com/stream.mpd',
11
+ * }),
12
+ * })
13
+ * ```
14
+ */
15
+ import type { MediaPlayerSettingClass } from 'dashjs';
16
+ import { BaseMediaEngine } from './base-engine';
17
+ export interface DashMediaEngineOptions {
18
+ /** Source URL (.mpd manifest). */
19
+ src?: string;
20
+ /** Optional dash.js settings (MediaPlayerSettingClass). */
21
+ dashConfig?: MediaPlayerSettingClass;
22
+ }
23
+ export declare class DashMediaEngine extends BaseMediaEngine {
24
+ private dashPlayer;
25
+ private dashSrc;
26
+ private dashConfig?;
27
+ constructor(video: HTMLVideoElement, options?: DashMediaEngineOptions);
28
+ private initDash;
29
+ private initDashAsync;
30
+ load(): void;
31
+ destroy(): void;
32
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * HlsMediaEngine — MediaEngine backed by hls.js.
3
+ *
4
+ * Uses HLS.js to play HTTP Live Streaming (HLS) sources via MSE.
5
+ * Falls through to native playback in browsers with native HLS support (Safari).
6
+ *
7
+ * Usage:
8
+ * ```ts
9
+ * const player = createPlayer({
10
+ * engine: (video) => new HlsMediaEngine(video, {
11
+ * src: 'https://example.com/stream.m3u8',
12
+ * }),
13
+ * })
14
+ * ```
15
+ */
16
+ import { BaseMediaEngine } from './base-engine';
17
+ export interface HlsMediaEngineOptions {
18
+ /** Source URL (.m3u8 playlist). */
19
+ src?: string;
20
+ /** Optional hls.js configuration (Partial<import('hls.js').HlsConfig>). */
21
+ hlsConfig?: Partial<import('hls.js').HlsConfig>;
22
+ }
23
+ export declare class HlsMediaEngine extends BaseMediaEngine {
24
+ private hls;
25
+ private hlsSrc;
26
+ private hlsConfig;
27
+ constructor(video: HTMLVideoElement, options?: HlsMediaEngineOptions);
28
+ private initHls;
29
+ private initHlsAsync;
30
+ load(): void;
31
+ destroy(): void;
32
+ }
@@ -0,0 +1,7 @@
1
+ export type { MediaEngine, MediaEngineDimensions, MediaEngineError, MediaEngineEvent, MediaEngineEventHandler, } from './types';
2
+ export { BaseMediaEngine } from './base-engine';
3
+ export { NativeVideoEngine } from './native-engine';
4
+ export { HlsMediaEngine } from './hls-engine';
5
+ export type { HlsMediaEngineOptions } from './hls-engine';
6
+ export { DashMediaEngine } from './dash-engine';
7
+ export type { DashMediaEngineOptions } from './dash-engine';
@@ -0,0 +1,9 @@
1
+ /**
2
+ * NativeVideoEngine — MediaEngine backed by an HTMLVideoElement.
3
+ *
4
+ * The default engine used by createPlayer() when no custom engine is provided.
5
+ * Delegates all playback to the native <video> element.
6
+ */
7
+ import { BaseMediaEngine } from './base-engine';
8
+ export declare class NativeVideoEngine extends BaseMediaEngine {
9
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * MediaEngine — Strategy interface for media playback.
3
+ *
4
+ * Abstracts away the underlying media source (native <video>, HLS, DASH, etc.)
5
+ * so that the player core operates against a stable contract.
6
+ *
7
+ * Implementations own their event listeners and must clean up on destroy().
8
+ */
9
+ export interface MediaEngineDimensions {
10
+ width: number;
11
+ height: number;
12
+ }
13
+ export interface MediaEngineError {
14
+ message: string;
15
+ code?: number;
16
+ }
17
+ /** Events emitted by every MediaEngine implementation. */
18
+ export type MediaEngineEvent = 'play' | 'pause' | 'ended' | 'timeupdate' | 'loadedmetadata' | 'loadeddata' | 'durationchange' | 'progress' | 'seeking' | 'seeked' | 'waiting' | 'stalled' | 'playing' | 'canplay' | 'canplaythrough' | 'volumechange' | 'ratechange' | 'playblocked' | 'error' | (string & {});
19
+ export type MediaEngineEventHandler = (...args: any[]) => void;
20
+ export interface MediaEngine {
21
+ /** The underlying media element (e.g. HTMLVideoElement). */
22
+ readonly element: HTMLElement;
23
+ play(): Promise<void>;
24
+ pause(): void;
25
+ seek(time: number): void;
26
+ setVolume(volume: number): void;
27
+ setMuted(muted: boolean): void;
28
+ setPlaybackRate(rate: number): void;
29
+ setLooping(loop: boolean): void;
30
+ /** Reload the current source. */
31
+ load(): void;
32
+ requestPictureInPicture(): Promise<void>;
33
+ exitPictureInPicture(): Promise<void>;
34
+ /** Capture a still frame from the current video frame. */
35
+ screenshot(): Promise<Blob | null>;
36
+ readonly currentTime: number;
37
+ readonly duration: number;
38
+ readonly volume: number;
39
+ readonly muted: boolean;
40
+ readonly playbackRate: number;
41
+ readonly paused: boolean;
42
+ readonly ended: boolean;
43
+ readonly looping: boolean;
44
+ readonly buffered: TimeRanges;
45
+ readonly videoWidth: number;
46
+ readonly videoHeight: number;
47
+ readonly error: MediaEngineError | null;
48
+ on(event: MediaEngineEvent, handler: MediaEngineEventHandler): () => void;
49
+ destroy(): void;
50
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @vplayer/core — Player factory.
3
+ *
4
+ * Architecture:
5
+ * createPlayer() → PlayerInstance
6
+ * ├── store → Reactive state (TanStack Store)
7
+ * ├── engine → Media playback abstraction (MediaEngine)
8
+ * ├── events → Typed event bus for plugin communication
9
+ * ├── storage → Persistence layer (localStorage + fallback)
10
+ * ├── i18n → Internationalization
11
+ * └── hotkeys → Keyboard shortcut registry
12
+ *
13
+ * The player delegates all media I/O to a swappable MediaEngine.
14
+ * At mount time, a NativeVideoEngine wraps the <video> element.
15
+ * Custom engines (HLS, DASH, mock) can be injected for testing or
16
+ * advanced playback scenarios.
17
+ */
18
+ import type { PlayerOptions, PlayerInstance } from './types';
19
+ export declare function createPlayer(options: PlayerOptions): PlayerInstance;
@@ -0,0 +1,86 @@
1
+ import type { Store } from '@tanstack/store';
2
+ import type { EventBus } from './event-bus';
3
+ import type { HotkeyRegistry, HotkeyBinding } from './hotkey-registry';
4
+ import type { I18n } from './i18n';
5
+ import type { Storage } from './storage';
6
+ export interface RemoteRef {
7
+ play: () => void;
8
+ pause: () => void;
9
+ togglePlay: () => void;
10
+ seek: (time: number) => void;
11
+ skip: (seconds: number) => void;
12
+ setVolume: (v: number) => void;
13
+ toggleMute: () => void;
14
+ setPlaybackRate: (rate: number) => void;
15
+ toggleFullscreen: () => void;
16
+ togglePiP: () => void;
17
+ takeScreenshot: () => void;
18
+ setFlip: (flip: FlipState) => void;
19
+ setAspectRatio: (ratio: AspectRatioState) => void;
20
+ cycleAspectRatio: () => void;
21
+ toggleLoop: () => void;
22
+ toggleInfoPanel: () => void;
23
+ }
24
+ export interface PlayerContextRef {
25
+ containerRef: {
26
+ readonly current: HTMLDivElement | null;
27
+ };
28
+ videoRef: {
29
+ readonly current: HTMLVideoElement | null;
30
+ };
31
+ }
32
+ export interface ControlRegistration {
33
+ name: string;
34
+ position: 'left' | 'right' | 'top' | 'center';
35
+ index: number;
36
+ render: unknown;
37
+ }
38
+ export interface SettingItem {
39
+ label: string;
40
+ value: string;
41
+ }
42
+ export interface SettingRegistration {
43
+ name: string;
44
+ label: string;
45
+ items?: SettingItem[];
46
+ render?: unknown;
47
+ }
48
+ export interface LayerRegistration {
49
+ name: string;
50
+ render: unknown;
51
+ }
52
+ export interface ContextMenuItem {
53
+ label: string;
54
+ onAction: () => void;
55
+ separator?: boolean;
56
+ disabled?: boolean;
57
+ }
58
+ export interface ContextMenuRegistration {
59
+ name: string;
60
+ items: ContextMenuItem[];
61
+ }
62
+ export type FlipState = 'normal' | 'horizontal' | 'vertical';
63
+ export type AspectRatioState = 'default' | '16:9' | '4:3' | '21:9' | 'cover' | 'fill';
64
+ export interface PluginAPI {
65
+ readonly name: string;
66
+ readonly store: Store<any>;
67
+ readonly remote: RemoteRef;
68
+ readonly events: EventBus;
69
+ readonly storage: Storage;
70
+ readonly hotkeys: HotkeyRegistry;
71
+ readonly i18n: I18n;
72
+ readonly context: PlayerContextRef;
73
+ addControl(def: ControlRegistration): () => void;
74
+ removeControl(name: string): void;
75
+ addSetting(def: SettingRegistration): () => void;
76
+ removeSetting(name: string): void;
77
+ addLayer(def: LayerRegistration): () => void;
78
+ removeLayer(name: string): void;
79
+ addHotkey(binding: HotkeyBinding): () => void;
80
+ addContextMenuItems(items: ContextMenuItem[]): () => void;
81
+ notify(message: string, duration?: number): void;
82
+ }
83
+ export interface PlayerPlugin {
84
+ name: string;
85
+ setup?: (api: PluginAPI) => void | (() => void);
86
+ }
@@ -0,0 +1,10 @@
1
+ import type { MediaEngine } from './media-engine';
2
+ import type { PlayerOptions } from './types';
3
+ export type SourceKind = 'native' | 'hls' | 'dash' | 'unknown';
4
+ export interface PlayerSource {
5
+ src: string;
6
+ type?: string;
7
+ }
8
+ export declare function detectSourceKind(source: PlayerSource): SourceKind;
9
+ export declare function toPlayerSource(options: Pick<PlayerOptions, 'src' | 'type'>): PlayerSource;
10
+ export declare function createResolvedMediaEngine(video: HTMLVideoElement, options: Pick<PlayerOptions, 'src' | 'type' | 'engine' | 'hlsConfig' | 'dashConfig'>): MediaEngine;
@@ -0,0 +1,134 @@
1
+ /**
2
+ * State Slices — Logical groupings of MediaState properties.
3
+ *
4
+ * These types DOCUMENT the slice boundaries within the flat MediaState.
5
+ * At runtime the store keeps a flat state for backward compat, but the
6
+ * slice types make ownership clear and enable future per-slice subscriptions.
7
+ *
8
+ * Migration path toward separate sub-stores:
9
+ * 1. Define slices here (done ✓)
10
+ * 2. Use createMediaStore() for initialization (done ✓)
11
+ * 3. Eventually migrate to separate Store instances per slice
12
+ */
13
+ import { Store } from '@tanstack/store';
14
+ import type { MediaCapabilitiesSnapshot } from '../media-capabilities';
15
+ import type { ControlRegistration, ContextMenuItem, FlipState, AspectRatioState } from '../plugin-api';
16
+ import type { LayerRegistration, SettingRegistration } from '../plugin-api';
17
+ import type { PlayerSource } from '../source-resolver';
18
+ import type { SubtitleTrack, ThumbnailCue } from '../subtitle-parser';
19
+ export type PlaybackStatus = 'idle' | 'loading' | 'ready' | 'playing' | 'paused' | 'buffering' | 'seeking' | 'ended' | 'error';
20
+ export interface MediaSlice {
21
+ status: PlaybackStatus;
22
+ source: PlayerSource | null;
23
+ isPlaying: boolean;
24
+ isPaused: boolean;
25
+ isBuffering: boolean;
26
+ isEnded: boolean;
27
+ currentTime: number;
28
+ duration: number;
29
+ bufferedPercent: number;
30
+ playbackRate: number;
31
+ isLive: boolean;
32
+ }
33
+ export interface AudioSlice {
34
+ volume: number;
35
+ isMuted: boolean;
36
+ }
37
+ export interface PreferencesSlice {
38
+ isLooping: boolean;
39
+ flip: FlipState;
40
+ aspectRatio: AspectRatioState;
41
+ activeSubtitle: SubtitleTrack | null;
42
+ subtitleTracks: SubtitleTrack[];
43
+ activeQuality: string;
44
+ qualities: string[];
45
+ }
46
+ export interface UISlice {
47
+ controlsVisible: boolean;
48
+ capabilities: MediaCapabilitiesSnapshot;
49
+ isFullscreen: boolean;
50
+ infoPanelVisible: boolean;
51
+ notification: {
52
+ message: string;
53
+ duration: number;
54
+ } | null;
55
+ }
56
+ export interface PluginSlice {
57
+ controls: ControlRegistration[];
58
+ settings: SettingRegistration[];
59
+ layers: LayerRegistration[];
60
+ contextMenuItems: ContextMenuItem[];
61
+ contextMenuEnabled: boolean;
62
+ }
63
+ export interface ThumbnailSlice {
64
+ thumbnailCues: ThumbnailCue[];
65
+ }
66
+ export interface ErrorSlice {
67
+ error: {
68
+ message: string;
69
+ reconnectAttempt: number;
70
+ isReconnecting: boolean;
71
+ } | null;
72
+ }
73
+ /**
74
+ * Full MediaState — flat at runtime, logically organized here.
75
+ *
76
+ * Consumers can reference MediaState or individual slices depending
77
+ * on how granular they need to be.
78
+ */
79
+ export interface MediaState extends MediaSlice, AudioSlice, PreferencesSlice, UISlice, PluginSlice, ThumbnailSlice, ErrorSlice {
80
+ }
81
+ export type PlayerStore = Store<MediaState>;
82
+ export declare function createMediaStore(): PlayerStore;
83
+ export declare function getInitialMediaState(): MediaState;
84
+ export declare const selectMedia: (s: MediaState) => {
85
+ status: PlaybackStatus;
86
+ source: PlayerSource | null;
87
+ isPlaying: boolean;
88
+ isPaused: boolean;
89
+ isBuffering: boolean;
90
+ isEnded: boolean;
91
+ currentTime: number;
92
+ duration: number;
93
+ bufferedPercent: number;
94
+ playbackRate: number;
95
+ isLive: boolean;
96
+ };
97
+ export declare const selectAudio: (s: MediaState) => {
98
+ volume: number;
99
+ isMuted: boolean;
100
+ };
101
+ export declare const selectPreferences: (s: MediaState) => {
102
+ isLooping: boolean;
103
+ flip: FlipState;
104
+ aspectRatio: AspectRatioState;
105
+ activeSubtitle: SubtitleTrack | null;
106
+ subtitleTracks: SubtitleTrack[];
107
+ activeQuality: string;
108
+ qualities: string[];
109
+ };
110
+ export declare const selectUI: (s: MediaState) => {
111
+ controlsVisible: boolean;
112
+ capabilities: MediaCapabilitiesSnapshot;
113
+ isFullscreen: boolean;
114
+ infoPanelVisible: boolean;
115
+ notification: {
116
+ message: string;
117
+ duration: number;
118
+ } | null;
119
+ };
120
+ export declare const selectPlugins: (s: MediaState) => {
121
+ controls: ControlRegistration[];
122
+ settings: SettingRegistration[];
123
+ layers: LayerRegistration[];
124
+ contextMenuItems: ContextMenuItem[];
125
+ contextMenuEnabled: boolean;
126
+ };
127
+ export declare const selectThumbnails: (s: MediaState) => {
128
+ thumbnailCues: ThumbnailCue[];
129
+ };
130
+ export declare const selectError: (s: MediaState) => {
131
+ message: string;
132
+ reconnectAttempt: number;
133
+ isReconnecting: boolean;
134
+ } | null;
@@ -0,0 +1,25 @@
1
+ export interface StorageOptions {
2
+ prefix?: string;
3
+ }
4
+ export declare class Storage {
5
+ private fallback;
6
+ private prefix;
7
+ constructor(options?: StorageOptions);
8
+ private fullKey;
9
+ get<T = unknown>(key: string): T | undefined;
10
+ set<T = unknown>(key: string, value: T): void;
11
+ remove(key: string): void;
12
+ clear(): void;
13
+ }
14
+ /** Pre-defined persistence keys */
15
+ export declare const STORAGE_KEYS: {
16
+ readonly VOLUME: "volume";
17
+ readonly MUTED: "muted";
18
+ readonly PLAYBACK_RATE: "playbackRate";
19
+ readonly SUBTITLE_LANG: "subtitleLang";
20
+ readonly QUALITY: "quality";
21
+ readonly PLAYBACK_PROGRESS: "playbackProgress";
22
+ readonly LOOP: "loop";
23
+ readonly FLIP: "flip";
24
+ readonly ASPECT_RATIO: "aspectRatio";
25
+ };
@@ -0,0 +1,41 @@
1
+ export interface SubtitleCue {
2
+ index: number;
3
+ start: number;
4
+ end: number;
5
+ text: string;
6
+ }
7
+ export interface SubtitleTrack {
8
+ src: string;
9
+ lang: string;
10
+ label: string;
11
+ default?: boolean;
12
+ }
13
+ /** Parse HH:MM:SS.mmm or HH:MM:SS,mmm timestamp → total seconds */
14
+ export declare function parseTimestamp(ts: string): number;
15
+ /** Parse SRT format text into cues */
16
+ export declare function parseSRT(content: string): SubtitleCue[];
17
+ /** Parse VTT format text into cues */
18
+ export declare function parseVTT(content: string): SubtitleCue[];
19
+ /** Fetch and parse subtitles from URL */
20
+ export declare function fetchSubtitles(track: SubtitleTrack): Promise<SubtitleCue[]>;
21
+ /** Get active cue at a given time */
22
+ export declare function getActiveCue(cues: SubtitleCue[], time: number): SubtitleCue | null;
23
+ export interface ThumbnailCue {
24
+ start: number;
25
+ end: number;
26
+ /** Sprite sheet image URL */
27
+ src: string;
28
+ /** Crop x on sprite */
29
+ x: number;
30
+ /** Crop y on sprite */
31
+ y: number;
32
+ /** Crop width */
33
+ w: number;
34
+ /** Crop height */
35
+ h: number;
36
+ }
37
+ /** Parse a VTT thumbnail sprite file into cues */
38
+ export declare function parseThumbnailVTT(content: string): ThumbnailCue[];
39
+ export declare function fetchThumbnails(url: string, signal?: AbortSignal): Promise<ThumbnailCue[]>;
40
+ /** Find the matching thumbnail cue for a given time */
41
+ export declare function getThumbnailAtTime(cues: ThumbnailCue[], time: number): ThumbnailCue | null;