strataplayer 1.0.6 → 1.0.9

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 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. **Opt-in Complexity:** The core is lightweight. Advanced capabilities like HLS/DASH adaptive streaming, analytics, or casting are treated as plugins, keeping your bundle size strictly minimal unless needed.
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:** Native support for HLS (`.m3u8`) and DASH via plugin architecture.
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
- <div className="player-wrapper">
68
- <StrataPlayer
69
- src="https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8"
70
- theme="default"
71
- themeColor="#6366f1"
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 / Angular
102
+ ### 3. Vanilla JS / Vue / Svelte + MP4
79
103
 
80
- For non-React frameworks, use the `mountStrataPlayer` helper. This mounts the player into a specific DOM node and returns an instance for cleanup and updates.
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://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8",
121
+ src: "https://example.com/video.mp4",
98
122
  autoPlay: false,
99
- theme: "hacker",
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
- // To update props later (e.g., change video):
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: "1080p HLS", url: "master.m3u8", type: "hls" },
124
- { name: "720p MP4", url: "fallback.mp4", type: "mp4" },
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,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,208 @@
1
+ declare class EventBus {
2
+ private events;
3
+ constructor();
4
+ on<T>(event: string, callback: EventCallback<T>): () => void;
5
+ off<T>(event: string, callback: EventCallback<T>): void;
6
+ emit<T>(event: string, data?: T): void;
7
+ destroy(): void;
8
+ }
9
+
10
+ declare type EventCallback<T = any> = (data: T) => void;
11
+
12
+ export declare class HlsPlugin implements IPlugin {
13
+ name: string;
14
+ private hls;
15
+ private core;
16
+ init(core: StrataCore): void;
17
+ private loadHls;
18
+ destroy(): void;
19
+ }
20
+
21
+ declare interface IPlugin {
22
+ name: string;
23
+ init(core: StrataCore): void;
24
+ destroy?(): void;
25
+ }
26
+
27
+ declare type Listener<T> = (state: T, prevState: T) => void;
28
+
29
+ declare class NanoStore<T> {
30
+ private state;
31
+ private listeners;
32
+ constructor(initialState: T);
33
+ get(): T;
34
+ setState(partial: Partial<T> | ((prev: T) => Partial<T>)): void;
35
+ subscribe(listener: Listener<T>): () => void;
36
+ destroy(): void;
37
+ }
38
+
39
+ declare interface Notification_2 {
40
+ id: string;
41
+ message: string;
42
+ type: 'info' | 'success' | 'warning' | 'error' | 'loading';
43
+ duration?: number;
44
+ progress?: number;
45
+ }
46
+
47
+ declare interface PlayerSource {
48
+ url: string;
49
+ type?: 'hls' | 'mp4' | 'webm' | 'dash' | string;
50
+ name?: string;
51
+ }
52
+
53
+ declare interface PlayerState {
54
+ isPlaying: boolean;
55
+ isBuffering: boolean;
56
+ currentTime: number;
57
+ duration: number;
58
+ buffered: {
59
+ start: number;
60
+ end: number;
61
+ }[];
62
+ volume: number;
63
+ isMuted: boolean;
64
+ audioGain: number;
65
+ playbackRate: number;
66
+ qualityLevels: {
67
+ height: number;
68
+ bitrate: number;
69
+ index: number;
70
+ }[];
71
+ currentQuality: number;
72
+ audioTracks: {
73
+ label: string;
74
+ language: string;
75
+ index: number;
76
+ }[];
77
+ currentAudioTrack: number;
78
+ error: string | null;
79
+ isFullscreen: boolean;
80
+ isPip: boolean;
81
+ subtitleTracks: {
82
+ label: string;
83
+ language: string;
84
+ index: number;
85
+ }[];
86
+ currentSubtitle: number;
87
+ subtitleOffset: number;
88
+ subtitleSettings: SubtitleSettings;
89
+ activeCues: string[];
90
+ viewMode: 'normal' | 'theater' | 'pip';
91
+ notifications: Notification_2[];
92
+ iconSize: 'small' | 'medium' | 'large';
93
+ themeColor: string;
94
+ theme: PlayerTheme;
95
+ sources: PlayerSource[];
96
+ currentSourceIndex: number;
97
+ }
98
+
99
+ declare type PlayerTheme = 'default' | 'pixel' | 'game' | 'hacker';
100
+
101
+ declare interface StrataConfig {
102
+ volume?: number;
103
+ muted?: boolean;
104
+ playbackRate?: number;
105
+ audioGain?: number;
106
+ theme?: PlayerTheme;
107
+ themeColor?: string;
108
+ iconSize?: 'small' | 'medium' | 'large';
109
+ subtitleSettings?: Partial<SubtitleSettings>;
110
+ disablePersistence?: boolean;
111
+ }
112
+
113
+ declare class StrataCore {
114
+ video: HTMLVideoElement;
115
+ container: HTMLElement | null;
116
+ events: EventBus;
117
+ store: NanoStore<PlayerState>;
118
+ private plugins;
119
+ private audioEngine;
120
+ private config;
121
+ private retryCount;
122
+ private maxRetries;
123
+ private retryTimer;
124
+ private currentSource;
125
+ private currentSrc;
126
+ private currentTracks;
127
+ private castInitialized;
128
+ private boundCueChange;
129
+ private boundFullscreenChange;
130
+ constructor(config?: StrataConfig, videoElement?: HTMLVideoElement);
131
+ private initVideoListeners;
132
+ triggerError(message: string, isFatal?: boolean): void;
133
+ private handleError;
134
+ private updateBuffer;
135
+ private updateSubtitles;
136
+ fetchWithRetry(url: string, retries?: number): Promise<Response>;
137
+ attach(container: HTMLElement): void;
138
+ use(plugin: IPlugin): void;
139
+ setSources(sources: PlayerSource[], tracks?: TextTrackConfig[]): void;
140
+ switchSource(index: number): void;
141
+ load(source: PlayerSource | string, tracks?: TextTrackConfig[], isRetry?: boolean): void;
142
+ addTextTrack(file: File, label: string): void;
143
+ private addTextTrackInternal;
144
+ play(): Promise<void>;
145
+ pause(): void;
146
+ togglePlay(): void;
147
+ seek(time: number): void;
148
+ skip(seconds: number): void;
149
+ setVolume(vol: number): void;
150
+ toggleMute(): void;
151
+ setAudioGain(gain: number): void;
152
+ setQuality(index: number): void;
153
+ setAudioTrack(index: number): void;
154
+ toggleFullscreen(): void;
155
+ togglePip(): void;
156
+ private initCast;
157
+ requestCast(): void;
158
+ private loadMediaToCast;
159
+ private handleCueChange;
160
+ setSubtitle(index: number): void;
161
+ updateSubtitleSettings(settings: Partial<SubtitleSettings>): void;
162
+ resetSubtitleSettings(): void;
163
+ setSubtitleOffset(offset: number): void;
164
+ download(): Promise<void>;
165
+ notify(n: Omit<Notification_2, 'id'> & {
166
+ id?: string;
167
+ }): string;
168
+ removeNotification(id: string): void;
169
+ setAppearance(settings: {
170
+ iconSize?: 'small' | 'medium' | 'large';
171
+ themeColor?: string;
172
+ theme?: PlayerTheme;
173
+ }): void;
174
+ destroy(): void;
175
+ }
176
+
177
+ declare interface SubtitleSettings {
178
+ useNative: boolean;
179
+ fixCapitalization: boolean;
180
+ backgroundOpacity: number;
181
+ backgroundBlur: boolean;
182
+ backgroundBlurAmount: number;
183
+ textSize: number;
184
+ textStyle: 'none' | 'outline' | 'raised' | 'depressed' | 'shadow';
185
+ isBold: boolean;
186
+ textColor: string;
187
+ verticalOffset: number;
188
+ }
189
+
190
+ declare interface TextTrackConfig {
191
+ kind: 'subtitles' | 'captions' | 'descriptions' | 'chapters' | 'metadata';
192
+ label: string;
193
+ src: string;
194
+ srcLang: string;
195
+ default?: boolean;
196
+ }
197
+
198
+ export { }
199
+
200
+
201
+ declare module 'react' {
202
+ namespace JSX {
203
+ interface IntrinsicElements {
204
+ 'google-cast-launcher': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>;
205
+ }
206
+ }
207
+ }
208
+
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;"}
@@ -11,15 +11,6 @@ declare class EventBus {
11
11
 
12
12
  declare type EventCallback<T = any> = (data: T) => void;
13
13
 
14
- export declare class HlsPlugin implements IPlugin {
15
- name: string;
16
- private hls;
17
- private core;
18
- init(core: StrataCore): void;
19
- private loadHls;
20
- destroy(): void;
21
- }
22
-
23
14
  declare interface IPlugin {
24
15
  name: string;
25
16
  init(core: StrataCore): void;