@scarlett-player/core 0.1.1 → 0.2.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Hackney Enterprises Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,229 @@
1
+ /**
2
+ * ErrorHandler - Centralized error handling for Scarlett Player.
3
+ *
4
+ * Provides error classification, logging, history tracking, and event emission
5
+ * for all player errors.
6
+ *
7
+ * Target size: ~0.4-0.6KB
8
+ */
9
+ import type { EventBus } from './events/event-bus';
10
+ import type { Logger } from './logger';
11
+ /**
12
+ * Player error codes.
13
+ */
14
+ export declare enum ErrorCode {
15
+ SOURCE_NOT_SUPPORTED = "SOURCE_NOT_SUPPORTED",
16
+ SOURCE_LOAD_FAILED = "SOURCE_LOAD_FAILED",
17
+ PROVIDER_NOT_FOUND = "PROVIDER_NOT_FOUND",
18
+ PROVIDER_SETUP_FAILED = "PROVIDER_SETUP_FAILED",
19
+ PLUGIN_SETUP_FAILED = "PLUGIN_SETUP_FAILED",
20
+ PLUGIN_NOT_FOUND = "PLUGIN_NOT_FOUND",
21
+ PLAYBACK_FAILED = "PLAYBACK_FAILED",
22
+ MEDIA_DECODE_ERROR = "MEDIA_DECODE_ERROR",
23
+ MEDIA_NETWORK_ERROR = "MEDIA_NETWORK_ERROR",
24
+ UNKNOWN_ERROR = "UNKNOWN_ERROR"
25
+ }
26
+ /**
27
+ * Structured player error.
28
+ */
29
+ export interface PlayerError {
30
+ /** Error code */
31
+ code: ErrorCode;
32
+ /** Error message */
33
+ message: string;
34
+ /** Whether error is fatal (unrecoverable) */
35
+ fatal: boolean;
36
+ /** Timestamp (milliseconds since epoch) */
37
+ timestamp: number;
38
+ /** Optional context (what was happening) */
39
+ context?: Record<string, any>;
40
+ /** Original error if wrapped */
41
+ originalError?: Error;
42
+ }
43
+ /**
44
+ * ErrorHandler options.
45
+ */
46
+ export interface ErrorHandlerOptions {
47
+ /** Maximum error history to keep (default: 10) */
48
+ maxHistory?: number;
49
+ }
50
+ /**
51
+ * ErrorHandler manages all player errors with classification and history.
52
+ *
53
+ * Features:
54
+ * - Error classification (fatal vs recoverable)
55
+ * - Error code mapping
56
+ * - Error event emission
57
+ * - Error history tracking
58
+ * - Context capture
59
+ * - Logger integration
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * const errorHandler = new ErrorHandler(eventBus, logger);
64
+ *
65
+ * // Handle native error
66
+ * try {
67
+ * // Some operation
68
+ * } catch (error) {
69
+ * errorHandler.handle(error as Error, {
70
+ * operation: 'loadSource',
71
+ * src: 'video.mp4'
72
+ * });
73
+ * }
74
+ *
75
+ * // Throw specific error
76
+ * errorHandler.throw(
77
+ * ErrorCode.SOURCE_NOT_SUPPORTED,
78
+ * 'MP4 files are not supported',
79
+ * { fatal: true, context: { src: 'video.mp4' } }
80
+ * );
81
+ *
82
+ * // Check error history
83
+ * const lastError = errorHandler.getLastError();
84
+ * const hasFatal = errorHandler.hasFatalError();
85
+ * ```
86
+ */
87
+ export declare class ErrorHandler {
88
+ /** Event bus for error emission */
89
+ private eventBus;
90
+ /** Logger for error logging */
91
+ private logger;
92
+ /** Error history */
93
+ private errors;
94
+ /** Maximum history size */
95
+ private maxHistory;
96
+ /**
97
+ * Create a new ErrorHandler.
98
+ *
99
+ * @param eventBus - Event bus for error emission
100
+ * @param logger - Logger for error logging
101
+ * @param options - Optional configuration
102
+ */
103
+ constructor(eventBus: EventBus, logger: Logger, options?: ErrorHandlerOptions);
104
+ /**
105
+ * Handle an error.
106
+ *
107
+ * Normalizes, logs, emits, and tracks the error.
108
+ *
109
+ * @param error - Error to handle (native or PlayerError)
110
+ * @param context - Optional context (what was happening)
111
+ * @returns Normalized PlayerError
112
+ *
113
+ * @example
114
+ * ```ts
115
+ * try {
116
+ * loadVideo();
117
+ * } catch (error) {
118
+ * errorHandler.handle(error as Error, { src: 'video.mp4' });
119
+ * }
120
+ * ```
121
+ */
122
+ handle(error: Error | PlayerError, context?: Record<string, any>): PlayerError;
123
+ /**
124
+ * Create and handle an error from code.
125
+ *
126
+ * @param code - Error code
127
+ * @param message - Error message
128
+ * @param options - Optional error options
129
+ * @returns Created PlayerError
130
+ *
131
+ * @example
132
+ * ```ts
133
+ * errorHandler.throw(
134
+ * ErrorCode.SOURCE_NOT_SUPPORTED,
135
+ * 'MP4 not supported',
136
+ * { fatal: true, context: { type: 'video/mp4' } }
137
+ * );
138
+ * ```
139
+ */
140
+ throw(code: ErrorCode, message: string, options?: {
141
+ fatal?: boolean;
142
+ context?: Record<string, any>;
143
+ originalError?: Error;
144
+ }): PlayerError;
145
+ /**
146
+ * Get error history.
147
+ *
148
+ * @returns Readonly copy of error history
149
+ *
150
+ * @example
151
+ * ```ts
152
+ * const history = errorHandler.getHistory();
153
+ * console.log(`${history.length} errors occurred`);
154
+ * ```
155
+ */
156
+ getHistory(): readonly PlayerError[];
157
+ /**
158
+ * Get last error that occurred.
159
+ *
160
+ * @returns Last error or null if none
161
+ *
162
+ * @example
163
+ * ```ts
164
+ * const lastError = errorHandler.getLastError();
165
+ * if (lastError?.fatal) {
166
+ * showErrorMessage(lastError.message);
167
+ * }
168
+ * ```
169
+ */
170
+ getLastError(): PlayerError | null;
171
+ /**
172
+ * Clear error history.
173
+ *
174
+ * @example
175
+ * ```ts
176
+ * errorHandler.clearHistory();
177
+ * ```
178
+ */
179
+ clearHistory(): void;
180
+ /**
181
+ * Check if any fatal errors occurred.
182
+ *
183
+ * @returns True if any fatal error in history
184
+ *
185
+ * @example
186
+ * ```ts
187
+ * if (errorHandler.hasFatalError()) {
188
+ * player.reset();
189
+ * }
190
+ * ```
191
+ */
192
+ hasFatalError(): boolean;
193
+ /**
194
+ * Normalize error to PlayerError.
195
+ * @private
196
+ */
197
+ private normalizeError;
198
+ /**
199
+ * Determine error code from native Error.
200
+ * @private
201
+ */
202
+ private getErrorCode;
203
+ /**
204
+ * Determine if error is fatal.
205
+ * @private
206
+ */
207
+ private isFatal;
208
+ /**
209
+ * Determine if error code is fatal.
210
+ * @private
211
+ */
212
+ private isFatalCode;
213
+ /**
214
+ * Type guard for PlayerError.
215
+ * @private
216
+ */
217
+ private isPlayerError;
218
+ /**
219
+ * Add error to history.
220
+ * @private
221
+ */
222
+ private addToHistory;
223
+ /**
224
+ * Log error with appropriate level.
225
+ * @private
226
+ */
227
+ private logError;
228
+ }
229
+ //# sourceMappingURL=error-handler.d.ts.map
@@ -0,0 +1,212 @@
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
+ import type { EventName, EventPayload, EventHandler, EventInterceptor, EventEmitterOptions } from '../types/events';
10
+ /**
11
+ * EventBus provides type-safe event emission and subscription.
12
+ *
13
+ * Features:
14
+ * - Type-safe events based on PlayerEventMap
15
+ * - Event interceptors for modification/cancellation
16
+ * - One-time subscriptions with once()
17
+ * - Async event emission
18
+ * - Error handling for handlers
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * const events = new EventBus();
23
+ *
24
+ * // Subscribe to event
25
+ * events.on('playback:play', () => {
26
+ * console.log('Playing!');
27
+ * });
28
+ *
29
+ * // Emit event
30
+ * events.emit('playback:play', undefined);
31
+ *
32
+ * // Intercept events
33
+ * events.intercept('playback:timeupdate', (payload) => {
34
+ * // Modify payload or return null to cancel
35
+ * return { currentTime: Math.floor(payload.currentTime) };
36
+ * });
37
+ * ```
38
+ */
39
+ export declare class EventBus {
40
+ /** Event listeners map */
41
+ private listeners;
42
+ /** One-time listeners (removed after first call) */
43
+ private onceListeners;
44
+ /** Event interceptors map */
45
+ private interceptors;
46
+ /** Configuration options */
47
+ private options;
48
+ /**
49
+ * Create a new EventBus.
50
+ *
51
+ * @param options - Optional configuration
52
+ */
53
+ constructor(options?: EventEmitterOptions);
54
+ /**
55
+ * Subscribe to an event.
56
+ *
57
+ * @param event - Event name
58
+ * @param handler - Event handler function
59
+ * @returns Unsubscribe function
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * const unsub = events.on('playback:play', () => {
64
+ * console.log('Playing!');
65
+ * });
66
+ *
67
+ * // Later: unsubscribe
68
+ * unsub();
69
+ * ```
70
+ */
71
+ on<T extends EventName>(event: T, handler: EventHandler<T>): () => void;
72
+ /**
73
+ * Subscribe to an event once (auto-unsubscribe after first call).
74
+ *
75
+ * @param event - Event name
76
+ * @param handler - Event handler function
77
+ * @returns Unsubscribe function
78
+ *
79
+ * @example
80
+ * ```ts
81
+ * events.once('player:ready', () => {
82
+ * console.log('Player ready!');
83
+ * });
84
+ * ```
85
+ */
86
+ once<T extends EventName>(event: T, handler: EventHandler<T>): () => void;
87
+ /**
88
+ * Unsubscribe from an event.
89
+ *
90
+ * @param event - Event name
91
+ * @param handler - Event handler function to remove
92
+ *
93
+ * @example
94
+ * ```ts
95
+ * const handler = () => console.log('Playing!');
96
+ * events.on('playback:play', handler);
97
+ * events.off('playback:play', handler);
98
+ * ```
99
+ */
100
+ off<T extends EventName>(event: T, handler: EventHandler<T>): void;
101
+ /**
102
+ * Emit an event synchronously.
103
+ *
104
+ * Runs interceptors first, then calls all handlers.
105
+ *
106
+ * @param event - Event name
107
+ * @param payload - Event payload
108
+ *
109
+ * @example
110
+ * ```ts
111
+ * events.emit('playback:play', undefined);
112
+ * events.emit('playback:timeupdate', { currentTime: 10.5 });
113
+ * ```
114
+ */
115
+ emit<T extends EventName>(event: T, payload: EventPayload<T>): void;
116
+ /**
117
+ * Emit an event asynchronously (next tick).
118
+ *
119
+ * @param event - Event name
120
+ * @param payload - Event payload
121
+ * @returns Promise that resolves when all handlers complete
122
+ *
123
+ * @example
124
+ * ```ts
125
+ * await events.emitAsync('media:loaded', { src: 'video.mp4', type: 'video/mp4' });
126
+ * ```
127
+ */
128
+ emitAsync<T extends EventName>(event: T, payload: EventPayload<T>): Promise<void>;
129
+ /**
130
+ * Add an event interceptor.
131
+ *
132
+ * Interceptors run before handlers and can modify or cancel events.
133
+ *
134
+ * @param event - Event name
135
+ * @param interceptor - Interceptor function
136
+ * @returns Remove interceptor function
137
+ *
138
+ * @example
139
+ * ```ts
140
+ * events.intercept('playback:timeupdate', (payload) => {
141
+ * // Round time to 2 decimals
142
+ * return { currentTime: Math.round(payload.currentTime * 100) / 100 };
143
+ * });
144
+ *
145
+ * // Cancel events
146
+ * events.intercept('playback:play', (payload) => {
147
+ * if (notReady) return null; // Cancel event
148
+ * return payload;
149
+ * });
150
+ * ```
151
+ */
152
+ intercept<T extends EventName>(event: T, interceptor: EventInterceptor<T>): () => void;
153
+ /**
154
+ * Remove all listeners for an event (or all events if no event specified).
155
+ *
156
+ * @param event - Optional event name
157
+ *
158
+ * @example
159
+ * ```ts
160
+ * events.removeAllListeners('playback:play'); // Remove all playback:play listeners
161
+ * events.removeAllListeners(); // Remove ALL listeners
162
+ * ```
163
+ */
164
+ removeAllListeners(event?: EventName): void;
165
+ /**
166
+ * Get the number of listeners for an event.
167
+ *
168
+ * @param event - Event name
169
+ * @returns Number of listeners
170
+ *
171
+ * @example
172
+ * ```ts
173
+ * events.listenerCount('playback:play'); // 3
174
+ * ```
175
+ */
176
+ listenerCount(event: EventName): number;
177
+ /**
178
+ * Destroy event bus and cleanup all listeners/interceptors.
179
+ *
180
+ * @example
181
+ * ```ts
182
+ * events.destroy();
183
+ * ```
184
+ */
185
+ destroy(): void;
186
+ /**
187
+ * Run interceptors synchronously.
188
+ * @private
189
+ */
190
+ private runInterceptors;
191
+ /**
192
+ * Run interceptors asynchronously.
193
+ * @private
194
+ */
195
+ private runInterceptorsAsync;
196
+ /**
197
+ * Safely call a handler with error handling.
198
+ * @private
199
+ */
200
+ private safeCallHandler;
201
+ /**
202
+ * Safely call a handler asynchronously with error handling.
203
+ * @private
204
+ */
205
+ private safeCallHandlerAsync;
206
+ /**
207
+ * Check if max listeners exceeded and warn.
208
+ * @private
209
+ */
210
+ private checkMaxListeners;
211
+ }
212
+ //# sourceMappingURL=event-bus.d.ts.map
package/dist/index.cjs CHANGED
@@ -1,2 +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;
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,poster:e.poster??""}),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.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.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
2
  //# sourceMappingURL=index.cjs.map