@scarlett-player/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.
Files changed (59) hide show
  1. package/dist/error-handler.d.ts.map +1 -0
  2. package/dist/error-handler.js +300 -0
  3. package/dist/error-handler.js.map +1 -0
  4. package/dist/events/event-bus.d.ts.map +1 -0
  5. package/dist/events/event-bus.js +407 -0
  6. package/dist/events/event-bus.js.map +1 -0
  7. package/dist/index.cjs +2 -0
  8. package/dist/index.cjs.map +1 -0
  9. package/dist/index.d.ts +16 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +2271 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/logger.d.ts.map +1 -0
  14. package/dist/logger.js +272 -0
  15. package/dist/logger.js.map +1 -0
  16. package/dist/plugin-api.d.ts +147 -0
  17. package/dist/plugin-api.d.ts.map +1 -0
  18. package/dist/plugin-api.js +160 -0
  19. package/dist/plugin-api.js.map +1 -0
  20. package/dist/plugin-manager.d.ts +52 -0
  21. package/dist/plugin-manager.d.ts.map +1 -0
  22. package/dist/plugin-manager.js +224 -0
  23. package/dist/plugin-manager.js.map +1 -0
  24. package/dist/scarlett-player.d.ts +404 -0
  25. package/dist/scarlett-player.d.ts.map +1 -0
  26. package/dist/scarlett-player.js +769 -0
  27. package/dist/scarlett-player.js.map +1 -0
  28. package/dist/state/computed.d.ts.map +1 -0
  29. package/dist/state/computed.js +134 -0
  30. package/dist/state/computed.js.map +1 -0
  31. package/dist/state/effect.d.ts.map +1 -0
  32. package/dist/state/effect.js +77 -0
  33. package/dist/state/effect.js.map +1 -0
  34. package/dist/state/index.d.ts.map +1 -0
  35. package/dist/state/index.js +9 -0
  36. package/dist/state/index.js.map +1 -0
  37. package/dist/state/signal.d.ts.map +1 -0
  38. package/dist/state/signal.js +126 -0
  39. package/dist/state/signal.js.map +1 -0
  40. package/dist/state/state-manager.d.ts.map +1 -0
  41. package/dist/state/state-manager.js +334 -0
  42. package/dist/state/state-manager.js.map +1 -0
  43. package/dist/types/events.d.ts +323 -0
  44. package/dist/types/events.d.ts.map +1 -0
  45. package/dist/types/events.js +7 -0
  46. package/dist/types/events.js.map +1 -0
  47. package/dist/types/index.d.ts +9 -0
  48. package/dist/types/index.d.ts.map +1 -0
  49. package/dist/types/index.js +7 -0
  50. package/dist/types/index.js.map +1 -0
  51. package/dist/types/plugin.d.ts +141 -0
  52. package/dist/types/plugin.d.ts.map +1 -0
  53. package/dist/types/plugin.js +8 -0
  54. package/dist/types/plugin.js.map +1 -0
  55. package/dist/types/state.d.ts +232 -0
  56. package/dist/types/state.d.ts.map +1 -0
  57. package/dist/types/state.js +8 -0
  58. package/dist/types/state.js.map +1 -0
  59. package/package.json +64 -0
@@ -0,0 +1,407 @@
1
+ /**
2
+ * EventBus - Type-safe event system for Scarlett Player.
3
+ *
4
+ * Provides pub/sub event communication between player components and plugins
5
+ * with optional interceptors for event modification/cancellation.
6
+ *
7
+ * Target size: ~1KB
8
+ */
9
+ /**
10
+ * Default options for EventBus.
11
+ */
12
+ const DEFAULT_OPTIONS = {
13
+ maxListeners: 100,
14
+ async: false,
15
+ interceptors: true,
16
+ };
17
+ /**
18
+ * EventBus provides type-safe event emission and subscription.
19
+ *
20
+ * Features:
21
+ * - Type-safe events based on PlayerEventMap
22
+ * - Event interceptors for modification/cancellation
23
+ * - One-time subscriptions with once()
24
+ * - Async event emission
25
+ * - Error handling for handlers
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * const events = new EventBus();
30
+ *
31
+ * // Subscribe to event
32
+ * events.on('playback:play', () => {
33
+ * console.log('Playing!');
34
+ * });
35
+ *
36
+ * // Emit event
37
+ * events.emit('playback:play', undefined);
38
+ *
39
+ * // Intercept events
40
+ * events.intercept('playback:timeupdate', (payload) => {
41
+ * // Modify payload or return null to cancel
42
+ * return { currentTime: Math.floor(payload.currentTime) };
43
+ * });
44
+ * ```
45
+ */
46
+ export class EventBus {
47
+ /**
48
+ * Create a new EventBus.
49
+ *
50
+ * @param options - Optional configuration
51
+ */
52
+ constructor(options) {
53
+ /** Event listeners map */
54
+ this.listeners = new Map();
55
+ /** One-time listeners (removed after first call) */
56
+ this.onceListeners = new Map();
57
+ /** Event interceptors map */
58
+ this.interceptors = new Map();
59
+ this.options = { ...DEFAULT_OPTIONS, ...options };
60
+ }
61
+ /**
62
+ * Subscribe to an event.
63
+ *
64
+ * @param event - Event name
65
+ * @param handler - Event handler function
66
+ * @returns Unsubscribe function
67
+ *
68
+ * @example
69
+ * ```ts
70
+ * const unsub = events.on('playback:play', () => {
71
+ * console.log('Playing!');
72
+ * });
73
+ *
74
+ * // Later: unsubscribe
75
+ * unsub();
76
+ * ```
77
+ */
78
+ on(event, handler) {
79
+ if (!this.listeners.has(event)) {
80
+ this.listeners.set(event, new Set());
81
+ }
82
+ const handlers = this.listeners.get(event);
83
+ handlers.add(handler);
84
+ // Warn if max listeners exceeded
85
+ this.checkMaxListeners(event);
86
+ return () => this.off(event, handler);
87
+ }
88
+ /**
89
+ * Subscribe to an event once (auto-unsubscribe after first call).
90
+ *
91
+ * @param event - Event name
92
+ * @param handler - Event handler function
93
+ * @returns Unsubscribe function
94
+ *
95
+ * @example
96
+ * ```ts
97
+ * events.once('player:ready', () => {
98
+ * console.log('Player ready!');
99
+ * });
100
+ * ```
101
+ */
102
+ once(event, handler) {
103
+ if (!this.onceListeners.has(event)) {
104
+ this.onceListeners.set(event, new Set());
105
+ }
106
+ const handlers = this.onceListeners.get(event);
107
+ handlers.add(handler);
108
+ // Also track in regular listeners for counting
109
+ if (!this.listeners.has(event)) {
110
+ this.listeners.set(event, new Set());
111
+ }
112
+ return () => {
113
+ handlers.delete(handler);
114
+ };
115
+ }
116
+ /**
117
+ * Unsubscribe from an event.
118
+ *
119
+ * @param event - Event name
120
+ * @param handler - Event handler function to remove
121
+ *
122
+ * @example
123
+ * ```ts
124
+ * const handler = () => console.log('Playing!');
125
+ * events.on('playback:play', handler);
126
+ * events.off('playback:play', handler);
127
+ * ```
128
+ */
129
+ off(event, handler) {
130
+ const handlers = this.listeners.get(event);
131
+ if (handlers) {
132
+ handlers.delete(handler);
133
+ if (handlers.size === 0) {
134
+ this.listeners.delete(event);
135
+ }
136
+ }
137
+ const onceHandlers = this.onceListeners.get(event);
138
+ if (onceHandlers) {
139
+ onceHandlers.delete(handler);
140
+ if (onceHandlers.size === 0) {
141
+ this.onceListeners.delete(event);
142
+ }
143
+ }
144
+ }
145
+ /**
146
+ * Emit an event synchronously.
147
+ *
148
+ * Runs interceptors first, then calls all handlers.
149
+ *
150
+ * @param event - Event name
151
+ * @param payload - Event payload
152
+ *
153
+ * @example
154
+ * ```ts
155
+ * events.emit('playback:play', undefined);
156
+ * events.emit('playback:timeupdate', { currentTime: 10.5 });
157
+ * ```
158
+ */
159
+ emit(event, payload) {
160
+ // Run interceptors
161
+ const interceptedPayload = this.runInterceptors(event, payload);
162
+ if (interceptedPayload === null) {
163
+ // Event cancelled by interceptor
164
+ return;
165
+ }
166
+ // Call regular handlers
167
+ const handlers = this.listeners.get(event);
168
+ if (handlers) {
169
+ // Create array to avoid calling handlers added during iteration
170
+ const handlersArray = Array.from(handlers);
171
+ handlersArray.forEach(handler => {
172
+ this.safeCallHandler(handler, interceptedPayload);
173
+ });
174
+ }
175
+ // Call once handlers
176
+ const onceHandlers = this.onceListeners.get(event);
177
+ if (onceHandlers) {
178
+ // Create array to avoid modification during iteration
179
+ const handlersArray = Array.from(onceHandlers);
180
+ handlersArray.forEach(handler => {
181
+ this.safeCallHandler(handler, interceptedPayload);
182
+ });
183
+ // Clear once handlers after calling
184
+ this.onceListeners.delete(event);
185
+ }
186
+ }
187
+ /**
188
+ * Emit an event asynchronously (next tick).
189
+ *
190
+ * @param event - Event name
191
+ * @param payload - Event payload
192
+ * @returns Promise that resolves when all handlers complete
193
+ *
194
+ * @example
195
+ * ```ts
196
+ * await events.emitAsync('media:loaded', { src: 'video.mp4', type: 'video/mp4' });
197
+ * ```
198
+ */
199
+ async emitAsync(event, payload) {
200
+ // Run interceptors
201
+ const interceptedPayload = await this.runInterceptorsAsync(event, payload);
202
+ if (interceptedPayload === null) {
203
+ // Event cancelled by interceptor
204
+ return;
205
+ }
206
+ // Call regular handlers
207
+ const handlers = this.listeners.get(event);
208
+ if (handlers) {
209
+ const promises = Array.from(handlers).map(handler => this.safeCallHandlerAsync(handler, interceptedPayload));
210
+ await Promise.all(promises);
211
+ }
212
+ // Call once handlers
213
+ const onceHandlers = this.onceListeners.get(event);
214
+ if (onceHandlers) {
215
+ const handlersArray = Array.from(onceHandlers);
216
+ const promises = handlersArray.map(handler => this.safeCallHandlerAsync(handler, interceptedPayload));
217
+ await Promise.all(promises);
218
+ // Clear once handlers after calling
219
+ this.onceListeners.delete(event);
220
+ }
221
+ }
222
+ /**
223
+ * Add an event interceptor.
224
+ *
225
+ * Interceptors run before handlers and can modify or cancel events.
226
+ *
227
+ * @param event - Event name
228
+ * @param interceptor - Interceptor function
229
+ * @returns Remove interceptor function
230
+ *
231
+ * @example
232
+ * ```ts
233
+ * events.intercept('playback:timeupdate', (payload) => {
234
+ * // Round time to 2 decimals
235
+ * return { currentTime: Math.round(payload.currentTime * 100) / 100 };
236
+ * });
237
+ *
238
+ * // Cancel events
239
+ * events.intercept('playback:play', (payload) => {
240
+ * if (notReady) return null; // Cancel event
241
+ * return payload;
242
+ * });
243
+ * ```
244
+ */
245
+ intercept(event, interceptor) {
246
+ if (!this.options.interceptors) {
247
+ return () => { }; // No-op if interceptors disabled
248
+ }
249
+ if (!this.interceptors.has(event)) {
250
+ this.interceptors.set(event, new Set());
251
+ }
252
+ const interceptorsSet = this.interceptors.get(event);
253
+ interceptorsSet.add(interceptor);
254
+ return () => {
255
+ interceptorsSet.delete(interceptor);
256
+ if (interceptorsSet.size === 0) {
257
+ this.interceptors.delete(event);
258
+ }
259
+ };
260
+ }
261
+ /**
262
+ * Remove all listeners for an event (or all events if no event specified).
263
+ *
264
+ * @param event - Optional event name
265
+ *
266
+ * @example
267
+ * ```ts
268
+ * events.removeAllListeners('playback:play'); // Remove all playback:play listeners
269
+ * events.removeAllListeners(); // Remove ALL listeners
270
+ * ```
271
+ */
272
+ removeAllListeners(event) {
273
+ if (event) {
274
+ this.listeners.delete(event);
275
+ this.onceListeners.delete(event);
276
+ }
277
+ else {
278
+ this.listeners.clear();
279
+ this.onceListeners.clear();
280
+ }
281
+ }
282
+ /**
283
+ * Get the number of listeners for an event.
284
+ *
285
+ * @param event - Event name
286
+ * @returns Number of listeners
287
+ *
288
+ * @example
289
+ * ```ts
290
+ * events.listenerCount('playback:play'); // 3
291
+ * ```
292
+ */
293
+ listenerCount(event) {
294
+ const regularCount = this.listeners.get(event)?.size ?? 0;
295
+ const onceCount = this.onceListeners.get(event)?.size ?? 0;
296
+ return regularCount + onceCount;
297
+ }
298
+ /**
299
+ * Destroy event bus and cleanup all listeners/interceptors.
300
+ *
301
+ * @example
302
+ * ```ts
303
+ * events.destroy();
304
+ * ```
305
+ */
306
+ destroy() {
307
+ this.listeners.clear();
308
+ this.onceListeners.clear();
309
+ this.interceptors.clear();
310
+ }
311
+ /**
312
+ * Run interceptors synchronously.
313
+ * @private
314
+ */
315
+ runInterceptors(event, payload) {
316
+ if (!this.options.interceptors) {
317
+ return payload;
318
+ }
319
+ const interceptorsSet = this.interceptors.get(event);
320
+ if (!interceptorsSet || interceptorsSet.size === 0) {
321
+ return payload;
322
+ }
323
+ let currentPayload = payload;
324
+ for (const interceptor of interceptorsSet) {
325
+ try {
326
+ currentPayload = interceptor(currentPayload);
327
+ if (currentPayload === null) {
328
+ // Event cancelled
329
+ return null;
330
+ }
331
+ }
332
+ catch (error) {
333
+ console.error('[EventBus] Error in interceptor:', error);
334
+ // Continue with other interceptors
335
+ }
336
+ }
337
+ return currentPayload;
338
+ }
339
+ /**
340
+ * Run interceptors asynchronously.
341
+ * @private
342
+ */
343
+ async runInterceptorsAsync(event, payload) {
344
+ if (!this.options.interceptors) {
345
+ return payload;
346
+ }
347
+ const interceptorsSet = this.interceptors.get(event);
348
+ if (!interceptorsSet || interceptorsSet.size === 0) {
349
+ return payload;
350
+ }
351
+ let currentPayload = payload;
352
+ for (const interceptor of interceptorsSet) {
353
+ try {
354
+ const result = interceptor(currentPayload);
355
+ currentPayload = (result instanceof Promise ? await result : result);
356
+ if (currentPayload === null) {
357
+ // Event cancelled
358
+ return null;
359
+ }
360
+ }
361
+ catch (error) {
362
+ console.error('[EventBus] Error in interceptor:', error);
363
+ // Continue with other interceptors
364
+ }
365
+ }
366
+ return currentPayload;
367
+ }
368
+ /**
369
+ * Safely call a handler with error handling.
370
+ * @private
371
+ */
372
+ safeCallHandler(handler, payload) {
373
+ try {
374
+ handler(payload);
375
+ }
376
+ catch (error) {
377
+ console.error('[EventBus] Error in event handler:', error);
378
+ }
379
+ }
380
+ /**
381
+ * Safely call a handler asynchronously with error handling.
382
+ * @private
383
+ */
384
+ async safeCallHandlerAsync(handler, payload) {
385
+ try {
386
+ const result = handler(payload);
387
+ if (result instanceof Promise) {
388
+ await result;
389
+ }
390
+ }
391
+ catch (error) {
392
+ console.error('[EventBus] Error in event handler:', error);
393
+ }
394
+ }
395
+ /**
396
+ * Check if max listeners exceeded and warn.
397
+ * @private
398
+ */
399
+ checkMaxListeners(event) {
400
+ const count = this.listenerCount(event);
401
+ if (count > this.options.maxListeners) {
402
+ console.warn(`[EventBus] Max listeners (${this.options.maxListeners}) exceeded for event: ${event}. ` +
403
+ `Current count: ${count}. This may indicate a memory leak.`);
404
+ }
405
+ }
406
+ }
407
+ //# sourceMappingURL=event-bus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-bus.js","sourceRoot":"","sources":["../../src/events/event-bus.ts"],"names":[],"mappings":"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"}
package/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e={current:null};function t(t){e.current=t,exports.currentEffect=t}function r(){return e.current}exports.currentEffect=null;class s{constructor(e){this.subscribers=new Set,this.value=e}get(){return exports.currentEffect&&this.subscribers.add(exports.currentEffect),this.value}set(e){Object.is(this.value,e)||(this.value=e,this.notify())}update(e){this.set(e(this.value))}subscribe(e){return this.subscribers.add(e),()=>this.subscribers.delete(e)}notify(){this.subscribers.forEach(e=>{try{e()}catch(t){console.error("[Scarlett Player] Error in signal subscriber:",t)}})}destroy(){this.subscribers.clear()}getSubscriberCount(){return this.subscribers.size}}function i(e){return new s(e)}class n{constructor(e){this.dirty=!0,this.subscribers=new Set,this.dependencies=new Set,this.computation=e,this.invalidateCallback=()=>this.invalidate()}get(){if(this.dirty){const e=r();t(this.invalidateCallback);try{this.value=this.computation(),this.dirty=!1}finally{t(e)}}return exports.currentEffect&&this.subscribers.add(exports.currentEffect),this.value}invalidate(){const e=this.dirty;this.dirty=!0,(!e||this.subscribers.size>0)&&this.notifySubscribers()}subscribe(e){return this.subscribers.add(e),()=>this.subscribers.delete(e)}notifySubscribers(){this.subscribers.forEach(e=>{try{e()}catch(t){console.error("[Scarlett Player] Error in computed subscriber:",t)}})}destroy(){this.dependencies.forEach(e=>e()),this.dependencies.clear(),this.subscribers.clear(),this.value=void 0,this.dirty=!0}getSubscriberCount(){return this.subscribers.size}}const a={playbackState:"idle",playing:!1,paused:!0,ended:!1,buffering:!1,waiting:!1,seeking:!1,currentTime:0,duration:NaN,buffered:null,bufferedAmount:0,mediaType:"unknown",source:null,title:"",poster:"",volume:1,muted:!1,playbackRate:1,fullscreen:!1,pip:!1,controlsVisible:!0,qualities:[],currentQuality:null,audioTracks:[],currentAudioTrack:null,textTracks:[],currentTextTrack:null,live:!1,liveEdge:!0,seekableRange:null,liveLatency:0,lowLatencyMode:!1,chapters:[],currentChapter:null,error:null,bandwidth:0,autoplay:!1,loop:!1,airplayAvailable:!1,airplayActive:!1,chromecastAvailable:!1,chromecastActive:!1,interacting:!1,hovering:!1,focused:!1};class o{constructor(e){this.signals=new Map,this.changeSubscribers=new Set,this.initializeSignals(e)}initializeSignals(e){const t={...a,...e};for(const[r,s]of Object.entries(t)){const e=r,t=i(s);t.subscribe(()=>{this.notifyChangeSubscribers(e)}),this.signals.set(e,t)}}get(e){const t=this.signals.get(e);if(!t)throw new Error(`[StateManager] Unknown state key: ${e}`);return t}getValue(e){return this.get(e).get()}set(e,t){this.get(e).set(t)}update(e){for(const[t,r]of Object.entries(e)){const e=t;this.signals.has(e)&&this.set(e,r)}}subscribeToKey(e,t){const r=this.get(e);return r.subscribe(()=>{t(r.get())})}subscribe(e){return this.changeSubscribers.add(e),()=>this.changeSubscribers.delete(e)}notifyChangeSubscribers(e){const t=this.get(e).get(),r={key:e,value:t,previousValue:t};this.changeSubscribers.forEach(e=>{try{e(r)}catch(t){console.error("[StateManager] Error in change subscriber:",t)}})}reset(){this.update(a)}resetKey(e){const t=a[e];this.set(e,t)}snapshot(){const e={};for(const[t,r]of this.signals)e[t]=r.get();return Object.freeze(e)}getSubscriberCount(e){return this.signals.get(e)?.getSubscriberCount()??0}destroy(){this.signals.forEach(e=>e.destroy()),this.signals.clear(),this.changeSubscribers.clear()}}const l={maxListeners:100,async:!1,interceptors:!0};class u{constructor(e){this.listeners=new Map,this.onceListeners=new Map,this.interceptors=new Map,this.options={...l,...e}}on(e,t){this.listeners.has(e)||this.listeners.set(e,new Set);return this.listeners.get(e).add(t),this.checkMaxListeners(e),()=>this.off(e,t)}once(e,t){this.onceListeners.has(e)||this.onceListeners.set(e,new Set);const r=this.onceListeners.get(e);return r.add(t),this.listeners.has(e)||this.listeners.set(e,new Set),()=>{r.delete(t)}}off(e,t){const r=this.listeners.get(e);r&&(r.delete(t),0===r.size&&this.listeners.delete(e));const s=this.onceListeners.get(e);s&&(s.delete(t),0===s.size&&this.onceListeners.delete(e))}emit(e,t){const r=this.runInterceptors(e,t);if(null===r)return;const s=this.listeners.get(e);if(s){Array.from(s).forEach(e=>{this.safeCallHandler(e,r)})}const i=this.onceListeners.get(e);if(i){Array.from(i).forEach(e=>{this.safeCallHandler(e,r)}),this.onceListeners.delete(e)}}async emitAsync(e,t){const r=await this.runInterceptorsAsync(e,t);if(null===r)return;const s=this.listeners.get(e);if(s){const e=Array.from(s).map(e=>this.safeCallHandlerAsync(e,r));await Promise.all(e)}const i=this.onceListeners.get(e);if(i){const t=Array.from(i).map(e=>this.safeCallHandlerAsync(e,r));await Promise.all(t),this.onceListeners.delete(e)}}intercept(e,t){if(!this.options.interceptors)return()=>{};this.interceptors.has(e)||this.interceptors.set(e,new Set);const r=this.interceptors.get(e);return r.add(t),()=>{r.delete(t),0===r.size&&this.interceptors.delete(e)}}removeAllListeners(e){e?(this.listeners.delete(e),this.onceListeners.delete(e)):(this.listeners.clear(),this.onceListeners.clear())}listenerCount(e){return(this.listeners.get(e)?.size??0)+(this.onceListeners.get(e)?.size??0)}destroy(){this.listeners.clear(),this.onceListeners.clear(),this.interceptors.clear()}runInterceptors(e,t){if(!this.options.interceptors)return t;const r=this.interceptors.get(e);if(!r||0===r.size)return t;let s=t;for(const n of r)try{if(s=n(s),null===s)return null}catch(i){console.error("[EventBus] Error in interceptor:",i)}return s}async runInterceptorsAsync(e,t){if(!this.options.interceptors)return t;const r=this.interceptors.get(e);if(!r||0===r.size)return t;let s=t;for(const n of r)try{const e=n(s);if(s=e instanceof Promise?await e:e,null===s)return null}catch(i){console.error("[EventBus] Error in interceptor:",i)}return s}safeCallHandler(e,t){try{e(t)}catch(r){console.error("[EventBus] Error in event handler:",r)}}async safeCallHandlerAsync(e,t){try{const r=e(t);r instanceof Promise&&await r}catch(r){console.error("[EventBus] Error in event handler:",r)}}checkMaxListeners(e){const t=this.listenerCount(e);t>this.options.maxListeners&&console.warn(`[EventBus] Max listeners (${this.options.maxListeners}) exceeded for event: ${e}. Current count: ${t}. This may indicate a memory leak.`)}}const c=["debug","info","warn","error"],h=e=>{const t=`${e.scope?`[${e.scope}]`:"[ScarlettPlayer]"} ${e.message}`,r=e.metadata??"";switch(e.level){case"debug":console.debug(t,r);break;case"info":console.info(t,r);break;case"warn":console.warn(t,r);break;case"error":console.error(t,r)}};class g{constructor(e){this.level=e?.level??"warn",this.scope=e?.scope,this.enabled=e?.enabled??!0,this.handlers=e?.handlers??[h]}child(e){return new g({level:this.level,scope:this.scope?`${this.scope}:${e}`:e,enabled:this.enabled,handlers:this.handlers})}debug(e,t){this.log("debug",e,t)}info(e,t){this.log("info",e,t)}warn(e,t){this.log("warn",e,t)}error(e,t){this.log("error",e,t)}setLevel(e){this.level=e}setEnabled(e){this.enabled=e}addHandler(e){this.handlers.push(e)}removeHandler(e){const t=this.handlers.indexOf(e);-1!==t&&this.handlers.splice(t,1)}log(e,t,r){if(!this.enabled||!this.shouldLog(e))return;const s={level:e,message:t,timestamp:Date.now(),scope:this.scope,metadata:r};for(const n of this.handlers)try{n(s)}catch(i){console.error("[Logger] Handler error:",i)}}shouldLog(e){return c.indexOf(e)>=c.indexOf(this.level)}}var d=(e=>(e.SOURCE_NOT_SUPPORTED="SOURCE_NOT_SUPPORTED",e.SOURCE_LOAD_FAILED="SOURCE_LOAD_FAILED",e.PROVIDER_NOT_FOUND="PROVIDER_NOT_FOUND",e.PROVIDER_SETUP_FAILED="PROVIDER_SETUP_FAILED",e.PLUGIN_SETUP_FAILED="PLUGIN_SETUP_FAILED",e.PLUGIN_NOT_FOUND="PLUGIN_NOT_FOUND",e.PLAYBACK_FAILED="PLAYBACK_FAILED",e.MEDIA_DECODE_ERROR="MEDIA_DECODE_ERROR",e.MEDIA_NETWORK_ERROR="MEDIA_NETWORK_ERROR",e.UNKNOWN_ERROR="UNKNOWN_ERROR",e))(d||{});class p{constructor(e,t,r){this.errors=[],this.eventBus=e,this.logger=t,this.maxHistory=r?.maxHistory??10}handle(e,t){const r=this.normalizeError(e,t);return this.addToHistory(r),this.logError(r),this.eventBus.emit("error",r),r}throw(e,t,r){const s={code:e,message:t,fatal:r?.fatal??this.isFatalCode(e),timestamp:Date.now(),context:r?.context,originalError:r?.originalError};return this.handle(s,r?.context)}getHistory(){return[...this.errors]}getLastError(){return this.errors[this.errors.length-1]??null}clearHistory(){this.errors=[]}hasFatalError(){return this.errors.some(e=>e.fatal)}normalizeError(e,t){return this.isPlayerError(e)?{...e,context:{...e.context,...t}}:{code:this.getErrorCode(e),message:e.message,fatal:this.isFatal(e),timestamp:Date.now(),context:t,originalError:e}}getErrorCode(e){const t=e.message.toLowerCase();return t.includes("network")?"MEDIA_NETWORK_ERROR":t.includes("decode")?"MEDIA_DECODE_ERROR":t.includes("source")?"SOURCE_LOAD_FAILED":t.includes("plugin")?"PLUGIN_SETUP_FAILED":t.includes("provider")?"PROVIDER_SETUP_FAILED":"UNKNOWN_ERROR"}isFatal(e){return this.isFatalCode(this.getErrorCode(e))}isFatalCode(e){return["SOURCE_NOT_SUPPORTED","PROVIDER_NOT_FOUND","MEDIA_DECODE_ERROR"].includes(e)}isPlayerError(e){return"object"==typeof e&&null!==e&&"code"in e&&"message"in e&&"fatal"in e&&"timestamp"in e}addToHistory(e){this.errors.push(e),this.errors.length>this.maxHistory&&this.errors.shift()}logError(e){const t=`[${e.code}] ${e.message}`;e.fatal?this.logger.error(t,{code:e.code,context:e.context}):this.logger.warn(t,{code:e.code,context:e.context})}}class y{constructor(e,t){this.cleanupFns=[],this.pluginId=e,this.stateManager=t.stateManager,this.eventBus=t.eventBus,this.container=t.container,this.getPluginFn=t.getPlugin,this.logger={debug:(r,s)=>t.logger.debug(`[${e}] ${r}`,s),info:(r,s)=>t.logger.info(`[${e}] ${r}`,s),warn:(r,s)=>t.logger.warn(`[${e}] ${r}`,s),error:(r,s)=>t.logger.error(`[${e}] ${r}`,s)}}getState(e){return this.stateManager.getValue(e)}setState(e,t){this.stateManager.set(e,t)}on(e,t){return this.eventBus.on(e,t)}off(e,t){this.eventBus.off(e,t)}emit(e,t){this.eventBus.emit(e,t)}getPlugin(e){return this.getPluginFn(e)}onDestroy(e){this.cleanupFns.push(e)}subscribeToState(e){return this.stateManager.subscribe(e)}runCleanups(){for(const t of this.cleanupFns)try{t()}catch(e){this.logger.error("Cleanup function failed",{error:e})}this.cleanupFns=[]}getCleanupFns(){return this.cleanupFns}}class f{constructor(e,t,r,s){this.plugins=new Map,this.eventBus=e,this.stateManager=t,this.logger=r,this.container=s.container}register(e,t){if(this.plugins.has(e.id))throw new Error(`Plugin "${e.id}" is already registered`);this.validatePlugin(e);const r=new y(e.id,{stateManager:this.stateManager,eventBus:this.eventBus,logger:this.logger,container:this.container,getPlugin:e=>this.getReadyPlugin(e)});this.plugins.set(e.id,{plugin:e,state:"registered",config:t,cleanupFns:[],api:r}),this.logger.info(`Plugin registered: ${e.id}`),this.eventBus.emit("plugin:registered",{name:e.id,type:e.type})}async unregister(e){const t=this.plugins.get(e);t&&("ready"===t.state&&await this.destroyPlugin(e),this.plugins.delete(e),this.logger.info(`Plugin unregistered: ${e}`))}async initAll(){const e=this.resolveDependencyOrder();for(const t of e)await this.initPlugin(t)}async initPlugin(e){const t=this.plugins.get(e);if(!t)throw new Error(`Plugin "${e}" not found`);if("ready"!==t.state){if("initializing"===t.state)throw new Error(`Plugin "${e}" is already initializing (possible circular dependency)`);for(const r of t.plugin.dependencies||[]){const t=this.plugins.get(r);if(!t)throw new Error(`Plugin "${e}" depends on missing plugin "${r}"`);"ready"!==t.state&&await this.initPlugin(r)}try{if(t.state="initializing",t.plugin.onStateChange){const e=this.stateManager.subscribe(t.plugin.onStateChange.bind(t.plugin));t.api.onDestroy(e)}if(t.plugin.onError){const e=this.eventBus.on("error",e=>{t.plugin.onError?.(e.originalError||new Error(e.message))});t.api.onDestroy(e)}await t.plugin.init(t.api,t.config),t.state="ready",this.logger.info(`Plugin ready: ${e}`),this.eventBus.emit("plugin:active",{name:e})}catch(r){throw t.state="error",t.error=r,this.logger.error(`Plugin init failed: ${e}`,{error:r}),this.eventBus.emit("plugin:error",{name:e,error:r}),r}}}async destroyAll(){const e=this.resolveDependencyOrder().reverse();for(const t of e)await this.destroyPlugin(t)}async destroyPlugin(e){const t=this.plugins.get(e);if(t&&"ready"===t.state)try{await t.plugin.destroy(),t.api.runCleanups(),t.state="registered",this.logger.info(`Plugin destroyed: ${e}`),this.eventBus.emit("plugin:destroyed",{name:e})}catch(r){this.logger.error(`Plugin destroy failed: ${e}`,{error:r}),t.state="registered"}}getPlugin(e){const t=this.plugins.get(e);return t?t.plugin:null}getReadyPlugin(e){const t=this.plugins.get(e);return"ready"===t?.state?t.plugin:null}hasPlugin(e){return this.plugins.has(e)}getPluginState(e){return this.plugins.get(e)?.state??null}getPluginIds(){return Array.from(this.plugins.keys())}getReadyPlugins(){return Array.from(this.plugins.values()).filter(e=>"ready"===e.state).map(e=>e.plugin)}getPluginsByType(e){return Array.from(this.plugins.values()).filter(t=>t.plugin.type===e).map(e=>e.plugin)}selectProvider(e){const t=this.getPluginsByType("provider");for(const r of t){const t=r.canPlay;if("function"==typeof t&&t(e))return r}return null}resolveDependencyOrder(){const e=new Set,t=new Set,r=[],s=(i,n=[])=>{if(e.has(i))return;if(t.has(i)){const e=[...n,i].join(" -> ");throw new Error(`Circular dependency detected: ${e}`)}const a=this.plugins.get(i);if(a){t.add(i);for(const e of a.plugin.dependencies||[])this.plugins.has(e)&&s(e,[...n,i]);t.delete(i),e.add(i),r.push(i)}};for(const i of this.plugins.keys())s(i);return r}validatePlugin(e){if(!e.id||"string"!=typeof e.id)throw new Error("Plugin must have a valid id");if(!e.name||"string"!=typeof e.name)throw new Error(`Plugin "${e.id}" must have a valid name`);if(!e.version||"string"!=typeof e.version)throw new Error(`Plugin "${e.id}" must have a valid version`);if(!e.type||"string"!=typeof e.type)throw new Error(`Plugin "${e.id}" must have a valid type`);if("function"!=typeof e.init)throw new Error(`Plugin "${e.id}" must have an init() method`);if("function"!=typeof e.destroy)throw new Error(`Plugin "${e.id}" must have a destroy() method`)}}class m{constructor(e){if(this._currentProvider=null,this.destroyed=!1,this.seekingWhilePlaying=!1,this.seekResumeTimeout=null,"string"==typeof e.container){const t=document.querySelector(e.container);if(!(t&&t instanceof HTMLElement))throw new Error(`ScarlettPlayer: container not found: ${e.container}`);this.container=t}else{if(!(e.container instanceof HTMLElement))throw new Error("ScarlettPlayer requires a valid HTMLElement container or CSS selector");this.container=e.container}if(this.initialSrc=e.src,this.eventBus=new u,this.stateManager=new o({autoplay:e.autoplay??!1,loop:e.loop??!1,volume:e.volume??1,muted:e.muted??!1}),this.logger=new g({level:e.logLevel??"warn",scope:"ScarlettPlayer"}),this.errorHandler=new p(this.eventBus,this.logger),this.pluginManager=new f(this.eventBus,this.stateManager,this.logger,{container:this.container}),e.plugins)for(const t of e.plugins)this.pluginManager.register(t);this.logger.info("ScarlettPlayer initialized",{autoplay:e.autoplay,plugins:e.plugins?.length??0}),this.eventBus.emit("player:ready",void 0)}async init(){this.checkDestroyed();for(const[e,t]of this.pluginManager.plugins)"provider"!==t.plugin.type&&"registered"===t.state&&await this.pluginManager.initPlugin(e);return this.initialSrc&&await this.load(this.initialSrc),Promise.resolve()}async load(e){this.checkDestroyed();try{if(this.logger.info("Loading source",{source:e}),this.stateManager.update({playing:!1,paused:!0,ended:!1,buffering:!0,currentTime:0,duration:0,bufferedAmount:0,playbackState:"loading"}),this._currentProvider){const e=this._currentProvider.id;this.logger.info("Destroying previous provider",{provider:e}),await this.pluginManager.destroyPlugin(e),this._currentProvider=null}const t=this.pluginManager.selectProvider(e);if(!t)return void this.errorHandler.throw(d.PROVIDER_NOT_FOUND,`No provider found for source: ${e}`,{fatal:!0,context:{source:e}});this._currentProvider=t,this.logger.info("Provider selected",{provider:t.id}),await this.pluginManager.initPlugin(t.id),this.stateManager.set("source",{src:e,type:this.detectMimeType(e)}),"function"==typeof t.loadSource&&await t.loadSource(e),this.stateManager.getValue("autoplay")&&await this.play()}catch(t){this.errorHandler.handle(t,{operation:"load",source:e})}}async play(){this.checkDestroyed();try{this.logger.debug("Play requested"),this.stateManager.update({playing:!0,paused:!1,playbackState:"playing"}),this.eventBus.emit("playback:play",void 0)}catch(e){this.errorHandler.handle(e,{operation:"play"})}}pause(){this.checkDestroyed();try{this.logger.debug("Pause requested"),this.seekingWhilePlaying=!1,null!==this.seekResumeTimeout&&(clearTimeout(this.seekResumeTimeout),this.seekResumeTimeout=null),this.stateManager.update({playing:!1,paused:!0,playbackState:"paused"}),this.eventBus.emit("playback:pause",void 0)}catch(e){this.errorHandler.handle(e,{operation:"pause"})}}seek(e){this.checkDestroyed();try{this.logger.debug("Seek requested",{time:e});this.stateManager.getValue("playing")&&(this.seekingWhilePlaying=!0),null!==this.seekResumeTimeout&&(clearTimeout(this.seekResumeTimeout),this.seekResumeTimeout=null),this.eventBus.emit("playback:seeking",{time:e}),this.stateManager.set("currentTime",e),this.seekingWhilePlaying&&(this.seekResumeTimeout=setTimeout(()=>{this.seekingWhilePlaying&&this.stateManager.getValue("playing")&&(this.logger.debug("Resuming playback after seek"),this.seekingWhilePlaying=!1,this.eventBus.emit("playback:play",void 0)),this.seekResumeTimeout=null},300))}catch(t){this.errorHandler.handle(t,{operation:"seek",time:e})}}setVolume(e){this.checkDestroyed();const t=Math.max(0,Math.min(1,e));this.stateManager.set("volume",t),this.eventBus.emit("volume:change",{volume:t,muted:this.stateManager.getValue("muted")})}setMuted(e){this.checkDestroyed(),this.stateManager.set("muted",e),this.eventBus.emit("volume:mute",{muted:e})}setPlaybackRate(e){this.checkDestroyed(),this.stateManager.set("playbackRate",e),this.eventBus.emit("playback:ratechange",{rate:e})}setAutoplay(e){this.checkDestroyed(),this.stateManager.set("autoplay",e),this.logger.debug("Autoplay set",{autoplay:e})}on(e,t){return this.checkDestroyed(),this.eventBus.on(e,t)}once(e,t){return this.checkDestroyed(),this.eventBus.once(e,t)}getPlugin(e){return this.checkDestroyed(),this.pluginManager.getPlugin(e)}registerPlugin(e){this.checkDestroyed(),this.pluginManager.register(e)}getState(){return this.checkDestroyed(),this.stateManager.snapshot()}getQualities(){if(this.checkDestroyed(),!this._currentProvider)return[];const e=this._currentProvider;return"function"==typeof e.getLevels?e.getLevels():[]}setQuality(e){if(this.checkDestroyed(),!this._currentProvider)return void this.logger.warn("No provider available for quality change");const t=this._currentProvider;"function"==typeof t.setLevel&&(t.setLevel(e),this.eventBus.emit("quality:change",{quality:-1===e?"auto":`level-${e}`,auto:-1===e}))}getCurrentQuality(){if(this.checkDestroyed(),!this._currentProvider)return-1;const e=this._currentProvider;return"function"==typeof e.getCurrentLevel?e.getCurrentLevel():-1}async requestFullscreen(){this.checkDestroyed();try{this.container.requestFullscreen?await this.container.requestFullscreen():this.container.webkitRequestFullscreen&&await this.container.webkitRequestFullscreen(),this.stateManager.set("fullscreen",!0),this.eventBus.emit("fullscreen:change",{fullscreen:!0})}catch(e){this.logger.error("Fullscreen request failed",{error:e})}}async exitFullscreen(){this.checkDestroyed();try{document.exitFullscreen?await document.exitFullscreen():document.webkitExitFullscreen&&await document.webkitExitFullscreen(),this.stateManager.set("fullscreen",!1),this.eventBus.emit("fullscreen:change",{fullscreen:!1})}catch(e){this.logger.error("Exit fullscreen failed",{error:e})}}async toggleFullscreen(){this.fullscreen?await this.exitFullscreen():await this.requestFullscreen()}requestAirPlay(){this.checkDestroyed();const e=this.pluginManager.getPlugin("airplay");e&&"function"==typeof e.showPicker?e.showPicker():this.logger.warn("AirPlay plugin not available")}async requestChromecast(){this.checkDestroyed();const e=this.pluginManager.getPlugin("chromecast");e&&"function"==typeof e.requestSession?await e.requestSession():this.logger.warn("Chromecast plugin not available")}stopCasting(){this.checkDestroyed();const e=this.pluginManager.getPlugin("airplay");e&&"function"==typeof e.stop&&e.stop();const t=this.pluginManager.getPlugin("chromecast");t&&"function"==typeof t.stopSession&&t.stopSession()}seekToLive(){this.checkDestroyed();if(!this.stateManager.getValue("live"))return void this.logger.warn("Not a live stream");if(this._currentProvider){const e=this._currentProvider;if("function"==typeof e.getLiveInfo){const t=e.getLiveInfo();if(void 0!==t?.liveSyncPosition)return void this.seek(t.liveSyncPosition)}}const e=this.stateManager.getValue("duration");e>0&&this.seek(e)}destroy(){this.destroyed||(this.logger.info("Destroying player"),null!==this.seekResumeTimeout&&(clearTimeout(this.seekResumeTimeout),this.seekResumeTimeout=null),this.eventBus.emit("player:destroy",void 0),this.pluginManager.destroyAll(),this.eventBus.destroy(),this.stateManager.destroy(),this.destroyed=!0,this.logger.info("Player destroyed"))}get playing(){return this.stateManager.getValue("playing")}get paused(){return this.stateManager.getValue("paused")}get currentTime(){return this.stateManager.getValue("currentTime")}get duration(){return this.stateManager.getValue("duration")}get volume(){return this.stateManager.getValue("volume")}get muted(){return this.stateManager.getValue("muted")}get playbackRate(){return this.stateManager.getValue("playbackRate")}get bufferedAmount(){return this.stateManager.getValue("bufferedAmount")}get currentProvider(){return this._currentProvider}get fullscreen(){return this.stateManager.getValue("fullscreen")}get live(){return this.stateManager.getValue("live")}get autoplay(){return this.stateManager.getValue("autoplay")}checkDestroyed(){if(this.destroyed)throw new Error("Cannot call methods on destroyed player")}detectMimeType(e){const t=e.split(".").pop()?.toLowerCase();switch(t){case"m3u8":return"application/x-mpegURL";case"mpd":return"application/dash+xml";case"mp4":default:return"video/mp4";case"webm":return"video/webm";case"ogg":return"video/ogg"}}}exports.Computed=n,exports.ErrorCode=d,exports.ErrorHandler=p,exports.EventBus=u,exports.Logger=g,exports.PluginAPI=y,exports.PluginManager=f,exports.ScarlettPlayer=m,exports.Signal=s,exports.StateManager=o,exports.computed=function(e){return new n(e)},exports.createLogger=function(e){return new g({scope:e})},exports.createPlayer=async function(e){const t=new m(e);return await t.init(),t},exports.effect=function(e){const r=()=>{t(r);try{e()}catch(s){throw console.error("[Scarlett Player] Error in effect:",s),s}finally{t(null)}};return r(),()=>{}},exports.getCurrentEffect=r,exports.setCurrentEffect=t,exports.signal=i;
2
+ //# sourceMappingURL=index.cjs.map