strataplayer 1.0.6 → 1.0.10
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/README.md +66 -34
- package/dist/core/AudioEngine.d.ts +14 -0
- package/dist/core/EventBus.d.ts +9 -0
- package/dist/core/NanoStore.d.ts +10 -0
- package/dist/core/StrataCore.d.ts +162 -0
- package/dist/hls.cjs.js +2 -0
- package/dist/hls.cjs.js.map +1 -0
- package/dist/hls.d.ts +1 -0
- package/dist/hls.es.js +59 -0
- package/dist/hls.es.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/lib.d.ts +9 -227
- package/dist/plugins/HlsPlugin.d.ts +10 -0
- package/dist/strataplayer.cjs.js +290 -0
- package/dist/strataplayer.cjs.js.map +1 -0
- package/dist/strataplayer.es.js +469 -522
- package/dist/strataplayer.es.js.map +1 -1
- package/dist/ui/Icons.d.ts +38 -0
- package/dist/ui/StrataPlayer.d.ts +21 -0
- package/dist/ui/components/Menu.d.ts +16 -0
- package/dist/ui/components/NotificationContainer.d.ts +5 -0
- package/dist/ui/components/SettingsPrimitives.d.ts +4 -0
- package/dist/ui/components/SubtitleMenu.d.ts +1 -0
- package/dist/ui/components/SubtitleOverlay.d.ts +6 -0
- package/dist/ui/hooks/useTransition.d.ts +4 -0
- package/dist/utils/playerUtils.d.ts +11 -0
- package/package.json +13 -3
- package/dist/strataplayer.umd.js +0 -290
- package/dist/strataplayer.umd.js.map +0 -1
package/README.md
CHANGED
|
@@ -30,54 +30,78 @@ While the UI layer is powered by the high-performance concurrent rendering of Re
|
|
|
30
30
|
|
|
31
31
|
1. **Universal Compatibility:** Write once, run anywhere. Whether you are building a static HTML site or a complex Next.js application, the implementation remains consistent.
|
|
32
32
|
2. **State Isolation:** The playback engine runs on a detached state store (`NanoStore`). This allows the player to handle micro-updates (video time, download progress) internally without triggering re-renders in your parent application.
|
|
33
|
-
3. **
|
|
33
|
+
3. **Modular Architecture:** The core is ultra-lightweight. Heavy dependencies like HLS (`.m3u8`) are entirely opt-in via sub-path imports, keeping your main bundle size minimal.
|
|
34
34
|
|
|
35
35
|
## ✨ Features
|
|
36
36
|
|
|
37
|
-
### Engine & Performance
|
|
38
|
-
|
|
39
37
|
- **Framework Agnostic:** First-class support for React, with a mounting API for Vue, Svelte, and Vanilla JS.
|
|
40
38
|
- **Resilient Network Handling:** Automatic exponential backoff and retry logic for unstable connections.
|
|
41
|
-
- **Adaptive Streaming:**
|
|
39
|
+
- **Adaptive Streaming:** Plugin-based support for HLS (powered by `hls.js`).
|
|
42
40
|
- **Audio Boost Engine:** Integrated Web Audio API nodes allowing volume boosting up to 300%.
|
|
43
|
-
|
|
44
|
-
### Visuals & Interface
|
|
45
|
-
|
|
46
|
-
- **Themeable System:** 4 built-in distinct themes (Default, Pixel, Game, Hacker) with CSS variable support.
|
|
41
|
+
- **Themeable System:** 4 built-in distinct themes (Default, Pixel, Game, Hacker).
|
|
47
42
|
- **Advanced Subtitles:** DOM-based rendering supporting custom positioning, shadows, and runtime sync adjustment.
|
|
48
|
-
- **Mobile Optimized:** Touch gestures for seeking (double-tap), scrubbing, and volume control.
|
|
49
|
-
- **Google Cast:** Native Chromecast integration.
|
|
50
43
|
|
|
51
44
|
## 🚀 Installation
|
|
52
45
|
|
|
46
|
+
Install the core player:
|
|
47
|
+
|
|
53
48
|
```bash
|
|
54
49
|
npm install strataplayer
|
|
55
50
|
```
|
|
56
51
|
|
|
52
|
+
If you need HLS support (streaming `.m3u8` files), install the peer dependency:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npm install hls.js
|
|
56
|
+
```
|
|
57
|
+
|
|
57
58
|
## 💻 Usage
|
|
58
59
|
|
|
59
|
-
### React
|
|
60
|
+
### 1. React + MP4 (Basic)
|
|
61
|
+
|
|
62
|
+
For standard video files, simply import the component. No extra plugins required.
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
import { StrataPlayer } from "strataplayer";
|
|
66
|
+
import "strataplayer/style.css";
|
|
67
|
+
|
|
68
|
+
const App = () => {
|
|
69
|
+
return (
|
|
70
|
+
<StrataPlayer
|
|
71
|
+
src="https://example.com/video.mp4"
|
|
72
|
+
theme="default"
|
|
73
|
+
themeColor="#6366f1"
|
|
74
|
+
/>
|
|
75
|
+
);
|
|
76
|
+
};
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 2. React + HLS (Advanced)
|
|
80
|
+
|
|
81
|
+
To play HLS streams, import the `HlsPlugin` from the sub-path `strataplayer/hls`.
|
|
60
82
|
|
|
61
83
|
```tsx
|
|
62
84
|
import { StrataPlayer } from "strataplayer";
|
|
85
|
+
import { HlsPlugin } from "strataplayer/hls"; // Import from sub-path
|
|
63
86
|
import "strataplayer/style.css";
|
|
64
87
|
|
|
88
|
+
// Initialize plugins outside render loop or via useMemo
|
|
89
|
+
const plugins = [new HlsPlugin()];
|
|
90
|
+
|
|
65
91
|
const App = () => {
|
|
66
92
|
return (
|
|
67
|
-
<
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
/>
|
|
73
|
-
</div>
|
|
93
|
+
<StrataPlayer
|
|
94
|
+
src="https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8"
|
|
95
|
+
plugins={plugins}
|
|
96
|
+
theme="hacker"
|
|
97
|
+
/>
|
|
74
98
|
);
|
|
75
99
|
};
|
|
76
100
|
```
|
|
77
101
|
|
|
78
|
-
### Vanilla JS / Vue / Svelte
|
|
102
|
+
### 3. Vanilla JS / Vue / Svelte + MP4
|
|
79
103
|
|
|
80
|
-
For non-React frameworks, use the `mountStrataPlayer` helper.
|
|
104
|
+
For non-React frameworks, use the `mountStrataPlayer` helper.
|
|
81
105
|
|
|
82
106
|
**index.html**
|
|
83
107
|
|
|
@@ -94,34 +118,42 @@ import "strataplayer/style.css";
|
|
|
94
118
|
const container = document.getElementById("player-container");
|
|
95
119
|
|
|
96
120
|
const instance = mountStrataPlayer(container, {
|
|
97
|
-
src: "https://
|
|
121
|
+
src: "https://example.com/video.mp4",
|
|
98
122
|
autoPlay: false,
|
|
99
|
-
theme: "
|
|
100
|
-
themeColor: "#22c55e",
|
|
101
|
-
sources: [
|
|
102
|
-
{ name: "HLS Stream", url: "...", type: "hls" },
|
|
103
|
-
{ name: "MP4 Fallback", url: "...", type: "mp4" },
|
|
104
|
-
],
|
|
123
|
+
theme: "game",
|
|
105
124
|
});
|
|
106
125
|
|
|
107
|
-
//
|
|
108
|
-
// instance.update({ src: 'new-video.mp4' });
|
|
109
|
-
|
|
110
|
-
// To destroy/cleanup (e.g., in Vue onUnmounted):
|
|
126
|
+
// Cleanup when done
|
|
111
127
|
// instance.unmount();
|
|
112
128
|
```
|
|
113
129
|
|
|
130
|
+
### 4. Vanilla JS / Vue / Svelte + HLS
|
|
131
|
+
|
|
132
|
+
Just like in React, import the plugin and pass it in the config object.
|
|
133
|
+
|
|
134
|
+
```javascript
|
|
135
|
+
import { mountStrataPlayer } from "strataplayer";
|
|
136
|
+
import { HlsPlugin } from "strataplayer/hls";
|
|
137
|
+
import "strataplayer/style.css";
|
|
138
|
+
|
|
139
|
+
const instance = mountStrataPlayer(document.getElementById("root"), {
|
|
140
|
+
src: "https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8",
|
|
141
|
+
plugins: [new HlsPlugin()],
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
114
145
|
## ⚙️ Advanced Configuration
|
|
115
146
|
|
|
116
147
|
### Sources & Tracks
|
|
117
148
|
|
|
118
|
-
StrataPlayer supports complex source arrays and subtitle tracks.
|
|
149
|
+
StrataPlayer supports complex source arrays (for quality fallbacks) and subtitle tracks.
|
|
119
150
|
|
|
120
151
|
```javascript
|
|
121
152
|
const props = {
|
|
153
|
+
// Priority order: HLS -> MP4
|
|
122
154
|
sources: [
|
|
123
|
-
{ name: "
|
|
124
|
-
{ name: "
|
|
155
|
+
{ name: "Stream", url: "master.m3u8", type: "hls" },
|
|
156
|
+
{ name: "Download", url: "fallback.mp4", type: "mp4" },
|
|
125
157
|
],
|
|
126
158
|
poster: "https://example.com/poster.jpg",
|
|
127
159
|
thumbnails: "https://example.com/storyboard.vtt", // For seek preview
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare class AudioEngine {
|
|
2
|
+
private context;
|
|
3
|
+
private source;
|
|
4
|
+
private gainNode;
|
|
5
|
+
private video;
|
|
6
|
+
private isInitialized;
|
|
7
|
+
constructor(video: HTMLVideoElement);
|
|
8
|
+
/**
|
|
9
|
+
* Initialize the AudioContext. Must be called after user interaction.
|
|
10
|
+
*/
|
|
11
|
+
init(): void;
|
|
12
|
+
setGain(value: number): void;
|
|
13
|
+
destroy(): void;
|
|
14
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type EventCallback<T = any> = (data: T) => void;
|
|
2
|
+
export declare class EventBus {
|
|
3
|
+
private events;
|
|
4
|
+
constructor();
|
|
5
|
+
on<T>(event: string, callback: EventCallback<T>): () => void;
|
|
6
|
+
off<T>(event: string, callback: EventCallback<T>): void;
|
|
7
|
+
emit<T>(event: string, data?: T): void;
|
|
8
|
+
destroy(): void;
|
|
9
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type Listener<T> = (state: T, prevState: T) => void;
|
|
2
|
+
export declare class NanoStore<T> {
|
|
3
|
+
private state;
|
|
4
|
+
private listeners;
|
|
5
|
+
constructor(initialState: T);
|
|
6
|
+
get(): T;
|
|
7
|
+
setState(partial: Partial<T> | ((prev: T) => Partial<T>)): void;
|
|
8
|
+
subscribe(listener: Listener<T>): () => void;
|
|
9
|
+
destroy(): void;
|
|
10
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { EventBus } from './EventBus';
|
|
2
|
+
import { NanoStore } from './NanoStore';
|
|
3
|
+
|
|
4
|
+
export interface Notification {
|
|
5
|
+
id: string;
|
|
6
|
+
message: string;
|
|
7
|
+
type: 'info' | 'success' | 'warning' | 'error' | 'loading';
|
|
8
|
+
duration?: number;
|
|
9
|
+
progress?: number;
|
|
10
|
+
}
|
|
11
|
+
export interface SubtitleSettings {
|
|
12
|
+
useNative: boolean;
|
|
13
|
+
fixCapitalization: boolean;
|
|
14
|
+
backgroundOpacity: number;
|
|
15
|
+
backgroundBlur: boolean;
|
|
16
|
+
backgroundBlurAmount: number;
|
|
17
|
+
textSize: number;
|
|
18
|
+
textStyle: 'none' | 'outline' | 'raised' | 'depressed' | 'shadow';
|
|
19
|
+
isBold: boolean;
|
|
20
|
+
textColor: string;
|
|
21
|
+
verticalOffset: number;
|
|
22
|
+
}
|
|
23
|
+
export declare const DEFAULT_SUBTITLE_SETTINGS: SubtitleSettings;
|
|
24
|
+
export type PlayerTheme = 'default' | 'pixel' | 'game' | 'hacker';
|
|
25
|
+
export interface PlayerSource {
|
|
26
|
+
url: string;
|
|
27
|
+
type?: 'hls' | 'mp4' | 'webm' | 'dash' | string;
|
|
28
|
+
name?: string;
|
|
29
|
+
}
|
|
30
|
+
export interface PlayerState {
|
|
31
|
+
isPlaying: boolean;
|
|
32
|
+
isBuffering: boolean;
|
|
33
|
+
currentTime: number;
|
|
34
|
+
duration: number;
|
|
35
|
+
buffered: {
|
|
36
|
+
start: number;
|
|
37
|
+
end: number;
|
|
38
|
+
}[];
|
|
39
|
+
volume: number;
|
|
40
|
+
isMuted: boolean;
|
|
41
|
+
audioGain: number;
|
|
42
|
+
playbackRate: number;
|
|
43
|
+
qualityLevels: {
|
|
44
|
+
height: number;
|
|
45
|
+
bitrate: number;
|
|
46
|
+
index: number;
|
|
47
|
+
}[];
|
|
48
|
+
currentQuality: number;
|
|
49
|
+
audioTracks: {
|
|
50
|
+
label: string;
|
|
51
|
+
language: string;
|
|
52
|
+
index: number;
|
|
53
|
+
}[];
|
|
54
|
+
currentAudioTrack: number;
|
|
55
|
+
error: string | null;
|
|
56
|
+
isFullscreen: boolean;
|
|
57
|
+
isPip: boolean;
|
|
58
|
+
subtitleTracks: {
|
|
59
|
+
label: string;
|
|
60
|
+
language: string;
|
|
61
|
+
index: number;
|
|
62
|
+
}[];
|
|
63
|
+
currentSubtitle: number;
|
|
64
|
+
subtitleOffset: number;
|
|
65
|
+
subtitleSettings: SubtitleSettings;
|
|
66
|
+
activeCues: string[];
|
|
67
|
+
viewMode: 'normal' | 'theater' | 'pip';
|
|
68
|
+
notifications: Notification[];
|
|
69
|
+
iconSize: 'small' | 'medium' | 'large';
|
|
70
|
+
themeColor: string;
|
|
71
|
+
theme: PlayerTheme;
|
|
72
|
+
sources: PlayerSource[];
|
|
73
|
+
currentSourceIndex: number;
|
|
74
|
+
}
|
|
75
|
+
export interface StrataConfig {
|
|
76
|
+
volume?: number;
|
|
77
|
+
muted?: boolean;
|
|
78
|
+
playbackRate?: number;
|
|
79
|
+
audioGain?: number;
|
|
80
|
+
theme?: PlayerTheme;
|
|
81
|
+
themeColor?: string;
|
|
82
|
+
iconSize?: 'small' | 'medium' | 'large';
|
|
83
|
+
subtitleSettings?: Partial<SubtitleSettings>;
|
|
84
|
+
disablePersistence?: boolean;
|
|
85
|
+
}
|
|
86
|
+
export declare const DEFAULT_STATE: PlayerState;
|
|
87
|
+
export declare const getResolvedState: (config?: StrataConfig) => PlayerState;
|
|
88
|
+
export interface IPlugin {
|
|
89
|
+
name: string;
|
|
90
|
+
init(core: StrataCore): void;
|
|
91
|
+
destroy?(): void;
|
|
92
|
+
}
|
|
93
|
+
export interface TextTrackConfig {
|
|
94
|
+
kind: 'subtitles' | 'captions' | 'descriptions' | 'chapters' | 'metadata';
|
|
95
|
+
label: string;
|
|
96
|
+
src: string;
|
|
97
|
+
srcLang: string;
|
|
98
|
+
default?: boolean;
|
|
99
|
+
}
|
|
100
|
+
export declare class StrataCore {
|
|
101
|
+
video: HTMLVideoElement;
|
|
102
|
+
container: HTMLElement | null;
|
|
103
|
+
events: EventBus;
|
|
104
|
+
store: NanoStore<PlayerState>;
|
|
105
|
+
private plugins;
|
|
106
|
+
private audioEngine;
|
|
107
|
+
private config;
|
|
108
|
+
private retryCount;
|
|
109
|
+
private maxRetries;
|
|
110
|
+
private retryTimer;
|
|
111
|
+
private currentSource;
|
|
112
|
+
private currentSrc;
|
|
113
|
+
private currentTracks;
|
|
114
|
+
private castInitialized;
|
|
115
|
+
private boundCueChange;
|
|
116
|
+
private boundFullscreenChange;
|
|
117
|
+
constructor(config?: StrataConfig, videoElement?: HTMLVideoElement);
|
|
118
|
+
private initVideoListeners;
|
|
119
|
+
triggerError(message: string, isFatal?: boolean): void;
|
|
120
|
+
private handleError;
|
|
121
|
+
private updateBuffer;
|
|
122
|
+
private updateSubtitles;
|
|
123
|
+
fetchWithRetry(url: string, retries?: number): Promise<Response>;
|
|
124
|
+
attach(container: HTMLElement): void;
|
|
125
|
+
use(plugin: IPlugin): void;
|
|
126
|
+
setSources(sources: PlayerSource[], tracks?: TextTrackConfig[]): void;
|
|
127
|
+
switchSource(index: number): void;
|
|
128
|
+
load(source: PlayerSource | string, tracks?: TextTrackConfig[], isRetry?: boolean): void;
|
|
129
|
+
addTextTrack(file: File, label: string): void;
|
|
130
|
+
private addTextTrackInternal;
|
|
131
|
+
play(): Promise<void>;
|
|
132
|
+
pause(): void;
|
|
133
|
+
togglePlay(): void;
|
|
134
|
+
seek(time: number): void;
|
|
135
|
+
skip(seconds: number): void;
|
|
136
|
+
setVolume(vol: number): void;
|
|
137
|
+
toggleMute(): void;
|
|
138
|
+
setAudioGain(gain: number): void;
|
|
139
|
+
setQuality(index: number): void;
|
|
140
|
+
setAudioTrack(index: number): void;
|
|
141
|
+
toggleFullscreen(): void;
|
|
142
|
+
togglePip(): void;
|
|
143
|
+
private initCast;
|
|
144
|
+
requestCast(): void;
|
|
145
|
+
private loadMediaToCast;
|
|
146
|
+
private handleCueChange;
|
|
147
|
+
setSubtitle(index: number): void;
|
|
148
|
+
updateSubtitleSettings(settings: Partial<SubtitleSettings>): void;
|
|
149
|
+
resetSubtitleSettings(): void;
|
|
150
|
+
setSubtitleOffset(offset: number): void;
|
|
151
|
+
download(): Promise<void>;
|
|
152
|
+
notify(n: Omit<Notification, 'id'> & {
|
|
153
|
+
id?: string;
|
|
154
|
+
}): string;
|
|
155
|
+
removeNotification(id: string): void;
|
|
156
|
+
setAppearance(settings: {
|
|
157
|
+
iconSize?: 'small' | 'medium' | 'large';
|
|
158
|
+
themeColor?: string;
|
|
159
|
+
theme?: PlayerTheme;
|
|
160
|
+
}): void;
|
|
161
|
+
destroy(): void;
|
|
162
|
+
}
|
package/dist/hls.cjs.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";var a=Object.defineProperty;var u=(i,s,e)=>s in i?a(i,s,{enumerable:!0,configurable:!0,writable:!0,value:e}):i[s]=e;var n=(i,s,e)=>u(i,typeof s!="symbol"?s+"":s,e);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const h=require("hls.js");class c{constructor(){n(this,"name","HlsPlugin");n(this,"hls",null);n(this,"core",null)}init(s){this.core=s,this.core.events.on("load",e=>{e.type==="hls"||e.url.includes(".m3u8")?h.isSupported()?this.loadHls(e.url):this.core.video.canPlayType("application/vnd.apple.mpegurl")&&(this.core.video.src=e.url):this.hls&&(this.hls.destroy(),this.hls=null)}),this.core.events.on("quality-request",e=>{this.hls&&(this.hls.currentLevel=e)}),this.core.events.on("audio-track-request",e=>{this.hls&&(this.hls.audioTrack=e)})}loadHls(s){this.hls&&this.hls.destroy(),this.hls=new h({autoStartLoad:!0,startLevel:-1,capLevelToPlayerSize:!0}),this.hls.loadSource(s),this.hls.attachMedia(this.core.video),this.hls.on(h.Events.MANIFEST_PARSED,(e,t)=>{const r=t.levels.map((l,o)=>({height:l.height,bitrate:l.bitrate,index:o}));this.core.store.setState({qualityLevels:r})}),this.hls.on(h.Events.AUDIO_TRACKS_UPDATED,(e,t)=>{const r=t.audioTracks.map((l,o)=>({label:l.name||l.lang||`Audio ${o+1}`,language:l.lang||"",index:o}));this.core.store.setState({audioTracks:r,currentAudioTrack:this.hls.audioTrack})}),this.hls.on(h.Events.LEVEL_SWITCHED,(e,t)=>{}),this.hls.on(h.Events.ERROR,(e,t)=>{if(t.fatal){const r=t.details||"Unknown HLS Error";this.core.triggerError(r,!0)}})}destroy(){this.hls&&(this.hls.destroy(),this.hls=null)}}exports.HlsPlugin=c;
|
|
2
|
+
//# sourceMappingURL=hls.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hls.cjs.js","sources":["../plugins/HlsPlugin.ts"],"sourcesContent":["\nimport { StrataCore, IPlugin } from '../core/StrataCore';\nimport Hls from 'hls.js';\n\nexport class HlsPlugin implements IPlugin {\n name = 'HlsPlugin';\n private hls: Hls | null = null;\n private core: StrataCore | null = null;\n\n init(core: StrataCore) {\n this.core = core;\n\n // Listen for load requests\n this.core.events.on('load', (data: { url: string, type: string }) => {\n // Only proceed if type matches HLS\n if (data.type === 'hls' || data.url.includes('.m3u8')) {\n if (Hls.isSupported()) {\n this.loadHls(data.url);\n } else if (this.core!.video.canPlayType('application/vnd.apple.mpegurl')) {\n // Native HLS fallback (Safari) - Core sets src, we do nothing\n // Core already handles setting video.src if standard\n this.core!.video.src = data.url;\n }\n } else {\n // If we had an active HLS instance but switched to MP4, destroy it\n if (this.hls) {\n this.hls.destroy();\n this.hls = null;\n }\n }\n });\n\n // Listen for quality changes from UI\n this.core.events.on('quality-request', (index: number) => {\n if (this.hls) {\n this.hls.currentLevel = index;\n }\n });\n\n // Listen for audio track changes from UI\n this.core.events.on('audio-track-request', (index: number) => {\n if (this.hls) {\n this.hls.audioTrack = index;\n }\n });\n }\n\n private loadHls(url: string) {\n if (this.hls) {\n this.hls.destroy();\n }\n\n this.hls = new Hls({\n autoStartLoad: true,\n startLevel: -1, // Auto\n capLevelToPlayerSize: true, // Performance opt\n });\n\n this.hls.loadSource(url);\n this.hls.attachMedia(this.core!.video);\n\n this.hls.on(Hls.Events.MANIFEST_PARSED, (event: any, data: any) => {\n const levels = data.levels.map((lvl: any, idx: number) => ({\n height: lvl.height,\n bitrate: lvl.bitrate,\n index: idx\n }));\n this.core!.store.setState({ qualityLevels: levels });\n });\n\n // Handle Audio Tracks\n this.hls.on(Hls.Events.AUDIO_TRACKS_UPDATED, (event: any, data: any) => {\n const tracks = data.audioTracks.map((track: any, idx: number) => ({\n label: track.name || track.lang || `Audio ${idx + 1}`,\n language: track.lang || '',\n index: idx\n }));\n this.core!.store.setState({\n audioTracks: tracks,\n currentAudioTrack: this.hls!.audioTrack\n });\n });\n\n this.hls.on(Hls.Events.LEVEL_SWITCHED, (event: any, data: any) => {\n // Update current quality only if in auto mode to show what's playing\n // If manual, state is already set\n });\n\n this.hls.on(Hls.Events.ERROR, (event: any, data: any) => {\n if (data.fatal) {\n // Pass fatal errors to Core to handle the retry loop visibly\n const msg = data.details || 'Unknown HLS Error';\n this.core!.triggerError(msg, true);\n\n // Cleanup if needed, but core.load() will eventually destroy and re-init this plugin\n }\n });\n }\n\n destroy() {\n if (this.hls) {\n this.hls.destroy();\n this.hls = null;\n }\n }\n}\n"],"names":["HlsPlugin","__publicField","core","data","Hls","index","url","event","levels","lvl","idx","tracks","track","msg"],"mappings":"8QAIO,MAAMA,CAA6B,CAAnC,cACLC,EAAA,YAAO,aACCA,EAAA,WAAkB,MAClBA,EAAA,YAA0B,MAElC,KAAKC,EAAkB,CACrB,KAAK,KAAOA,EAGZ,KAAK,KAAK,OAAO,GAAG,OAASC,GAAwC,CAE/DA,EAAK,OAAS,OAASA,EAAK,IAAI,SAAS,OAAO,EAC9CC,EAAI,cACN,KAAK,QAAQD,EAAK,GAAG,EACZ,KAAK,KAAM,MAAM,YAAY,+BAA+B,IAGrE,KAAK,KAAM,MAAM,IAAMA,EAAK,KAI1B,KAAK,MACP,KAAK,IAAI,QAAA,EACT,KAAK,IAAM,KAGjB,CAAC,EAGD,KAAK,KAAK,OAAO,GAAG,kBAAoBE,GAAkB,CACpD,KAAK,MACP,KAAK,IAAI,aAAeA,EAE5B,CAAC,EAGD,KAAK,KAAK,OAAO,GAAG,sBAAwBA,GAAkB,CACxD,KAAK,MACP,KAAK,IAAI,WAAaA,EAE1B,CAAC,CACH,CAEQ,QAAQC,EAAa,CACvB,KAAK,KACP,KAAK,IAAI,QAAA,EAGX,KAAK,IAAM,IAAIF,EAAI,CACjB,cAAe,GACf,WAAY,GACZ,qBAAsB,EAAA,CACvB,EAED,KAAK,IAAI,WAAWE,CAAG,EACvB,KAAK,IAAI,YAAY,KAAK,KAAM,KAAK,EAErC,KAAK,IAAI,GAAGF,EAAI,OAAO,gBAAiB,CAACG,EAAYJ,IAAc,CACjE,MAAMK,EAASL,EAAK,OAAO,IAAI,CAACM,EAAUC,KAAiB,CACzD,OAAQD,EAAI,OACZ,QAASA,EAAI,QACb,MAAOC,CAAA,EACP,EACF,KAAK,KAAM,MAAM,SAAS,CAAE,cAAeF,EAAQ,CACrD,CAAC,EAGD,KAAK,IAAI,GAAGJ,EAAI,OAAO,qBAAsB,CAACG,EAAYJ,IAAc,CACtE,MAAMQ,EAASR,EAAK,YAAY,IAAI,CAACS,EAAYF,KAAiB,CAChE,MAAOE,EAAM,MAAQA,EAAM,MAAQ,SAASF,EAAM,CAAC,GACnD,SAAUE,EAAM,MAAQ,GACxB,MAAOF,CAAA,EACP,EACF,KAAK,KAAM,MAAM,SAAS,CACxB,YAAaC,EACb,kBAAmB,KAAK,IAAK,UAAA,CAC9B,CACH,CAAC,EAED,KAAK,IAAI,GAAGP,EAAI,OAAO,eAAgB,CAACG,EAAYJ,IAAc,CAGlE,CAAC,EAED,KAAK,IAAI,GAAGC,EAAI,OAAO,MAAO,CAACG,EAAYJ,IAAc,CACvD,GAAIA,EAAK,MAAO,CAEd,MAAMU,EAAMV,EAAK,SAAW,oBAC5B,KAAK,KAAM,aAAaU,EAAK,EAAI,CAGnC,CACF,CAAC,CACH,CAEA,SAAU,CACJ,KAAK,MACP,KAAK,IAAI,QAAA,EACT,KAAK,IAAM,KAEf,CACF"}
|
package/dist/hls.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './plugins/HlsPlugin'
|
package/dist/hls.es.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
var a = Object.defineProperty;
|
|
2
|
+
var u = (i, e, s) => e in i ? a(i, e, { enumerable: !0, configurable: !0, writable: !0, value: s }) : i[e] = s;
|
|
3
|
+
var n = (i, e, s) => u(i, typeof e != "symbol" ? e + "" : e, s);
|
|
4
|
+
import h from "hls.js";
|
|
5
|
+
class v {
|
|
6
|
+
constructor() {
|
|
7
|
+
n(this, "name", "HlsPlugin");
|
|
8
|
+
n(this, "hls", null);
|
|
9
|
+
n(this, "core", null);
|
|
10
|
+
}
|
|
11
|
+
init(e) {
|
|
12
|
+
this.core = e, this.core.events.on("load", (s) => {
|
|
13
|
+
s.type === "hls" || s.url.includes(".m3u8") ? h.isSupported() ? this.loadHls(s.url) : this.core.video.canPlayType("application/vnd.apple.mpegurl") && (this.core.video.src = s.url) : this.hls && (this.hls.destroy(), this.hls = null);
|
|
14
|
+
}), this.core.events.on("quality-request", (s) => {
|
|
15
|
+
this.hls && (this.hls.currentLevel = s);
|
|
16
|
+
}), this.core.events.on("audio-track-request", (s) => {
|
|
17
|
+
this.hls && (this.hls.audioTrack = s);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
loadHls(e) {
|
|
21
|
+
this.hls && this.hls.destroy(), this.hls = new h({
|
|
22
|
+
autoStartLoad: !0,
|
|
23
|
+
startLevel: -1,
|
|
24
|
+
// Auto
|
|
25
|
+
capLevelToPlayerSize: !0
|
|
26
|
+
// Performance opt
|
|
27
|
+
}), this.hls.loadSource(e), this.hls.attachMedia(this.core.video), this.hls.on(h.Events.MANIFEST_PARSED, (s, t) => {
|
|
28
|
+
const r = t.levels.map((l, o) => ({
|
|
29
|
+
height: l.height,
|
|
30
|
+
bitrate: l.bitrate,
|
|
31
|
+
index: o
|
|
32
|
+
}));
|
|
33
|
+
this.core.store.setState({ qualityLevels: r });
|
|
34
|
+
}), this.hls.on(h.Events.AUDIO_TRACKS_UPDATED, (s, t) => {
|
|
35
|
+
const r = t.audioTracks.map((l, o) => ({
|
|
36
|
+
label: l.name || l.lang || `Audio ${o + 1}`,
|
|
37
|
+
language: l.lang || "",
|
|
38
|
+
index: o
|
|
39
|
+
}));
|
|
40
|
+
this.core.store.setState({
|
|
41
|
+
audioTracks: r,
|
|
42
|
+
currentAudioTrack: this.hls.audioTrack
|
|
43
|
+
});
|
|
44
|
+
}), this.hls.on(h.Events.LEVEL_SWITCHED, (s, t) => {
|
|
45
|
+
}), this.hls.on(h.Events.ERROR, (s, t) => {
|
|
46
|
+
if (t.fatal) {
|
|
47
|
+
const r = t.details || "Unknown HLS Error";
|
|
48
|
+
this.core.triggerError(r, !0);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
destroy() {
|
|
53
|
+
this.hls && (this.hls.destroy(), this.hls = null);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export {
|
|
57
|
+
v as HlsPlugin
|
|
58
|
+
};
|
|
59
|
+
//# sourceMappingURL=hls.es.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hls.es.js","sources":["../plugins/HlsPlugin.ts"],"sourcesContent":["\nimport { StrataCore, IPlugin } from '../core/StrataCore';\nimport Hls from 'hls.js';\n\nexport class HlsPlugin implements IPlugin {\n name = 'HlsPlugin';\n private hls: Hls | null = null;\n private core: StrataCore | null = null;\n\n init(core: StrataCore) {\n this.core = core;\n\n // Listen for load requests\n this.core.events.on('load', (data: { url: string, type: string }) => {\n // Only proceed if type matches HLS\n if (data.type === 'hls' || data.url.includes('.m3u8')) {\n if (Hls.isSupported()) {\n this.loadHls(data.url);\n } else if (this.core!.video.canPlayType('application/vnd.apple.mpegurl')) {\n // Native HLS fallback (Safari) - Core sets src, we do nothing\n // Core already handles setting video.src if standard\n this.core!.video.src = data.url;\n }\n } else {\n // If we had an active HLS instance but switched to MP4, destroy it\n if (this.hls) {\n this.hls.destroy();\n this.hls = null;\n }\n }\n });\n\n // Listen for quality changes from UI\n this.core.events.on('quality-request', (index: number) => {\n if (this.hls) {\n this.hls.currentLevel = index;\n }\n });\n\n // Listen for audio track changes from UI\n this.core.events.on('audio-track-request', (index: number) => {\n if (this.hls) {\n this.hls.audioTrack = index;\n }\n });\n }\n\n private loadHls(url: string) {\n if (this.hls) {\n this.hls.destroy();\n }\n\n this.hls = new Hls({\n autoStartLoad: true,\n startLevel: -1, // Auto\n capLevelToPlayerSize: true, // Performance opt\n });\n\n this.hls.loadSource(url);\n this.hls.attachMedia(this.core!.video);\n\n this.hls.on(Hls.Events.MANIFEST_PARSED, (event: any, data: any) => {\n const levels = data.levels.map((lvl: any, idx: number) => ({\n height: lvl.height,\n bitrate: lvl.bitrate,\n index: idx\n }));\n this.core!.store.setState({ qualityLevels: levels });\n });\n\n // Handle Audio Tracks\n this.hls.on(Hls.Events.AUDIO_TRACKS_UPDATED, (event: any, data: any) => {\n const tracks = data.audioTracks.map((track: any, idx: number) => ({\n label: track.name || track.lang || `Audio ${idx + 1}`,\n language: track.lang || '',\n index: idx\n }));\n this.core!.store.setState({\n audioTracks: tracks,\n currentAudioTrack: this.hls!.audioTrack\n });\n });\n\n this.hls.on(Hls.Events.LEVEL_SWITCHED, (event: any, data: any) => {\n // Update current quality only if in auto mode to show what's playing\n // If manual, state is already set\n });\n\n this.hls.on(Hls.Events.ERROR, (event: any, data: any) => {\n if (data.fatal) {\n // Pass fatal errors to Core to handle the retry loop visibly\n const msg = data.details || 'Unknown HLS Error';\n this.core!.triggerError(msg, true);\n\n // Cleanup if needed, but core.load() will eventually destroy and re-init this plugin\n }\n });\n }\n\n destroy() {\n if (this.hls) {\n this.hls.destroy();\n this.hls = null;\n }\n }\n}\n"],"names":["HlsPlugin","__publicField","core","data","Hls","index","url","event","levels","lvl","idx","tracks","track","msg"],"mappings":";;;;AAIO,MAAMA,EAA6B;AAAA,EAAnC;AACL,IAAAC,EAAA,cAAO;AACC,IAAAA,EAAA,aAAkB;AAClB,IAAAA,EAAA,cAA0B;AAAA;AAAA,EAElC,KAAKC,GAAkB;AACrB,SAAK,OAAOA,GAGZ,KAAK,KAAK,OAAO,GAAG,QAAQ,CAACC,MAAwC;AAEnE,MAAIA,EAAK,SAAS,SAASA,EAAK,IAAI,SAAS,OAAO,IAC9CC,EAAI,gBACN,KAAK,QAAQD,EAAK,GAAG,IACZ,KAAK,KAAM,MAAM,YAAY,+BAA+B,MAGrE,KAAK,KAAM,MAAM,MAAMA,EAAK,OAI1B,KAAK,QACP,KAAK,IAAI,QAAA,GACT,KAAK,MAAM;AAAA,IAGjB,CAAC,GAGD,KAAK,KAAK,OAAO,GAAG,mBAAmB,CAACE,MAAkB;AACxD,MAAI,KAAK,QACP,KAAK,IAAI,eAAeA;AAAA,IAE5B,CAAC,GAGD,KAAK,KAAK,OAAO,GAAG,uBAAuB,CAACA,MAAkB;AAC5D,MAAI,KAAK,QACP,KAAK,IAAI,aAAaA;AAAA,IAE1B,CAAC;AAAA,EACH;AAAA,EAEQ,QAAQC,GAAa;AAC3B,IAAI,KAAK,OACP,KAAK,IAAI,QAAA,GAGX,KAAK,MAAM,IAAIF,EAAI;AAAA,MACjB,eAAe;AAAA,MACf,YAAY;AAAA;AAAA,MACZ,sBAAsB;AAAA;AAAA,IAAA,CACvB,GAED,KAAK,IAAI,WAAWE,CAAG,GACvB,KAAK,IAAI,YAAY,KAAK,KAAM,KAAK,GAErC,KAAK,IAAI,GAAGF,EAAI,OAAO,iBAAiB,CAACG,GAAYJ,MAAc;AACjE,YAAMK,IAASL,EAAK,OAAO,IAAI,CAACM,GAAUC,OAAiB;AAAA,QACzD,QAAQD,EAAI;AAAA,QACZ,SAASA,EAAI;AAAA,QACb,OAAOC;AAAA,MAAA,EACP;AACF,WAAK,KAAM,MAAM,SAAS,EAAE,eAAeF,GAAQ;AAAA,IACrD,CAAC,GAGD,KAAK,IAAI,GAAGJ,EAAI,OAAO,sBAAsB,CAACG,GAAYJ,MAAc;AACtE,YAAMQ,IAASR,EAAK,YAAY,IAAI,CAACS,GAAYF,OAAiB;AAAA,QAChE,OAAOE,EAAM,QAAQA,EAAM,QAAQ,SAASF,IAAM,CAAC;AAAA,QACnD,UAAUE,EAAM,QAAQ;AAAA,QACxB,OAAOF;AAAA,MAAA,EACP;AACF,WAAK,KAAM,MAAM,SAAS;AAAA,QACxB,aAAaC;AAAA,QACb,mBAAmB,KAAK,IAAK;AAAA,MAAA,CAC9B;AAAA,IACH,CAAC,GAED,KAAK,IAAI,GAAGP,EAAI,OAAO,gBAAgB,CAACG,GAAYJ,MAAc;AAAA,IAGlE,CAAC,GAED,KAAK,IAAI,GAAGC,EAAI,OAAO,OAAO,CAACG,GAAYJ,MAAc;AACvD,UAAIA,EAAK,OAAO;AAEd,cAAMU,IAAMV,EAAK,WAAW;AAC5B,aAAK,KAAM,aAAaU,GAAK,EAAI;AAAA,MAGnC;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,UAAU;AACR,IAAI,KAAK,QACP,KAAK,IAAI,QAAA,GACT,KAAK,MAAM;AAAA,EAEf;AACF;"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './lib'
|