strataplayer 1.0.12 → 1.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.
package/README.md CHANGED
@@ -22,24 +22,21 @@
22
22
 
23
23
  ## Introduction
24
24
 
25
- StrataPlayer represents a shift in how media playback is handled on the web. Instead of tightly coupling playback logic to a specific framework, StrataPlayer operates as a **framework-agnostic engine**.
25
+ StrataPlayer is a production-grade, framework-agnostic media engine designed for the modern web. It decouples playback logic from the UI, ensuring high-performance rendering even during rapid state updates.
26
26
 
27
- While the UI layer is powered by the high-performance concurrent rendering of React 19, the player itself can be mounted into **any** application—be it Vanilla JS, Vue, Svelte, or Angular. It decouples the "playback state" (time, buffer, quality) from the "view layer," ensuring that high-frequency updates (like progress bars) never cause performance bottlenecks in your host application.
27
+ Built on **React 19** and **TypeScript**, it features a modular plugin architecture that allows you to support only the formats you need—keeping your bundle size minimal while offering support for everything from HLS to BitTorrent streaming.
28
28
 
29
- ## Core Philosophy
29
+ ## Key Features
30
30
 
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
- 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. **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
-
35
- ## Features
36
-
37
- - **Framework Agnostic:** First-class support for React, with a mounting API for Vue, Svelte, and Vanilla JS.
38
- - **Resilient Network Handling:** Automatic exponential backoff and retry logic for unstable connections.
39
- - **Adaptive Streaming:** Plugin-based support for HLS (powered by `hls.js`).
40
- - **Audio Boost Engine:** Integrated Web Audio API nodes allowing volume boosting up to 300%.
41
- - **Themeable System:** 4 built-in distinct themes (Default, Pixel, Game, Hacker).
42
- - **Advanced Subtitles:** DOM-based rendering supporting custom positioning, shadows, and runtime sync adjustment.
31
+ - **Universal Playback:** Support for **HLS**, **DASH**, **MPEG-TS/FLV**, and **WebTorrent** (P2P).
32
+ - **Framework Agnostic:** First-class React support, with easy mounting for Vue, Svelte, Angular, and Vanilla JS.
33
+ - **Robust Network Handling:** Automatic exponential backoff and retry logic for unstable connections.
34
+ - **Advanced Audio Engine:** Integrated Web Audio API nodes for volume boosting (up to 300%) and gain control.
35
+ - **Professional UI:**
36
+ - **Themes:** 4 built-in distinct themes (Default, Pixel, Game, Hacker).
37
+ - **Subtitles:** Comprehensive VTT/SRT support with user-customizable styling (size, color, sync, shadows).
38
+ - **Picture-in-Picture** & **Google Cast** integration.
39
+ - **State Management:** Powered by `NanoStore` for isolated, high-performance state updates.
43
40
 
44
41
  ## 🚀 Installation
45
42
 
@@ -49,17 +46,27 @@ Install the core player:
49
46
  npm install strataplayer
50
47
  ```
51
48
 
52
- If you need HLS support (streaming `.m3u8` files), install the peer dependency:
49
+ Install the specific engines you need as peer dependencies:
53
50
 
54
51
  ```bash
52
+ # For HLS (.m3u8)
55
53
  npm install hls.js
54
+
55
+ # For DASH (.mpd)
56
+ npm install dashjs
57
+
58
+ # For MPEG-TS / FLV
59
+ npm install mpegts.js
60
+
61
+ # For WebTorrent (Magnet links)
62
+ npm install webtorrent
56
63
  ```
57
64
 
58
65
  ## 💻 Usage
59
66
 
60
- ### 1. React + MP4 (Basic)
67
+ ### 1. Basic Usage (React)
61
68
 
62
- For standard video files, simply import the component. No extra plugins required.
69
+ For standard MP4/WebM files, no plugins are required.
63
70
 
64
71
  ```tsx
65
72
  import { StrataPlayer } from "strataplayer";
@@ -76,88 +83,138 @@ const App = () => {
76
83
  };
77
84
  ```
78
85
 
79
- ### 2. React + HLS (Advanced)
86
+ ### 2. Universal Player (All Formats)
80
87
 
81
- To play HLS streams, import the `HlsPlugin` from the sub-path `strataplayer/hls`.
88
+ To support all formats, import the plugins and pass them to the player.
82
89
 
83
90
  ```tsx
84
91
  import { StrataPlayer } from "strataplayer";
85
- import { HlsPlugin } from "strataplayer/hls"; // Import from sub-path
92
+ import { HlsPlugin } from "strataplayer/hls";
93
+ import { DashPlugin } from "strataplayer/dash";
94
+ import { MpegtsPlugin } from "strataplayer/mpegts";
95
+ import { WebTorrentPlugin } from "strataplayer/webtorrent";
86
96
  import "strataplayer/style.css";
87
97
 
88
- // Initialize plugins outside render loop or via useMemo
89
- const plugins = [new HlsPlugin()];
98
+ // Initialize plugins once
99
+ const plugins = [
100
+ new HlsPlugin(),
101
+ new DashPlugin(),
102
+ new MpegtsPlugin(),
103
+ new WebTorrentPlugin(),
104
+ ];
90
105
 
91
106
  const App = () => {
92
107
  return (
93
108
  <StrataPlayer
94
- src="https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8"
109
+ // Can be a magnet link, m3u8, mpd, or mp4
110
+ src="magnet:?xt=urn:btih:..."
95
111
  plugins={plugins}
96
112
  theme="hacker"
113
+ autoPlay={false}
97
114
  />
98
115
  );
99
116
  };
100
117
  ```
101
118
 
102
- ### 3. Vanilla JS / Vue / Svelte + MP4
103
-
104
- For non-React frameworks, use the `mountStrataPlayer` helper.
105
-
106
- **index.html**
107
-
108
- ```html
109
- <div id="player-container" style="width: 100%; aspect-ratio: 16/9;"></div>
110
- ```
119
+ ### 3. Vanilla JS / Vue / Svelte
111
120
 
112
- **main.js**
121
+ Use the `mountStrataPlayer` helper to render the player into any DOM node.
113
122
 
114
123
  ```javascript
115
124
  import { mountStrataPlayer } from "strataplayer";
125
+ import { HlsPlugin } from "strataplayer/hls";
116
126
  import "strataplayer/style.css";
117
127
 
118
- const container = document.getElementById("player-container");
128
+ const container = document.getElementById("player-root");
119
129
 
120
- const instance = mountStrataPlayer(container, {
121
- src: "https://example.com/video.mp4",
122
- autoPlay: false,
130
+ const player = mountStrataPlayer(container, {
131
+ src: "https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8",
132
+ plugins: [new HlsPlugin()],
123
133
  theme: "game",
134
+ themeColor: "#eab308",
124
135
  });
125
136
 
126
- // Cleanup when done
127
- // instance.unmount();
137
+ // Update props later
138
+ // player.update({ theme: 'pixel' });
139
+
140
+ // Cleanup
141
+ // player.unmount();
128
142
  ```
129
143
 
130
- ### 4. Vanilla JS / Vue / Svelte + HLS
144
+ ## 📋 Component Props
145
+
146
+ The `<StrataPlayer />` component accepts the following props:
147
+
148
+ | Prop | Type | Default | Description |
149
+ | :--------------------- | :------------------------------------------- | :---------- | :----------------------------------------------------------------------------------------- |
150
+ | **src** | `string` | `undefined` | The primary media URL. |
151
+ | **sources** | `PlayerSource[]` | `[]` | Array of sources for quality fallback or alternative formats. Overrides `src` if provided. |
152
+ | **poster** | `string` | `undefined` | URL for the poster image shown before playback. |
153
+ | **autoPlay** | `boolean` | `false` | Whether to start playback automatically. Note: Browsers may block unmuted autoplay. |
154
+ | **volume** | `number` | `1` | Initial volume (0.0 to 1.0). |
155
+ | **muted** | `boolean` | `false` | Initial mute state. |
156
+ | **theme** | `'default' \| 'pixel' \| 'game' \| 'hacker'` | `'default'` | The visual theme of the player. |
157
+ | **themeColor** | `string` | `'#6366f1'` | Primary accent color (Hex, RGB). |
158
+ | **iconSize** | `'small' \| 'medium' \| 'large'` | `'medium'` | Size of the control icons. |
159
+ | **thumbnails** | `string` | `undefined` | URL to a VTT file containing storyboard thumbnails for hover preview. |
160
+ | **textTracks** | `TextTrackConfig[]` | `[]` | Array of subtitle/caption tracks. |
161
+ | **plugins** | `IPlugin[]` | `[]` | Array of initialized plugin instances (e.g., `new HlsPlugin()`). |
162
+ | **disablePersistence** | `boolean` | `false` | If `true`, prevents saving settings to LocalStorage. |
163
+ | **audioGain** | `number` | `1` | Initial audio boost level (e.g., `1.5` for 150%). |
164
+ | **playbackRate** | `number` | `1` | Initial playback speed. |
165
+
166
+ ### Data Structures
167
+
168
+ #### `PlayerSource`
169
+
170
+ ```ts
171
+ {
172
+ url: string;
173
+ // Optional: 'hls' | 'dash' | 'mp4' | 'webm' | 'mpegts' | 'webtorrent'
174
+ // If omitted, the player attempts to detect it from the extension.
175
+ type?: string;
176
+ name?: string; // Label for source selector
177
+ }
178
+ ```
131
179
 
132
- Just like in React, import the plugin and pass it in the config object.
180
+ #### `TextTrackConfig`
133
181
 
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
- });
182
+ ```ts
183
+ {
184
+ kind: 'subtitles' | 'captions';
185
+ label: string; // e.g. "English"
186
+ src: string; // URL to .vtt file
187
+ srcLang: string; // e.g. "en"
188
+ default?: boolean;
189
+ }
143
190
  ```
144
191
 
145
- ## ⚙️ Advanced Configuration
192
+ ## 🧩 Plugin System
193
+
194
+ StrataPlayer uses a modular system. You only pay the bundle size cost for the formats you use.
195
+
196
+ | Plugin | Import Path | Dependency | Description |
197
+ | :------------------- | :------------------------ | :----------- | :----------------------------------- |
198
+ | **HlsPlugin** | `strataplayer/hls` | `hls.js` | Adaptive HTTP Live Streaming (.m3u8) |
199
+ | **DashPlugin** | `strataplayer/dash` | `dashjs` | MPEG-DASH Streaming (.mpd) |
200
+ | **MpegtsPlugin** | `strataplayer/mpegts` | `mpegts.js` | MPEG-TS and FLV live streams |
201
+ | **WebTorrentPlugin** | `strataplayer/webtorrent` | `webtorrent` | P2P streaming via WebRTC |
202
+
203
+ ## ⚙️ Configuration
146
204
 
147
205
  ### Sources & Tracks
148
206
 
149
- StrataPlayer supports complex source arrays (for quality fallbacks) and subtitle tracks.
207
+ Support multiple sources for quality fallback and detailed subtitle configuration.
150
208
 
151
- ```javascript
152
- const props = {
153
- // Priority order: HLS -> MP4
154
- sources: [
155
- { name: "Stream", url: "master.m3u8", type: "hls" },
156
- { name: "Download", url: "fallback.mp4", type: "mp4" },
157
- ],
158
- poster: "https://example.com/poster.jpg",
159
- thumbnails: "https://example.com/storyboard.vtt", // For seek preview
160
- textTracks: [
209
+ ```tsx
210
+ <StrataPlayer
211
+ sources={[
212
+ { name: "HLS Master", url: "master.m3u8", type: "hls" },
213
+ { name: "Fallback MP4", url: "fallback.mp4", type: "mp4" },
214
+ ]}
215
+ poster="https://example.com/poster.jpg"
216
+ thumbnails="https://example.com/storyboard.vtt" // for hover previews
217
+ textTracks={[
161
218
  {
162
219
  kind: "subtitles",
163
220
  label: "English",
@@ -165,8 +222,29 @@ const props = {
165
222
  srcLang: "en",
166
223
  default: true,
167
224
  },
168
- ],
169
- };
225
+ ]}
226
+ />
227
+ ```
228
+
229
+ ### UI & Themes
230
+
231
+ The player includes 4 professionally designed themes.
232
+
233
+ | Theme | Description |
234
+ | :-------- | :------------------------------------------------------------ |
235
+ | `default` | Clean, modern aesthetic using Inter font. |
236
+ | `pixel` | Retro 8-bit style with sharp edges and "Press Start 2P" font. |
237
+ | `game` | Cinematic RPG style with serif typography and gold accents. |
238
+ | `hacker` | Terminal-inspired look with scanlines and monospaced fonts. |
239
+
240
+ Customize further with:
241
+
242
+ ```tsx
243
+ <StrataPlayer
244
+ theme="game"
245
+ themeColor="#10b981" // Custom accent color
246
+ iconSize="medium" // 'small' | 'medium' | 'large'
247
+ />
170
248
  ```
171
249
 
172
250
  ## ⌨️ Keyboard Shortcuts
@@ -181,24 +259,27 @@ const props = {
181
259
  | `Arrow Up` | Increase Volume |
182
260
  | `Arrow Down` | Decrease Volume |
183
261
 
184
- ## 🎨 Themes
185
-
186
- StrataPlayer separates layout from aesthetics. Themes can be switched instantly at runtime via props:
262
+ ## 🛠️ Development
187
263
 
188
- - **Default:** Professional, clean lines using Inter font.
189
- - **Pixel:** Retro-gaming aesthetic with sharp edges and 8-bit fonts.
190
- - **Game:** Cinematic RPG style with serif typography and gold accents.
191
- - **Hacker:** Terminal-inspired aesthetic with scanlines and monospaced fonts.
264
+ 1. Clone the repository
265
+ 2. Install dependencies:
266
+ ```bash
267
+ npm install
268
+ ```
269
+ 3. Start the dev server:
270
+ ```bash
271
+ npm run dev
272
+ ```
192
273
 
193
274
  ## 🤝 Contributing
194
275
 
195
- We welcome contributions that align with our philosophy of performance and modularity.
276
+ Contributions are welcome! Please follow these steps:
196
277
 
197
- 1. Fork the project
198
- 2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
199
- 3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
200
- 4. Push to the branch (`git push origin feature/AmazingFeature`)
201
- 5. Open a Pull Request
278
+ 1. Fork the repository.
279
+ 2. Create your feature branch (`git checkout -b feature/AmazingFeature`).
280
+ 3. Commit your changes.
281
+ 4. Push to the branch.
282
+ 5. Open a Pull Request.
202
283
 
203
284
  ## 📄 License
204
285
 
@@ -0,0 +1,2 @@
1
+ "use strict";var h=Object.defineProperty;var u=(s,t,e)=>t in s?h(s,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):s[t]=e;var a=(s,t,e)=>u(s,typeof t!="symbol"?t+"":t,e);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const c=require("dashjs");function y(s){const t=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(s){for(const e in s)if(e!=="default"){const r=Object.getOwnPropertyDescriptor(s,e);Object.defineProperty(t,e,r.get?r:{enumerable:!0,get:()=>s[e]})}}return t.default=s,Object.freeze(t)}const n=y(c),l=n.default||n;class p{constructor(){a(this,"name","DashPlugin");a(this,"player",null);a(this,"core",null)}init(t){this.core=t,this.core.events.on("load",e=>{e.type==="dash"||e.url.includes(".mpd")?this.loadDash(e.url):this.player&&(this.player.destroy(),this.player=null)}),this.core.events.on("quality-request",e=>{this.player&&(e===-1?this.player.updateSettings({streaming:{abr:{autoSwitchBitrate:{video:!0}}}}):(this.player.updateSettings({streaming:{abr:{autoSwitchBitrate:{video:!1}}}}),this.player.setQualityFor("video",e)))}),this.core.events.on("audio-track-request",e=>{if(this.player){const r=this.player.getTracksFor("audio");r[e]&&this.player.setCurrentTrack(r[e])}})}loadDash(t){this.player&&(this.player.destroy(),this.player=null);try{this.player=l.MediaPlayer().create(),this.player.initialize(this.core.video,t,this.core.store.get().isPlaying),this.player.on(l.MediaPlayer.events.STREAM_INITIALIZED,()=>{this.updateQualityLevels(),this.updateAudioTracks()}),this.player.on(l.MediaPlayer.events.ERROR,e=>{var r;this.core.triggerError(`Dash Error: ${((r=e.error)==null?void 0:r.message)||"Unknown"}`,!0)})}catch(e){this.core.triggerError(`Dash Init Failed: ${e.message}`,!0)}}updateQualityLevels(){if(!this.player)return;const t=this.player.getBitrateInfoListFor("video");if(t&&t.length){const e=t.map((r,i)=>({height:r.height,bitrate:r.bitrate,index:i}));this.core.store.setState({qualityLevels:e})}}updateAudioTracks(){if(!this.player)return;const t=this.player.getTracksFor("audio");if(t&&t.length){const e=t.map((r,i)=>{var o;return{label:r.lang||((o=r.roles)==null?void 0:o[0])||`Audio ${i+1}`,language:r.lang||"",index:i}});this.core.store.setState({audioTracks:e})}}destroy(){this.player&&(this.player.destroy(),this.player=null)}}exports.DashPlugin=p;
2
+ //# sourceMappingURL=dash.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dash.cjs.js","sources":["../plugins/DashPlugin.ts"],"sourcesContent":["import { StrataCore, IPlugin } from '../core/StrataCore';\r\nimport * as DashModule from 'dashjs';\r\n\r\n// Handle environment differences where dashjs might be a default export or module export\r\nconst dashjs = (DashModule as any).default || DashModule;\r\n\r\nexport class DashPlugin implements IPlugin {\r\n name = 'DashPlugin';\r\n private player: any = null;\r\n private core: StrataCore | null = null;\r\n\r\n init(core: StrataCore) {\r\n this.core = core;\r\n\r\n // Listen for load requests\r\n this.core.events.on('load', (data: { url: string, type: string }) => {\r\n if (data.type === 'dash' || data.url.includes('.mpd')) {\r\n this.loadDash(data.url);\r\n } else {\r\n // Cleanup if we switch away from DASH\r\n if (this.player) {\r\n this.player.destroy();\r\n this.player = null;\r\n }\r\n }\r\n });\r\n\r\n // Quality Handling\r\n this.core.events.on('quality-request', (index: number) => {\r\n if (this.player) {\r\n // -1 means Auto (ABR enabled), otherwise specific index\r\n if (index === -1) {\r\n this.player.updateSettings({ streaming: { abr: { autoSwitchBitrate: { video: true } } } });\r\n } else {\r\n this.player.updateSettings({ streaming: { abr: { autoSwitchBitrate: { video: false } } } });\r\n this.player.setQualityFor('video', index);\r\n }\r\n }\r\n });\r\n\r\n // Audio Track Handling\r\n this.core.events.on('audio-track-request', (index: number) => {\r\n if (this.player) {\r\n const tracks = this.player.getTracksFor('audio');\r\n if (tracks[index]) {\r\n this.player.setCurrentTrack(tracks[index]);\r\n }\r\n }\r\n });\r\n }\r\n\r\n private loadDash(url: string) {\r\n if (this.player) {\r\n this.player.destroy();\r\n this.player = null;\r\n }\r\n\r\n try {\r\n this.player = dashjs.MediaPlayer().create();\r\n this.player.initialize(this.core!.video, url, this.core!.store.get().isPlaying);\r\n\r\n // Event Listeners\r\n this.player.on(dashjs.MediaPlayer.events.STREAM_INITIALIZED, () => {\r\n this.updateQualityLevels();\r\n this.updateAudioTracks();\r\n });\r\n\r\n this.player.on(dashjs.MediaPlayer.events.ERROR, (e: any) => {\r\n // Pass error to core with retry logic enabled (fatal=true)\r\n this.core!.triggerError(`Dash Error: ${e.error?.message || 'Unknown'}`, true);\r\n });\r\n\r\n } catch (e: any) {\r\n this.core!.triggerError(`Dash Init Failed: ${e.message}`, true);\r\n }\r\n }\r\n\r\n private updateQualityLevels() {\r\n if (!this.player) return;\r\n const bitrates = this.player.getBitrateInfoListFor('video');\r\n if (bitrates && bitrates.length) {\r\n const levels = bitrates.map((b: any, i: number) => ({\r\n height: b.height,\r\n bitrate: b.bitrate,\r\n index: i\r\n }));\r\n this.core!.store.setState({ qualityLevels: levels });\r\n }\r\n }\r\n\r\n private updateAudioTracks() {\r\n if (!this.player) return;\r\n const tracks = this.player.getTracksFor('audio');\r\n if (tracks && tracks.length) {\r\n const list = tracks.map((t: any, i: number) => ({\r\n label: t.lang || t.roles?.[0] || `Audio ${i + 1}`,\r\n language: t.lang || '',\r\n index: i\r\n }));\r\n this.core!.store.setState({ audioTracks: list });\r\n }\r\n }\r\n\r\n destroy() {\r\n if (this.player) {\r\n this.player.destroy();\r\n this.player = null;\r\n }\r\n }\r\n}"],"names":["dashjs","DashModule","DashPlugin","__publicField","core","data","index","tracks","url","_a","bitrates","levels","b","list","t"],"mappings":"wiBAIMA,EAAUC,EAAmB,SAAWA,EAEvC,MAAMC,CAA8B,CAApC,cACLC,EAAA,YAAO,cACCA,EAAA,cAAc,MACdA,EAAA,YAA0B,MAElC,KAAKC,EAAkB,CACrB,KAAK,KAAOA,EAGZ,KAAK,KAAK,OAAO,GAAG,OAASC,GAAwC,CAC/DA,EAAK,OAAS,QAAUA,EAAK,IAAI,SAAS,MAAM,EAClD,KAAK,SAASA,EAAK,GAAG,EAGlB,KAAK,SACP,KAAK,OAAO,QAAA,EACZ,KAAK,OAAS,KAGpB,CAAC,EAGD,KAAK,KAAK,OAAO,GAAG,kBAAoBC,GAAkB,CACpD,KAAK,SAEHA,IAAU,GACZ,KAAK,OAAO,eAAe,CAAE,UAAW,CAAE,IAAK,CAAE,kBAAmB,CAAE,MAAO,EAAA,CAAK,CAAE,EAAK,GAEzF,KAAK,OAAO,eAAe,CAAE,UAAW,CAAE,IAAK,CAAE,kBAAmB,CAAE,MAAO,EAAA,CAAM,CAAE,EAAK,EAC1F,KAAK,OAAO,cAAc,QAASA,CAAK,GAG9C,CAAC,EAGD,KAAK,KAAK,OAAO,GAAG,sBAAwBA,GAAkB,CAC5D,GAAI,KAAK,OAAQ,CACf,MAAMC,EAAS,KAAK,OAAO,aAAa,OAAO,EAC3CA,EAAOD,CAAK,GACd,KAAK,OAAO,gBAAgBC,EAAOD,CAAK,CAAC,CAE7C,CACF,CAAC,CACH,CAEQ,SAASE,EAAa,CACxB,KAAK,SACP,KAAK,OAAO,QAAA,EACZ,KAAK,OAAS,MAGhB,GAAI,CACF,KAAK,OAASR,EAAO,YAAA,EAAc,OAAA,EACnC,KAAK,OAAO,WAAW,KAAK,KAAM,MAAOQ,EAAK,KAAK,KAAM,MAAM,IAAA,EAAM,SAAS,EAG9E,KAAK,OAAO,GAAGR,EAAO,YAAY,OAAO,mBAAoB,IAAM,CACjE,KAAK,oBAAA,EACL,KAAK,kBAAA,CACP,CAAC,EAED,KAAK,OAAO,GAAGA,EAAO,YAAY,OAAO,MAAQ,GAAW,OAE1D,KAAK,KAAM,aAAa,iBAAeS,EAAA,EAAE,QAAF,YAAAA,EAAS,UAAW,SAAS,GAAI,EAAI,CAC9E,CAAC,CAEH,OAAS,EAAQ,CACf,KAAK,KAAM,aAAa,qBAAqB,EAAE,OAAO,GAAI,EAAI,CAChE,CACF,CAEQ,qBAAsB,CAC5B,GAAI,CAAC,KAAK,OAAQ,OAClB,MAAMC,EAAW,KAAK,OAAO,sBAAsB,OAAO,EAC1D,GAAIA,GAAYA,EAAS,OAAQ,CAC/B,MAAMC,EAASD,EAAS,IAAI,CAACE,EAAQ,KAAe,CAClD,OAAQA,EAAE,OACV,QAASA,EAAE,QACX,MAAO,CAAA,EACP,EACF,KAAK,KAAM,MAAM,SAAS,CAAE,cAAeD,EAAQ,CACrD,CACF,CAEQ,mBAAoB,CAC1B,GAAI,CAAC,KAAK,OAAQ,OAClB,MAAMJ,EAAS,KAAK,OAAO,aAAa,OAAO,EAC/C,GAAIA,GAAUA,EAAO,OAAQ,CAC3B,MAAMM,EAAON,EAAO,IAAI,CAACO,EAAQ,IAAA,OAAe,OAC9C,MAAOA,EAAE,QAAQL,EAAAK,EAAE,QAAF,YAAAL,EAAU,KAAM,SAAS,EAAI,CAAC,GAC/C,SAAUK,EAAE,MAAQ,GACpB,MAAO,CAAA,EACP,EACF,KAAK,KAAM,MAAM,SAAS,CAAE,YAAaD,EAAM,CACjD,CACF,CAEA,SAAU,CACJ,KAAK,SACP,KAAK,OAAO,QAAA,EACZ,KAAK,OAAS,KAElB,CACF"}
package/dist/dash.d.ts ADDED
@@ -0,0 +1,210 @@
1
+ export declare class DashPlugin implements IPlugin {
2
+ name: string;
3
+ private player;
4
+ private core;
5
+ init(core: StrataCore): void;
6
+ private loadDash;
7
+ private updateQualityLevels;
8
+ private updateAudioTracks;
9
+ destroy(): void;
10
+ }
11
+
12
+ declare class EventBus {
13
+ private events;
14
+ constructor();
15
+ on<T>(event: string, callback: EventCallback<T>): () => void;
16
+ off<T>(event: string, callback: EventCallback<T>): void;
17
+ emit<T>(event: string, data?: T): void;
18
+ destroy(): void;
19
+ }
20
+
21
+ declare type EventCallback<T = any> = (data: T) => void;
22
+
23
+ declare interface IPlugin {
24
+ name: string;
25
+ init(core: StrataCore): void;
26
+ destroy?(): void;
27
+ }
28
+
29
+ declare type Listener<T> = (state: T, prevState: T) => void;
30
+
31
+ declare class NanoStore<T> {
32
+ private state;
33
+ private listeners;
34
+ constructor(initialState: T);
35
+ get(): T;
36
+ setState(partial: Partial<T> | ((prev: T) => Partial<T>)): void;
37
+ subscribe(listener: Listener<T>): () => void;
38
+ destroy(): void;
39
+ }
40
+
41
+ declare interface Notification_2 {
42
+ id: string;
43
+ message: string;
44
+ type: 'info' | 'success' | 'warning' | 'error' | 'loading';
45
+ duration?: number;
46
+ progress?: number;
47
+ }
48
+
49
+ declare interface PlayerSource {
50
+ url: string;
51
+ type?: 'hls' | 'mp4' | 'webm' | 'dash' | 'mpegts' | 'webtorrent' | string;
52
+ name?: string;
53
+ }
54
+
55
+ declare interface PlayerState {
56
+ isPlaying: boolean;
57
+ isBuffering: boolean;
58
+ currentTime: number;
59
+ duration: number;
60
+ buffered: {
61
+ start: number;
62
+ end: number;
63
+ }[];
64
+ volume: number;
65
+ isMuted: boolean;
66
+ audioGain: number;
67
+ playbackRate: number;
68
+ qualityLevels: {
69
+ height: number;
70
+ bitrate: number;
71
+ index: number;
72
+ }[];
73
+ currentQuality: number;
74
+ audioTracks: {
75
+ label: string;
76
+ language: string;
77
+ index: number;
78
+ }[];
79
+ currentAudioTrack: number;
80
+ error: string | null;
81
+ isFullscreen: boolean;
82
+ isPip: boolean;
83
+ subtitleTracks: {
84
+ label: string;
85
+ language: string;
86
+ index: number;
87
+ }[];
88
+ currentSubtitle: number;
89
+ subtitleOffset: number;
90
+ subtitleSettings: SubtitleSettings;
91
+ activeCues: string[];
92
+ viewMode: 'normal' | 'theater' | 'pip';
93
+ notifications: Notification_2[];
94
+ iconSize: 'small' | 'medium' | 'large';
95
+ themeColor: string;
96
+ theme: PlayerTheme;
97
+ sources: PlayerSource[];
98
+ currentSourceIndex: number;
99
+ }
100
+
101
+ declare type PlayerTheme = 'default' | 'pixel' | 'game' | 'hacker';
102
+
103
+ declare interface StrataConfig {
104
+ volume?: number;
105
+ muted?: boolean;
106
+ playbackRate?: number;
107
+ audioGain?: number;
108
+ theme?: PlayerTheme;
109
+ themeColor?: string;
110
+ iconSize?: 'small' | 'medium' | 'large';
111
+ subtitleSettings?: Partial<SubtitleSettings>;
112
+ disablePersistence?: boolean;
113
+ }
114
+
115
+ declare class StrataCore {
116
+ video: HTMLVideoElement;
117
+ container: HTMLElement | null;
118
+ events: EventBus;
119
+ store: NanoStore<PlayerState>;
120
+ private plugins;
121
+ private audioEngine;
122
+ private config;
123
+ private retryCount;
124
+ private maxRetries;
125
+ private retryTimer;
126
+ private currentSource;
127
+ private currentSrc;
128
+ private currentTracks;
129
+ private castInitialized;
130
+ private boundCueChange;
131
+ private boundFullscreenChange;
132
+ constructor(config?: StrataConfig, videoElement?: HTMLVideoElement);
133
+ private initVideoListeners;
134
+ triggerError(message: string, isFatal?: boolean): void;
135
+ private handleError;
136
+ private updateBuffer;
137
+ private updateSubtitles;
138
+ fetchWithRetry(url: string, retries?: number): Promise<Response>;
139
+ attach(container: HTMLElement): void;
140
+ use(plugin: IPlugin): void;
141
+ setSources(sources: PlayerSource[], tracks?: TextTrackConfig[]): void;
142
+ switchSource(index: number): void;
143
+ load(source: PlayerSource | string, tracks?: TextTrackConfig[], isRetry?: boolean): void;
144
+ addTextTrack(file: File, label: string): void;
145
+ private addTextTrackInternal;
146
+ play(): Promise<void>;
147
+ pause(): void;
148
+ togglePlay(): void;
149
+ seek(time: number): void;
150
+ skip(seconds: number): void;
151
+ setVolume(vol: number): void;
152
+ toggleMute(): void;
153
+ setAudioGain(gain: number): void;
154
+ setQuality(index: number): void;
155
+ setAudioTrack(index: number): void;
156
+ toggleFullscreen(): void;
157
+ togglePip(): void;
158
+ private initCast;
159
+ requestCast(): void;
160
+ private loadMediaToCast;
161
+ private handleCueChange;
162
+ setSubtitle(index: number): void;
163
+ updateSubtitleSettings(settings: Partial<SubtitleSettings>): void;
164
+ resetSubtitleSettings(): void;
165
+ setSubtitleOffset(offset: number): void;
166
+ download(): Promise<void>;
167
+ notify(n: Omit<Notification_2, 'id'> & {
168
+ id?: string;
169
+ }): string;
170
+ removeNotification(id: string): void;
171
+ setAppearance(settings: {
172
+ iconSize?: 'small' | 'medium' | 'large';
173
+ themeColor?: string;
174
+ theme?: PlayerTheme;
175
+ }): void;
176
+ destroy(): void;
177
+ }
178
+
179
+ declare interface SubtitleSettings {
180
+ useNative: boolean;
181
+ fixCapitalization: boolean;
182
+ backgroundOpacity: number;
183
+ backgroundBlur: boolean;
184
+ backgroundBlurAmount: number;
185
+ textSize: number;
186
+ textStyle: 'none' | 'outline' | 'raised' | 'depressed' | 'shadow';
187
+ isBold: boolean;
188
+ textColor: string;
189
+ verticalOffset: number;
190
+ }
191
+
192
+ declare interface TextTrackConfig {
193
+ kind: 'subtitles' | 'captions' | 'descriptions' | 'chapters' | 'metadata';
194
+ label: string;
195
+ src: string;
196
+ srcLang: string;
197
+ default?: boolean;
198
+ }
199
+
200
+ export { }
201
+
202
+
203
+ declare module 'react' {
204
+ namespace JSX {
205
+ interface IntrinsicElements {
206
+ 'google-cast-launcher': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>;
207
+ }
208
+ }
209
+ }
210
+
@@ -0,0 +1,71 @@
1
+ var n = Object.defineProperty;
2
+ var u = (s, t, e) => t in s ? n(s, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : s[t] = e;
3
+ var a = (s, t, e) => u(s, typeof t != "symbol" ? t + "" : t, e);
4
+ import * as h from "dashjs";
5
+ const l = h.default || h;
6
+ class p {
7
+ constructor() {
8
+ a(this, "name", "DashPlugin");
9
+ a(this, "player", null);
10
+ a(this, "core", null);
11
+ }
12
+ init(t) {
13
+ this.core = t, this.core.events.on("load", (e) => {
14
+ e.type === "dash" || e.url.includes(".mpd") ? this.loadDash(e.url) : this.player && (this.player.destroy(), this.player = null);
15
+ }), this.core.events.on("quality-request", (e) => {
16
+ this.player && (e === -1 ? this.player.updateSettings({ streaming: { abr: { autoSwitchBitrate: { video: !0 } } } }) : (this.player.updateSettings({ streaming: { abr: { autoSwitchBitrate: { video: !1 } } } }), this.player.setQualityFor("video", e)));
17
+ }), this.core.events.on("audio-track-request", (e) => {
18
+ if (this.player) {
19
+ const r = this.player.getTracksFor("audio");
20
+ r[e] && this.player.setCurrentTrack(r[e]);
21
+ }
22
+ });
23
+ }
24
+ loadDash(t) {
25
+ this.player && (this.player.destroy(), this.player = null);
26
+ try {
27
+ this.player = l.MediaPlayer().create(), this.player.initialize(this.core.video, t, this.core.store.get().isPlaying), this.player.on(l.MediaPlayer.events.STREAM_INITIALIZED, () => {
28
+ this.updateQualityLevels(), this.updateAudioTracks();
29
+ }), this.player.on(l.MediaPlayer.events.ERROR, (e) => {
30
+ var r;
31
+ this.core.triggerError(`Dash Error: ${((r = e.error) == null ? void 0 : r.message) || "Unknown"}`, !0);
32
+ });
33
+ } catch (e) {
34
+ this.core.triggerError(`Dash Init Failed: ${e.message}`, !0);
35
+ }
36
+ }
37
+ updateQualityLevels() {
38
+ if (!this.player) return;
39
+ const t = this.player.getBitrateInfoListFor("video");
40
+ if (t && t.length) {
41
+ const e = t.map((r, i) => ({
42
+ height: r.height,
43
+ bitrate: r.bitrate,
44
+ index: i
45
+ }));
46
+ this.core.store.setState({ qualityLevels: e });
47
+ }
48
+ }
49
+ updateAudioTracks() {
50
+ if (!this.player) return;
51
+ const t = this.player.getTracksFor("audio");
52
+ if (t && t.length) {
53
+ const e = t.map((r, i) => {
54
+ var o;
55
+ return {
56
+ label: r.lang || ((o = r.roles) == null ? void 0 : o[0]) || `Audio ${i + 1}`,
57
+ language: r.lang || "",
58
+ index: i
59
+ };
60
+ });
61
+ this.core.store.setState({ audioTracks: e });
62
+ }
63
+ }
64
+ destroy() {
65
+ this.player && (this.player.destroy(), this.player = null);
66
+ }
67
+ }
68
+ export {
69
+ p as DashPlugin
70
+ };
71
+ //# sourceMappingURL=dash.es.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dash.es.js","sources":["../plugins/DashPlugin.ts"],"sourcesContent":["import { StrataCore, IPlugin } from '../core/StrataCore';\r\nimport * as DashModule from 'dashjs';\r\n\r\n// Handle environment differences where dashjs might be a default export or module export\r\nconst dashjs = (DashModule as any).default || DashModule;\r\n\r\nexport class DashPlugin implements IPlugin {\r\n name = 'DashPlugin';\r\n private player: any = null;\r\n private core: StrataCore | null = null;\r\n\r\n init(core: StrataCore) {\r\n this.core = core;\r\n\r\n // Listen for load requests\r\n this.core.events.on('load', (data: { url: string, type: string }) => {\r\n if (data.type === 'dash' || data.url.includes('.mpd')) {\r\n this.loadDash(data.url);\r\n } else {\r\n // Cleanup if we switch away from DASH\r\n if (this.player) {\r\n this.player.destroy();\r\n this.player = null;\r\n }\r\n }\r\n });\r\n\r\n // Quality Handling\r\n this.core.events.on('quality-request', (index: number) => {\r\n if (this.player) {\r\n // -1 means Auto (ABR enabled), otherwise specific index\r\n if (index === -1) {\r\n this.player.updateSettings({ streaming: { abr: { autoSwitchBitrate: { video: true } } } });\r\n } else {\r\n this.player.updateSettings({ streaming: { abr: { autoSwitchBitrate: { video: false } } } });\r\n this.player.setQualityFor('video', index);\r\n }\r\n }\r\n });\r\n\r\n // Audio Track Handling\r\n this.core.events.on('audio-track-request', (index: number) => {\r\n if (this.player) {\r\n const tracks = this.player.getTracksFor('audio');\r\n if (tracks[index]) {\r\n this.player.setCurrentTrack(tracks[index]);\r\n }\r\n }\r\n });\r\n }\r\n\r\n private loadDash(url: string) {\r\n if (this.player) {\r\n this.player.destroy();\r\n this.player = null;\r\n }\r\n\r\n try {\r\n this.player = dashjs.MediaPlayer().create();\r\n this.player.initialize(this.core!.video, url, this.core!.store.get().isPlaying);\r\n\r\n // Event Listeners\r\n this.player.on(dashjs.MediaPlayer.events.STREAM_INITIALIZED, () => {\r\n this.updateQualityLevels();\r\n this.updateAudioTracks();\r\n });\r\n\r\n this.player.on(dashjs.MediaPlayer.events.ERROR, (e: any) => {\r\n // Pass error to core with retry logic enabled (fatal=true)\r\n this.core!.triggerError(`Dash Error: ${e.error?.message || 'Unknown'}`, true);\r\n });\r\n\r\n } catch (e: any) {\r\n this.core!.triggerError(`Dash Init Failed: ${e.message}`, true);\r\n }\r\n }\r\n\r\n private updateQualityLevels() {\r\n if (!this.player) return;\r\n const bitrates = this.player.getBitrateInfoListFor('video');\r\n if (bitrates && bitrates.length) {\r\n const levels = bitrates.map((b: any, i: number) => ({\r\n height: b.height,\r\n bitrate: b.bitrate,\r\n index: i\r\n }));\r\n this.core!.store.setState({ qualityLevels: levels });\r\n }\r\n }\r\n\r\n private updateAudioTracks() {\r\n if (!this.player) return;\r\n const tracks = this.player.getTracksFor('audio');\r\n if (tracks && tracks.length) {\r\n const list = tracks.map((t: any, i: number) => ({\r\n label: t.lang || t.roles?.[0] || `Audio ${i + 1}`,\r\n language: t.lang || '',\r\n index: i\r\n }));\r\n this.core!.store.setState({ audioTracks: list });\r\n }\r\n }\r\n\r\n destroy() {\r\n if (this.player) {\r\n this.player.destroy();\r\n this.player = null;\r\n }\r\n }\r\n}"],"names":["dashjs","DashModule","DashPlugin","__publicField","core","data","index","tracks","url","_a","bitrates","levels","b","list","t"],"mappings":";;;;AAIA,MAAMA,IAAUC,EAAmB,WAAWA;AAEvC,MAAMC,EAA8B;AAAA,EAApC;AACL,IAAAC,EAAA,cAAO;AACC,IAAAA,EAAA,gBAAc;AACd,IAAAA,EAAA,cAA0B;AAAA;AAAA,EAElC,KAAKC,GAAkB;AACrB,SAAK,OAAOA,GAGZ,KAAK,KAAK,OAAO,GAAG,QAAQ,CAACC,MAAwC;AACnE,MAAIA,EAAK,SAAS,UAAUA,EAAK,IAAI,SAAS,MAAM,IAClD,KAAK,SAASA,EAAK,GAAG,IAGlB,KAAK,WACP,KAAK,OAAO,QAAA,GACZ,KAAK,SAAS;AAAA,IAGpB,CAAC,GAGD,KAAK,KAAK,OAAO,GAAG,mBAAmB,CAACC,MAAkB;AACxD,MAAI,KAAK,WAEHA,MAAU,KACZ,KAAK,OAAO,eAAe,EAAE,WAAW,EAAE,KAAK,EAAE,mBAAmB,EAAE,OAAO,GAAA,EAAK,EAAE,GAAK,KAEzF,KAAK,OAAO,eAAe,EAAE,WAAW,EAAE,KAAK,EAAE,mBAAmB,EAAE,OAAO,GAAA,EAAM,EAAE,GAAK,GAC1F,KAAK,OAAO,cAAc,SAASA,CAAK;AAAA,IAG9C,CAAC,GAGD,KAAK,KAAK,OAAO,GAAG,uBAAuB,CAACA,MAAkB;AAC5D,UAAI,KAAK,QAAQ;AACf,cAAMC,IAAS,KAAK,OAAO,aAAa,OAAO;AAC/C,QAAIA,EAAOD,CAAK,KACd,KAAK,OAAO,gBAAgBC,EAAOD,CAAK,CAAC;AAAA,MAE7C;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEQ,SAASE,GAAa;AAC5B,IAAI,KAAK,WACP,KAAK,OAAO,QAAA,GACZ,KAAK,SAAS;AAGhB,QAAI;AACF,WAAK,SAASR,EAAO,YAAA,EAAc,OAAA,GACnC,KAAK,OAAO,WAAW,KAAK,KAAM,OAAOQ,GAAK,KAAK,KAAM,MAAM,IAAA,EAAM,SAAS,GAG9E,KAAK,OAAO,GAAGR,EAAO,YAAY,OAAO,oBAAoB,MAAM;AACjE,aAAK,oBAAA,GACL,KAAK,kBAAA;AAAA,MACP,CAAC,GAED,KAAK,OAAO,GAAGA,EAAO,YAAY,OAAO,OAAO,CAAC,MAAW;;AAE1D,aAAK,KAAM,aAAa,iBAAeS,IAAA,EAAE,UAAF,gBAAAA,EAAS,YAAW,SAAS,IAAI,EAAI;AAAA,MAC9E,CAAC;AAAA,IAEH,SAAS,GAAQ;AACf,WAAK,KAAM,aAAa,qBAAqB,EAAE,OAAO,IAAI,EAAI;AAAA,IAChE;AAAA,EACF;AAAA,EAEQ,sBAAsB;AAC5B,QAAI,CAAC,KAAK,OAAQ;AAClB,UAAMC,IAAW,KAAK,OAAO,sBAAsB,OAAO;AAC1D,QAAIA,KAAYA,EAAS,QAAQ;AAC/B,YAAMC,IAASD,EAAS,IAAI,CAACE,GAAQ,OAAe;AAAA,QAClD,QAAQA,EAAE;AAAA,QACV,SAASA,EAAE;AAAA,QACX,OAAO;AAAA,MAAA,EACP;AACF,WAAK,KAAM,MAAM,SAAS,EAAE,eAAeD,GAAQ;AAAA,IACrD;AAAA,EACF;AAAA,EAEQ,oBAAoB;AAC1B,QAAI,CAAC,KAAK,OAAQ;AAClB,UAAMJ,IAAS,KAAK,OAAO,aAAa,OAAO;AAC/C,QAAIA,KAAUA,EAAO,QAAQ;AAC3B,YAAMM,IAAON,EAAO,IAAI,CAACO,GAAQ,MAAA;;AAAe;AAAA,UAC9C,OAAOA,EAAE,UAAQL,IAAAK,EAAE,UAAF,gBAAAL,EAAU,OAAM,SAAS,IAAI,CAAC;AAAA,UAC/C,UAAUK,EAAE,QAAQ;AAAA,UACpB,OAAO;AAAA,QAAA;AAAA,OACP;AACF,WAAK,KAAM,MAAM,SAAS,EAAE,aAAaD,GAAM;AAAA,IACjD;AAAA,EACF;AAAA,EAEA,UAAU;AACR,IAAI,KAAK,WACP,KAAK,OAAO,QAAA,GACZ,KAAK,SAAS;AAAA,EAElB;AACF;"}
package/dist/hls.d.ts CHANGED
@@ -46,7 +46,7 @@ declare interface Notification_2 {
46
46
 
47
47
  declare interface PlayerSource {
48
48
  url: string;
49
- type?: 'hls' | 'mp4' | 'webm' | 'dash' | string;
49
+ type?: 'hls' | 'mp4' | 'webm' | 'dash' | 'mpegts' | 'webtorrent' | string;
50
50
  name?: string;
51
51
  }
52
52