@mediafox/core 1.2.8 → 1.2.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/compositor/compositor.d.ts.map +1 -1
  2. package/dist/compositor-worker.js +1 -1
  3. package/dist/index.js +1 -1
  4. package/package.json +4 -3
  5. package/src/compositor/audio-manager.ts +411 -0
  6. package/src/compositor/compositor-worker.ts +158 -0
  7. package/src/compositor/compositor.ts +931 -0
  8. package/src/compositor/index.ts +19 -0
  9. package/src/compositor/source-pool.ts +450 -0
  10. package/src/compositor/types.ts +103 -0
  11. package/src/compositor/worker-client.ts +139 -0
  12. package/src/compositor/worker-types.ts +67 -0
  13. package/src/core/player-core.ts +273 -0
  14. package/src/core/state-facade.ts +98 -0
  15. package/src/core/track-switcher.ts +127 -0
  16. package/src/events/emitter.ts +137 -0
  17. package/src/events/types.ts +24 -0
  18. package/src/index.ts +124 -0
  19. package/src/mediafox.ts +642 -0
  20. package/src/playback/audio.ts +361 -0
  21. package/src/playback/controller.ts +446 -0
  22. package/src/playback/renderer.ts +1176 -0
  23. package/src/playback/renderers/canvas2d.ts +128 -0
  24. package/src/playback/renderers/factory.ts +172 -0
  25. package/src/playback/renderers/index.ts +5 -0
  26. package/src/playback/renderers/types.ts +57 -0
  27. package/src/playback/renderers/webgl.ts +373 -0
  28. package/src/playback/renderers/webgpu.ts +395 -0
  29. package/src/playlist/manager.ts +268 -0
  30. package/src/plugins/context.ts +93 -0
  31. package/src/plugins/index.ts +15 -0
  32. package/src/plugins/manager.ts +482 -0
  33. package/src/plugins/types.ts +243 -0
  34. package/src/sources/manager.ts +285 -0
  35. package/src/sources/source.ts +84 -0
  36. package/src/sources/types.ts +17 -0
  37. package/src/state/store.ts +389 -0
  38. package/src/state/types.ts +18 -0
  39. package/src/tracks/manager.ts +421 -0
  40. package/src/tracks/types.ts +30 -0
  41. package/src/types/jassub.d.ts +1 -0
  42. package/src/types.ts +235 -0
  43. package/src/utils/async-lock.ts +26 -0
  44. package/src/utils/dispose.ts +28 -0
  45. package/src/utils/equal.ts +33 -0
  46. package/src/utils/errors.ts +74 -0
  47. package/src/utils/logger.ts +50 -0
  48. package/src/utils/time.ts +157 -0
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Safely calls return() on an AsyncGenerator if present, swallowing errors.
3
+ * Used to ensure iterators are properly cleaned up without leaking promises.
4
+ */
5
+ export function safeAsyncReturn<T, TReturn = unknown, TNext = unknown>(
6
+ iterator: AsyncGenerator<T, TReturn, TNext> | null | undefined
7
+ ): void {
8
+ try {
9
+ // Intentionally do not await inside non-async dispose paths
10
+ // Some TS lib dom typings require a value argument
11
+ void iterator?.return?.(undefined as unknown as TReturn);
12
+ } catch {
13
+ // Ignore iterator return errors during cleanup
14
+ }
15
+ }
16
+
17
+ /**
18
+ * Clear interval/RAF ids safely.
19
+ */
20
+ export function safeClearInterval(id: number | null): null {
21
+ if (id !== null) clearInterval(id);
22
+ return null;
23
+ }
24
+
25
+ export function safeCancelAnimationFrame(id: number | null): null {
26
+ if (id !== null) cancelAnimationFrame(id);
27
+ return null;
28
+ }
@@ -0,0 +1,33 @@
1
+ // Lightweight deep equality for simple JSON-like structures used in state
2
+ export function isDeepEqual(a: unknown, b: unknown): boolean {
3
+ if (Object.is(a, b)) return true;
4
+ if (typeof a !== typeof b) return false;
5
+ if (a === null || b === null) return a === b;
6
+
7
+ // Arrays
8
+ if (Array.isArray(a) && Array.isArray(b)) {
9
+ if (a.length !== b.length) return false;
10
+ for (let i = 0; i < a.length; i++) {
11
+ if (!isDeepEqual(a[i], b[i])) return false;
12
+ }
13
+ return true;
14
+ }
15
+
16
+ // Objects
17
+ if (isPlainObject(a) && isPlainObject(b)) {
18
+ const ak = Object.keys(a as Record<string, unknown>);
19
+ const bk = Object.keys(b as Record<string, unknown>);
20
+ if (ak.length !== bk.length) return false;
21
+ for (const k of ak) {
22
+ if (!Object.hasOwn(b, k)) return false;
23
+ if (!isDeepEqual((a as Record<string, unknown>)[k], (b as Record<string, unknown>)[k])) return false;
24
+ }
25
+ return true;
26
+ }
27
+
28
+ return false;
29
+ }
30
+
31
+ function isPlainObject(v: unknown): v is Record<string, unknown> {
32
+ return typeof v === 'object' && v !== null && (v as object).constructor === Object;
33
+ }
@@ -0,0 +1,74 @@
1
+ export enum ErrorCode {
2
+ MEDIA_NOT_SUPPORTED = 'MEDIA_NOT_SUPPORTED',
3
+ MEDIA_LOAD_FAILED = 'MEDIA_LOAD_FAILED',
4
+ DECODE_ERROR = 'DECODE_ERROR',
5
+ NETWORK_ERROR = 'NETWORK_ERROR',
6
+ PERMISSION_DENIED = 'PERMISSION_DENIED',
7
+ PLAYBACK_ERROR = 'PLAYBACK_ERROR',
8
+ TRACK_NOT_FOUND = 'TRACK_NOT_FOUND',
9
+ INVALID_STATE = 'INVALID_STATE',
10
+ UNKNOWN_ERROR = 'UNKNOWN_ERROR',
11
+ }
12
+
13
+ export class MediaFoxError extends Error {
14
+ code: ErrorCode;
15
+ details?: unknown;
16
+
17
+ constructor(code: ErrorCode, message: string, details?: unknown) {
18
+ super(message);
19
+ this.name = 'MediaFoxError';
20
+ this.code = code;
21
+ this.details = details;
22
+ }
23
+
24
+ static mediaNotSupported(message = 'Media format not supported', details?: unknown): MediaFoxError {
25
+ return new MediaFoxError(ErrorCode.MEDIA_NOT_SUPPORTED, message, details);
26
+ }
27
+
28
+ static mediaLoadFailed(message = 'Failed to load media', details?: unknown): MediaFoxError {
29
+ return new MediaFoxError(ErrorCode.MEDIA_LOAD_FAILED, message, details);
30
+ }
31
+
32
+ static decodeError(message = 'Failed to decode media', details?: unknown): MediaFoxError {
33
+ return new MediaFoxError(ErrorCode.DECODE_ERROR, message, details);
34
+ }
35
+
36
+ static networkError(message = 'Network error occurred', details?: unknown): MediaFoxError {
37
+ return new MediaFoxError(ErrorCode.NETWORK_ERROR, message, details);
38
+ }
39
+
40
+ static permissionDenied(message = 'Permission denied', details?: unknown): MediaFoxError {
41
+ return new MediaFoxError(ErrorCode.PERMISSION_DENIED, message, details);
42
+ }
43
+
44
+ static playbackError(message = 'Playback error occurred', details?: unknown): MediaFoxError {
45
+ return new MediaFoxError(ErrorCode.PLAYBACK_ERROR, message, details);
46
+ }
47
+
48
+ static trackNotFound(message = 'Track not found', details?: unknown): MediaFoxError {
49
+ return new MediaFoxError(ErrorCode.TRACK_NOT_FOUND, message, details);
50
+ }
51
+
52
+ static invalidState(message = 'Invalid player state', details?: unknown): MediaFoxError {
53
+ return new MediaFoxError(ErrorCode.INVALID_STATE, message, details);
54
+ }
55
+
56
+ static unknownError(message = 'Unknown error occurred', details?: unknown): MediaFoxError {
57
+ return new MediaFoxError(ErrorCode.UNKNOWN_ERROR, message, details);
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Wrap an error with additional context
63
+ */
64
+ export function wrapError(error: unknown, context: string): MediaFoxError {
65
+ if (error instanceof MediaFoxError) {
66
+ return error;
67
+ }
68
+
69
+ if (error instanceof Error) {
70
+ return new MediaFoxError(ErrorCode.UNKNOWN_ERROR, `${context}: ${error.message}`, { originalError: error });
71
+ }
72
+
73
+ return new MediaFoxError(ErrorCode.UNKNOWN_ERROR, `${context}: ${String(error)}`, { originalError: error });
74
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Simple logger utility for MediaFox
3
+ */
4
+
5
+ export enum LogLevel {
6
+ DEBUG = 0,
7
+ INFO = 1,
8
+ WARN = 2,
9
+ ERROR = 3,
10
+ NONE = 4,
11
+ }
12
+
13
+ let logLevel: LogLevel = LogLevel.WARN;
14
+ const logPrefix = '[MediaFox]';
15
+
16
+ export function setLogLevel(level: LogLevel): void {
17
+ logLevel = level;
18
+ }
19
+
20
+ export function debug(message: string, ...args: unknown[]): void {
21
+ if (logLevel <= LogLevel.DEBUG) {
22
+ console.debug(`${logPrefix} ${message}`, ...args);
23
+ }
24
+ }
25
+
26
+ export function info(message: string, ...args: unknown[]): void {
27
+ if (logLevel <= LogLevel.INFO) {
28
+ console.info(`${logPrefix} ${message}`, ...args);
29
+ }
30
+ }
31
+
32
+ export function warn(message: string, ...args: unknown[]): void {
33
+ if (logLevel <= LogLevel.WARN) {
34
+ console.warn(`${logPrefix} ${message}`, ...args);
35
+ }
36
+ }
37
+
38
+ export function error(message: string, ...args: unknown[]): void {
39
+ if (logLevel <= LogLevel.ERROR) {
40
+ console.error(`${logPrefix} ${message}`, ...args);
41
+ }
42
+ }
43
+
44
+ export const logger = {
45
+ setLevel: setLogLevel,
46
+ debug,
47
+ info,
48
+ warn,
49
+ error,
50
+ };
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Format seconds into a human-readable time string
3
+ * @param seconds - Time in seconds
4
+ * @param showMilliseconds - Whether to show milliseconds
5
+ * @returns Formatted time string (e.g., "1:23:45" or "23:45.123")
6
+ */
7
+ export function formatTime(seconds: number, showMilliseconds = false): string {
8
+ const absSeconds = Math.abs(seconds);
9
+ const hours = Math.floor(absSeconds / 3600);
10
+ const minutes = Math.floor((absSeconds % 3600) / 60);
11
+ const secs = Math.floor(absSeconds % 60);
12
+ const ms = Math.floor((absSeconds % 1) * 1000);
13
+
14
+ let result = '';
15
+
16
+ if (seconds < 0) {
17
+ result = '-';
18
+ }
19
+
20
+ if (hours > 0) {
21
+ result += `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
22
+ } else {
23
+ result += `${minutes}:${secs.toString().padStart(2, '0')}`;
24
+ }
25
+
26
+ if (showMilliseconds) {
27
+ result += `.${ms.toString().padStart(3, '0')}`;
28
+ }
29
+
30
+ return result;
31
+ }
32
+
33
+ /**
34
+ * Parse a time string into seconds
35
+ * @param timeString - Time string (e.g., "1:23:45", "23:45", "45")
36
+ * @returns Time in seconds
37
+ */
38
+ export function parseTime(timeString: string): number {
39
+ const parts = timeString.trim().split(':').map(Number);
40
+
41
+ if (parts.some(Number.isNaN)) {
42
+ throw new Error('Invalid time string');
43
+ }
44
+
45
+ let seconds = 0;
46
+
47
+ if (parts.length === 3) {
48
+ // HH:MM:SS
49
+ seconds = parts[0] * 3600 + parts[1] * 60 + parts[2];
50
+ } else if (parts.length === 2) {
51
+ // MM:SS
52
+ seconds = parts[0] * 60 + parts[1];
53
+ } else if (parts.length === 1) {
54
+ // SS
55
+ seconds = parts[0];
56
+ } else {
57
+ throw new Error('Invalid time format');
58
+ }
59
+
60
+ return seconds;
61
+ }
62
+
63
+ /**
64
+ * Get frame number from time
65
+ * @param time - Time in seconds
66
+ * @param frameRate - Frame rate in Hz
67
+ * @returns Frame number
68
+ */
69
+ export function timeToFrame(time: number, frameRate: number): number {
70
+ return Math.floor(time * frameRate);
71
+ }
72
+
73
+ /**
74
+ * Get time from frame number
75
+ * @param frame - Frame number
76
+ * @param frameRate - Frame rate in Hz
77
+ * @returns Time in seconds
78
+ */
79
+ export function frameToTime(frame: number, frameRate: number): number {
80
+ return frame / frameRate;
81
+ }
82
+
83
+ /**
84
+ * Clamp a value between min and max
85
+ * @param value - Value to clamp
86
+ * @param min - Minimum value
87
+ * @param max - Maximum value
88
+ * @returns Clamped value
89
+ */
90
+ export function clamp(value: number, min: number, max: number): number {
91
+ return Math.max(min, Math.min(max, value));
92
+ }
93
+
94
+ /**
95
+ * Check if two time ranges overlap
96
+ * @param range1 - First time range
97
+ * @param range2 - Second time range
98
+ * @returns True if ranges overlap
99
+ */
100
+ export function timeRangesOverlap(
101
+ range1: { start: number; end: number },
102
+ range2: { start: number; end: number }
103
+ ): boolean {
104
+ return range1.start < range2.end && range2.start < range1.end;
105
+ }
106
+
107
+ /**
108
+ * Merge overlapping time ranges
109
+ * @param ranges - Array of time ranges
110
+ * @returns Merged time ranges
111
+ */
112
+ export function mergeTimeRanges(ranges: Array<{ start: number; end: number }>): Array<{ start: number; end: number }> {
113
+ if (ranges.length === 0) return [];
114
+
115
+ const sorted = [...ranges].sort((a, b) => a.start - b.start);
116
+ const merged: Array<{ start: number; end: number }> = [sorted[0]];
117
+
118
+ for (let i = 1; i < sorted.length; i++) {
119
+ const last = merged[merged.length - 1];
120
+ const current = sorted[i];
121
+
122
+ if (current.start <= last.end) {
123
+ last.end = Math.max(last.end, current.end);
124
+ } else {
125
+ merged.push(current);
126
+ }
127
+ }
128
+
129
+ return merged;
130
+ }
131
+
132
+ /**
133
+ * Calculate total buffered duration
134
+ * @param ranges - Array of time ranges
135
+ * @returns Total duration in seconds
136
+ */
137
+ export function totalBufferedDuration(ranges: Array<{ start: number; end: number }>): number {
138
+ return ranges.reduce((total, range) => total + (range.end - range.start), 0);
139
+ }
140
+
141
+ /**
142
+ * Find the buffered range containing a specific time
143
+ * @param ranges - Array of time ranges
144
+ * @param time - Time to search for
145
+ * @returns The containing range or null
146
+ */
147
+ export function findBufferedRange(
148
+ ranges: Array<{ start: number; end: number }>,
149
+ time: number
150
+ ): { start: number; end: number } | null {
151
+ for (const range of ranges) {
152
+ if (time >= range.start && time < range.end) {
153
+ return range;
154
+ }
155
+ }
156
+ return null;
157
+ }