@editframe/elements 0.18.3-beta.0 → 0.18.8-beta.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/dist/elements/EFAudio.d.ts +1 -2
- package/dist/elements/EFAudio.js +6 -9
- package/dist/elements/EFMedia/AssetMediaEngine.browsertest.d.ts +0 -0
- package/dist/elements/EFMedia/AssetMediaEngine.d.ts +2 -4
- package/dist/elements/EFMedia/AssetMediaEngine.js +34 -5
- package/dist/elements/EFMedia/BaseMediaEngine.js +20 -1
- package/dist/elements/EFMedia/BufferedSeekingInput.d.ts +5 -5
- package/dist/elements/EFMedia/BufferedSeekingInput.js +27 -7
- package/dist/elements/EFMedia/JitMediaEngine.d.ts +1 -1
- package/dist/elements/EFMedia/JitMediaEngine.js +22 -3
- package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +4 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js +11 -3
- package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.d.ts +0 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js +17 -4
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js +11 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js +3 -2
- package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +4 -1
- package/dist/elements/EFMedia/shared/PrecisionUtils.d.ts +28 -0
- package/dist/elements/EFMedia/shared/PrecisionUtils.js +29 -0
- package/dist/elements/EFMedia/videoTasks/makeVideoSeekTask.js +11 -2
- package/dist/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.js +11 -1
- package/dist/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.js +3 -2
- package/dist/elements/EFMedia.d.ts +0 -12
- package/dist/elements/EFMedia.js +4 -30
- package/dist/elements/EFTimegroup.js +12 -17
- package/dist/elements/EFVideo.d.ts +0 -9
- package/dist/elements/EFVideo.js +0 -7
- package/dist/elements/SampleBuffer.js +6 -6
- package/dist/getRenderInfo.d.ts +2 -2
- package/dist/gui/ContextMixin.js +71 -17
- package/dist/gui/TWMixin.js +1 -1
- package/dist/style.css +1 -1
- package/dist/transcoding/types/index.d.ts +9 -9
- package/package.json +2 -3
- package/src/elements/EFAudio.browsertest.ts +7 -7
- package/src/elements/EFAudio.ts +7 -20
- package/src/elements/EFMedia/AssetMediaEngine.browsertest.ts +100 -0
- package/src/elements/EFMedia/AssetMediaEngine.ts +72 -7
- package/src/elements/EFMedia/BaseMediaEngine.ts +50 -1
- package/src/elements/EFMedia/BufferedSeekingInput.browsertest.ts +135 -54
- package/src/elements/EFMedia/BufferedSeekingInput.ts +74 -17
- package/src/elements/EFMedia/JitMediaEngine.ts +58 -2
- package/src/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts +10 -1
- package/src/elements/EFMedia/audioTasks/makeAudioInputTask.ts +16 -8
- package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.ts +199 -0
- package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.ts +35 -4
- package/src/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.ts +12 -1
- package/src/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.ts +3 -2
- package/src/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts +10 -1
- package/src/elements/EFMedia/shared/PrecisionUtils.ts +46 -0
- package/src/elements/EFMedia/videoTasks/makeVideoSeekTask.ts +27 -3
- package/src/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.ts +12 -1
- package/src/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.ts +3 -2
- package/src/elements/EFMedia.browsertest.ts +73 -33
- package/src/elements/EFMedia.ts +11 -54
- package/src/elements/EFTimegroup.ts +21 -26
- package/src/elements/EFVideo.browsertest.ts +895 -162
- package/src/elements/EFVideo.ts +0 -16
- package/src/elements/SampleBuffer.ts +8 -10
- package/src/gui/ContextMixin.ts +104 -26
- package/src/transcoding/types/index.ts +10 -6
- package/test/EFVideo.framegen.browsertest.ts +1 -1
- package/test/__cache__/GET__api_v1_transcode_audio_1_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__32da3954ba60c96ad732020c65a08ebc/metadata.json +3 -3
- package/test/__cache__/GET__api_v1_transcode_audio_1_mp4_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4_bytes_0__9ed2d25c675aa6bb6ff5b3ae23887c71/data.bin +0 -0
- package/test/__cache__/GET__api_v1_transcode_audio_1_mp4_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4_bytes_0__9ed2d25c675aa6bb6ff5b3ae23887c71/metadata.json +22 -0
- package/test/__cache__/GET__api_v1_transcode_audio_2_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__b0b2b07efcf607de8ee0f650328c32f7/metadata.json +3 -3
- package/test/__cache__/GET__api_v1_transcode_audio_2_mp4_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4_bytes_0__d5a3309a2bf756dd6e304807eb402f56/data.bin +0 -0
- package/test/__cache__/GET__api_v1_transcode_audio_2_mp4_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4_bytes_0__d5a3309a2bf756dd6e304807eb402f56/metadata.json +22 -0
- package/test/__cache__/GET__api_v1_transcode_audio_3_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a75c2252b542e0c152c780e9a8d7b154/metadata.json +3 -3
- package/test/__cache__/GET__api_v1_transcode_audio_3_mp4_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4_bytes_0__773254bb671e3466fca8677139fb239e/data.bin +0 -0
- package/test/__cache__/GET__api_v1_transcode_audio_3_mp4_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4_bytes_0__773254bb671e3466fca8677139fb239e/metadata.json +22 -0
- package/test/__cache__/GET__api_v1_transcode_audio_4_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a64ff1cfb1b52cae14df4b5dfa1e222b/metadata.json +3 -3
- package/test/__cache__/GET__api_v1_transcode_audio_init_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__e66d2c831d951e74ad0aeaa6489795d0/metadata.json +3 -3
- package/test/__cache__/GET__api_v1_transcode_high_1_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__26197f6f7c46cacb0a71134131c3f775/metadata.json +3 -3
- package/test/__cache__/GET__api_v1_transcode_high_2_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__4cb6774cd3650ccf59c8f8dc6678c0b9/metadata.json +3 -3
- package/test/__cache__/GET__api_v1_transcode_high_4_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a6fb05a22b18d850f7f2950bbcdbdeed/data.bin +0 -0
- package/test/__cache__/GET__api_v1_transcode_high_4_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a6fb05a22b18d850f7f2950bbcdbdeed/metadata.json +21 -0
- package/test/__cache__/GET__api_v1_transcode_high_5_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a50058c7c3602e90879fe3428ed891f4/data.bin +0 -0
- package/test/__cache__/GET__api_v1_transcode_high_5_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a50058c7c3602e90879fe3428ed891f4/metadata.json +21 -0
- package/test/__cache__/GET__api_v1_transcode_high_init_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__0798c479b44aaeef850609a430f6e613/metadata.json +3 -3
- package/test/__cache__/GET__api_v1_transcode_manifest_json_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__3be92a0437de726b431ed5af2369158a/data.bin +1 -1
- package/test/__cache__/GET__api_v1_transcode_manifest_json_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__3be92a0437de726b431ed5af2369158a/metadata.json +4 -4
- package/test/recordReplayProxyPlugin.js +50 -0
- package/types.json +1 -1
- package/dist/DecoderResetFrequency.test.d.ts +0 -1
- package/dist/DecoderResetRecovery.test.d.ts +0 -1
- package/dist/ScrubTrackManager.d.ts +0 -96
- package/dist/elements/EFMedia/services/AudioElementFactory.browsertest.d.ts +0 -1
- package/dist/elements/EFMedia/services/AudioElementFactory.d.ts +0 -22
- package/dist/elements/EFMedia/services/AudioElementFactory.js +0 -72
- package/dist/elements/EFMedia/services/MediaSourceService.browsertest.d.ts +0 -1
- package/dist/elements/EFMedia/services/MediaSourceService.d.ts +0 -47
- package/dist/elements/EFMedia/services/MediaSourceService.js +0 -73
- package/dist/gui/services/ElementConnectionManager.browsertest.d.ts +0 -1
- package/dist/gui/services/ElementConnectionManager.d.ts +0 -59
- package/dist/gui/services/ElementConnectionManager.js +0 -128
- package/dist/gui/services/PlaybackController.browsertest.d.ts +0 -1
- package/dist/gui/services/PlaybackController.d.ts +0 -103
- package/dist/gui/services/PlaybackController.js +0 -290
- package/dist/services/MediaSourceManager.d.ts +0 -62
- package/dist/services/MediaSourceManager.js +0 -211
- package/src/elements/EFMedia/services/AudioElementFactory.browsertest.ts +0 -325
- package/src/elements/EFMedia/services/AudioElementFactory.ts +0 -119
- package/src/elements/EFMedia/services/MediaSourceService.browsertest.ts +0 -257
- package/src/elements/EFMedia/services/MediaSourceService.ts +0 -102
- package/src/gui/services/ElementConnectionManager.browsertest.ts +0 -263
- package/src/gui/services/ElementConnectionManager.ts +0 -224
- package/src/gui/services/PlaybackController.browsertest.ts +0 -437
- package/src/gui/services/PlaybackController.ts +0 -521
- package/src/services/MediaSourceManager.ts +0 -333
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
export interface ScrubTrackConfig {
|
|
2
|
-
maxScrubCacheSegments?: number;
|
|
3
|
-
prefetchCount?: number;
|
|
4
|
-
fastSeekThresholdMs?: number;
|
|
5
|
-
onLoadingStateChange?: (isLoading: boolean, message?: string) => void;
|
|
6
|
-
}
|
|
7
|
-
export interface CacheStats {
|
|
8
|
-
hits: number;
|
|
9
|
-
misses: number;
|
|
10
|
-
hitRate: number;
|
|
11
|
-
}
|
|
12
|
-
export declare class ScrubTrackManager {
|
|
13
|
-
readonly videoUrl: string;
|
|
14
|
-
private jitClient;
|
|
15
|
-
private config;
|
|
16
|
-
private scrubCache;
|
|
17
|
-
private metadata;
|
|
18
|
-
private cacheHits;
|
|
19
|
-
private cacheMisses;
|
|
20
|
-
private isInitialized;
|
|
21
|
-
private preloadingSegments;
|
|
22
|
-
constructor(videoUrl: string, jitClient: any, // JitTranscodingClient,
|
|
23
|
-
config?: ScrubTrackConfig);
|
|
24
|
-
/**
|
|
25
|
-
* Initialize scrub track manager and start preloading
|
|
26
|
-
*/
|
|
27
|
-
initialize(): Promise<void>;
|
|
28
|
-
/**
|
|
29
|
-
* Determine if scrub track should be used instead of normal video
|
|
30
|
-
*/
|
|
31
|
-
shouldUseScrubTrack(seekTimeMs: number): boolean;
|
|
32
|
-
/**
|
|
33
|
-
* Detect if this is a fast seeking operation
|
|
34
|
-
*/
|
|
35
|
-
isFastSeeking(currentTimeMs: number, seekTimeMs: number): boolean;
|
|
36
|
-
/**
|
|
37
|
-
* Align seek time to 30s scrub segment boundary
|
|
38
|
-
*/
|
|
39
|
-
alignToScrubBoundary(timeMs: number): number;
|
|
40
|
-
/**
|
|
41
|
-
* Get scrub frame for the given seek time
|
|
42
|
-
*/
|
|
43
|
-
getScrubFrame(seekTimeMs: number): Promise<VideoFrame | null>;
|
|
44
|
-
/**
|
|
45
|
-
* Record cache hit for statistics
|
|
46
|
-
*/
|
|
47
|
-
recordCacheHit(): void;
|
|
48
|
-
/**
|
|
49
|
-
* Record cache miss for statistics
|
|
50
|
-
*/
|
|
51
|
-
recordCacheMiss(): void;
|
|
52
|
-
/**
|
|
53
|
-
* Get cache performance statistics
|
|
54
|
-
*/
|
|
55
|
-
getCacheStats(): CacheStats;
|
|
56
|
-
/**
|
|
57
|
-
* Check if scrub segment is cached for the given time
|
|
58
|
-
*/
|
|
59
|
-
isScrubSegmentCached(seekTimeMs: number): boolean;
|
|
60
|
-
/**
|
|
61
|
-
* Get current scrub cache size
|
|
62
|
-
*/
|
|
63
|
-
getScrubCacheSize(): number;
|
|
64
|
-
/**
|
|
65
|
-
* Get total number of scrub segments for video
|
|
66
|
-
*/
|
|
67
|
-
getTotalScrubSegments(): number;
|
|
68
|
-
/**
|
|
69
|
-
* Update metadata (e.g., when video duration changes)
|
|
70
|
-
*/
|
|
71
|
-
updateMetadata(): Promise<void>;
|
|
72
|
-
/**
|
|
73
|
-
* Preload initial scrub segments
|
|
74
|
-
*/
|
|
75
|
-
private preloadInitialSegments;
|
|
76
|
-
/**
|
|
77
|
-
* Preload nearby segments around a given time
|
|
78
|
-
*/
|
|
79
|
-
private preloadNearbySegments;
|
|
80
|
-
/**
|
|
81
|
-
* Preload a single scrub segment (background operation)
|
|
82
|
-
*/
|
|
83
|
-
private preloadSegment;
|
|
84
|
-
/**
|
|
85
|
-
* Cache a scrub segment with LRU eviction
|
|
86
|
-
*/
|
|
87
|
-
private cacheSegment;
|
|
88
|
-
/**
|
|
89
|
-
* Clear cache and reset state
|
|
90
|
-
*/
|
|
91
|
-
reset(): void;
|
|
92
|
-
/**
|
|
93
|
-
* Clean up resources and cancel any pending operations
|
|
94
|
-
*/
|
|
95
|
-
cleanup(): void;
|
|
96
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { MediaSourceService } from './MediaSourceService.js';
|
|
2
|
-
/**
|
|
3
|
-
* Factory for creating and caching MediaElementAudioSourceNode instances
|
|
4
|
-
* Handles the complex lifecycle and caching logic previously embedded in EFMedia
|
|
5
|
-
*/
|
|
6
|
-
export declare class AudioElementFactory {
|
|
7
|
-
private cache;
|
|
8
|
-
private currentSource;
|
|
9
|
-
private currentAudioContext;
|
|
10
|
-
/**
|
|
11
|
-
* Create or retrieve cached MediaElementAudioSourceNode for the given AudioContext
|
|
12
|
-
*/
|
|
13
|
-
createMediaElementSource(audioContext: AudioContext, mediaSourceService: MediaSourceService): Promise<MediaElementAudioSourceNode>;
|
|
14
|
-
/**
|
|
15
|
-
* Clear all cached sources (useful for testing or cleanup)
|
|
16
|
-
*/
|
|
17
|
-
clearCache(): void;
|
|
18
|
-
/**
|
|
19
|
-
* Check if we have a cached source for the given AudioContext
|
|
20
|
-
*/
|
|
21
|
-
hasCachedSource(audioContext: AudioContext): boolean;
|
|
22
|
-
}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Factory for creating and caching MediaElementAudioSourceNode instances
|
|
3
|
-
* Handles the complex lifecycle and caching logic previously embedded in EFMedia
|
|
4
|
-
*/
|
|
5
|
-
var AudioElementFactory = class {
|
|
6
|
-
constructor() {
|
|
7
|
-
this.cache = /* @__PURE__ */ new WeakMap();
|
|
8
|
-
this.currentSource = null;
|
|
9
|
-
this.currentAudioContext = null;
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Create or retrieve cached MediaElementAudioSourceNode for the given AudioContext
|
|
13
|
-
*/
|
|
14
|
-
async createMediaElementSource(audioContext, mediaSourceService) {
|
|
15
|
-
const cached = this.cache.get(audioContext);
|
|
16
|
-
if (cached && audioContext.state !== "closed" && this.currentAudioContext === audioContext) return cached;
|
|
17
|
-
if (this.currentSource && this.currentAudioContext !== audioContext) {
|
|
18
|
-
this.currentSource.disconnect();
|
|
19
|
-
if (this.currentAudioContext) this.cache.delete(this.currentAudioContext);
|
|
20
|
-
this.currentSource = null;
|
|
21
|
-
this.currentAudioContext = null;
|
|
22
|
-
}
|
|
23
|
-
await mediaSourceService.ensureInitialized();
|
|
24
|
-
const audioElement = mediaSourceService.getAudioElement();
|
|
25
|
-
if (!audioElement) throw new Error("Audio element not available from MediaSourceService");
|
|
26
|
-
let mediaElementSource;
|
|
27
|
-
try {
|
|
28
|
-
mediaElementSource = audioContext.createMediaElementSource(audioElement);
|
|
29
|
-
} catch (error) {
|
|
30
|
-
if (error instanceof Error && error.message.includes("already connected")) {
|
|
31
|
-
this.clearCache();
|
|
32
|
-
try {
|
|
33
|
-
mediaElementSource = audioContext.createMediaElementSource(audioElement);
|
|
34
|
-
} catch (retryError) {
|
|
35
|
-
console.warn("AudioElementFactory: Failed to create MediaElementSource even after clearing cache:", retryError);
|
|
36
|
-
throw retryError;
|
|
37
|
-
}
|
|
38
|
-
} else throw error;
|
|
39
|
-
}
|
|
40
|
-
this.currentSource = mediaElementSource;
|
|
41
|
-
this.currentAudioContext = audioContext;
|
|
42
|
-
this.cache.set(audioContext, mediaElementSource);
|
|
43
|
-
const cleanup = () => {
|
|
44
|
-
if (audioContext.state === "closed") {
|
|
45
|
-
this.cache.delete(audioContext);
|
|
46
|
-
if (this.currentAudioContext === audioContext) {
|
|
47
|
-
this.currentSource = null;
|
|
48
|
-
this.currentAudioContext = null;
|
|
49
|
-
}
|
|
50
|
-
audioContext.removeEventListener("statechange", cleanup);
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
audioContext.addEventListener("statechange", cleanup);
|
|
54
|
-
return mediaElementSource;
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Clear all cached sources (useful for testing or cleanup)
|
|
58
|
-
*/
|
|
59
|
-
clearCache() {
|
|
60
|
-
if (this.currentSource) this.currentSource.disconnect();
|
|
61
|
-
this.cache = /* @__PURE__ */ new WeakMap();
|
|
62
|
-
this.currentSource = null;
|
|
63
|
-
this.currentAudioContext = null;
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Check if we have a cached source for the given AudioContext
|
|
67
|
-
*/
|
|
68
|
-
hasCachedSource(audioContext) {
|
|
69
|
-
return this.cache.has(audioContext) && audioContext.state !== "closed";
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
export { AudioElementFactory };
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
export interface MediaSourceServiceOptions {
|
|
2
|
-
onError?: (error: Error) => void;
|
|
3
|
-
onReady?: () => void;
|
|
4
|
-
onUpdateEnd?: () => void;
|
|
5
|
-
timeout?: number;
|
|
6
|
-
}
|
|
7
|
-
/**
|
|
8
|
-
* Service for managing MediaSource lifecycle and audio element creation
|
|
9
|
-
* Extracted from EFMedia to improve separation of concerns and testability
|
|
10
|
-
*/
|
|
11
|
-
export declare class MediaSourceService {
|
|
12
|
-
private mediaSourceManager;
|
|
13
|
-
private options;
|
|
14
|
-
constructor(options?: MediaSourceServiceOptions);
|
|
15
|
-
/**
|
|
16
|
-
* Initialize MediaSource if not already initialized
|
|
17
|
-
*/
|
|
18
|
-
ensureInitialized(): Promise<void>;
|
|
19
|
-
/**
|
|
20
|
-
* Initialize fresh MediaSource
|
|
21
|
-
*/
|
|
22
|
-
initialize(): Promise<void>;
|
|
23
|
-
/**
|
|
24
|
-
* Get audio element for MediaElementSource creation
|
|
25
|
-
*/
|
|
26
|
-
getAudioElement(): HTMLAudioElement | null;
|
|
27
|
-
/**
|
|
28
|
-
* Feed audio segments to MediaSource
|
|
29
|
-
*/
|
|
30
|
-
feedSegment(segmentBuffer: ArrayBuffer): Promise<void>;
|
|
31
|
-
/**
|
|
32
|
-
* Check if MediaSource is ready
|
|
33
|
-
*/
|
|
34
|
-
isReady(): boolean;
|
|
35
|
-
/**
|
|
36
|
-
* Get buffered time ranges
|
|
37
|
-
*/
|
|
38
|
-
getBuffered(): TimeRanges | null;
|
|
39
|
-
/**
|
|
40
|
-
* Set audio element current time
|
|
41
|
-
*/
|
|
42
|
-
setCurrentTime(timeMs: number): void;
|
|
43
|
-
/**
|
|
44
|
-
* Clean up MediaSource resources
|
|
45
|
-
*/
|
|
46
|
-
cleanup(): void;
|
|
47
|
-
}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { MediaSourceManager } from "../../../services/MediaSourceManager.js";
|
|
2
|
-
/**
|
|
3
|
-
* Service for managing MediaSource lifecycle and audio element creation
|
|
4
|
-
* Extracted from EFMedia to improve separation of concerns and testability
|
|
5
|
-
*/
|
|
6
|
-
var MediaSourceService = class {
|
|
7
|
-
constructor(options = {}) {
|
|
8
|
-
this.mediaSourceManager = null;
|
|
9
|
-
this.options = options;
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Initialize MediaSource if not already initialized
|
|
13
|
-
*/
|
|
14
|
-
async ensureInitialized() {
|
|
15
|
-
if (this.mediaSourceManager?.isReady()) return;
|
|
16
|
-
await this.initialize();
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Initialize fresh MediaSource
|
|
20
|
-
*/
|
|
21
|
-
async initialize() {
|
|
22
|
-
this.cleanup();
|
|
23
|
-
const managerOptions = {
|
|
24
|
-
onError: this.options.onError,
|
|
25
|
-
onReady: this.options.onReady,
|
|
26
|
-
onUpdateEnd: this.options.onUpdateEnd,
|
|
27
|
-
timeout: this.options.timeout
|
|
28
|
-
};
|
|
29
|
-
this.mediaSourceManager = new MediaSourceManager(managerOptions);
|
|
30
|
-
await this.mediaSourceManager.initialize();
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Get audio element for MediaElementSource creation
|
|
34
|
-
*/
|
|
35
|
-
getAudioElement() {
|
|
36
|
-
return this.mediaSourceManager?.getAudioElement() || null;
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Feed audio segments to MediaSource
|
|
40
|
-
*/
|
|
41
|
-
async feedSegment(segmentBuffer) {
|
|
42
|
-
await this.ensureInitialized();
|
|
43
|
-
if (this.mediaSourceManager) await this.mediaSourceManager.feedSegment(segmentBuffer);
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Check if MediaSource is ready
|
|
47
|
-
*/
|
|
48
|
-
isReady() {
|
|
49
|
-
return this.mediaSourceManager?.isReady() ?? false;
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Get buffered time ranges
|
|
53
|
-
*/
|
|
54
|
-
getBuffered() {
|
|
55
|
-
return this.mediaSourceManager?.getBuffered() || null;
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Set audio element current time
|
|
59
|
-
*/
|
|
60
|
-
setCurrentTime(timeMs) {
|
|
61
|
-
this.mediaSourceManager?.setCurrentTime(timeMs);
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Clean up MediaSource resources
|
|
65
|
-
*/
|
|
66
|
-
cleanup() {
|
|
67
|
-
if (this.mediaSourceManager) {
|
|
68
|
-
this.mediaSourceManager.cleanup();
|
|
69
|
-
this.mediaSourceManager = null;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
export { MediaSourceService };
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Manages dynamic connection/disconnection of media elements to AudioContext
|
|
3
|
-
* Extracted from ContextMixin to improve separation of concerns and testability
|
|
4
|
-
*/
|
|
5
|
-
export declare class ElementConnectionManager {
|
|
6
|
-
private connectedMediaSources;
|
|
7
|
-
private lookaheadMs;
|
|
8
|
-
constructor(lookaheadMs?: number);
|
|
9
|
-
/**
|
|
10
|
-
* Update connected media elements based on current playhead position
|
|
11
|
-
* Connects upcoming elements and disconnects past elements
|
|
12
|
-
*/
|
|
13
|
-
updateConnectedElements(audioContext: AudioContext, timegroup: any, // EFTimegroup type
|
|
14
|
-
currentMs: number): Promise<void>;
|
|
15
|
-
/**
|
|
16
|
-
* Find elements that should be connected based on timeline position
|
|
17
|
-
*/
|
|
18
|
-
private getElementsToConnect;
|
|
19
|
-
/**
|
|
20
|
-
* Connect new elements that aren't already connected
|
|
21
|
-
*/
|
|
22
|
-
private connectNewElements;
|
|
23
|
-
/**
|
|
24
|
-
* Update connection states for all managed elements
|
|
25
|
-
*/
|
|
26
|
-
private updateElementStates;
|
|
27
|
-
/**
|
|
28
|
-
* Activate an element (connect to destination and start playback)
|
|
29
|
-
*/
|
|
30
|
-
private activateElement;
|
|
31
|
-
/**
|
|
32
|
-
* Deactivate an element (disconnect but keep prepared)
|
|
33
|
-
*/
|
|
34
|
-
private deactivateElement;
|
|
35
|
-
/**
|
|
36
|
-
* Clean up elements that are far in the past
|
|
37
|
-
*/
|
|
38
|
-
private cleanupOldElements;
|
|
39
|
-
/**
|
|
40
|
-
* Clear all connected media sources (for cleanup)
|
|
41
|
-
*/
|
|
42
|
-
clearAll(): void;
|
|
43
|
-
/**
|
|
44
|
-
* Get connection status for testing/debugging
|
|
45
|
-
*/
|
|
46
|
-
getConnectionInfo(): {
|
|
47
|
-
total: number;
|
|
48
|
-
connected: number;
|
|
49
|
-
prepared: number;
|
|
50
|
-
};
|
|
51
|
-
/**
|
|
52
|
-
* Set lookahead time
|
|
53
|
-
*/
|
|
54
|
-
setLookaheadMs(lookaheadMs: number): void;
|
|
55
|
-
/**
|
|
56
|
-
* Get current lookahead time
|
|
57
|
-
*/
|
|
58
|
-
getLookaheadMs(): number;
|
|
59
|
-
}
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Manages dynamic connection/disconnection of media elements to AudioContext
|
|
3
|
-
* Extracted from ContextMixin to improve separation of concerns and testability
|
|
4
|
-
*/
|
|
5
|
-
var ElementConnectionManager = class {
|
|
6
|
-
constructor(lookaheadMs = 3e3) {
|
|
7
|
-
this.connectedMediaSources = /* @__PURE__ */ new Map();
|
|
8
|
-
this.lookaheadMs = lookaheadMs;
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* Update connected media elements based on current playhead position
|
|
12
|
-
* Connects upcoming elements and disconnects past elements
|
|
13
|
-
*/
|
|
14
|
-
async updateConnectedElements(audioContext, timegroup, currentMs) {
|
|
15
|
-
if (!audioContext || audioContext.state === "closed") return;
|
|
16
|
-
const allMediaElements = Array.from(timegroup.querySelectorAll("ef-audio, ef-video"));
|
|
17
|
-
const lookaheadMs = currentMs + this.lookaheadMs;
|
|
18
|
-
const elementsToConnect = this.getElementsToConnect(allMediaElements, currentMs, lookaheadMs);
|
|
19
|
-
await this.connectNewElements(audioContext, elementsToConnect);
|
|
20
|
-
await this.updateElementStates(currentMs);
|
|
21
|
-
this.cleanupOldElements(currentMs);
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Find elements that should be connected based on timeline position
|
|
25
|
-
*/
|
|
26
|
-
getElementsToConnect(allElements, currentMs, lookaheadMs) {
|
|
27
|
-
return allElements.filter((mediaElement) => {
|
|
28
|
-
const startTime = mediaElement.startTimeMs;
|
|
29
|
-
const endTime = mediaElement.endTimeMs;
|
|
30
|
-
const isCurrentlyActive = currentMs >= startTime && currentMs < endTime;
|
|
31
|
-
const isStartingSoon = startTime > currentMs && startTime <= lookaheadMs;
|
|
32
|
-
return isCurrentlyActive || isStartingSoon;
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Connect new elements that aren't already connected
|
|
37
|
-
*/
|
|
38
|
-
async connectNewElements(audioContext, elementsToConnect) {
|
|
39
|
-
for (const mediaElement of elementsToConnect) if (!this.connectedMediaSources.has(mediaElement)) {
|
|
40
|
-
const mediaElementSource = await mediaElement.getMediaElementSource(audioContext);
|
|
41
|
-
this.connectedMediaSources.set(mediaElement, {
|
|
42
|
-
mediaElementSource,
|
|
43
|
-
connected: false
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Update connection states for all managed elements
|
|
49
|
-
*/
|
|
50
|
-
async updateElementStates(currentMs) {
|
|
51
|
-
for (const [mediaElement, sourceInfo] of this.connectedMediaSources.entries()) {
|
|
52
|
-
const startTime = mediaElement.startTimeMs;
|
|
53
|
-
const endTime = mediaElement.endTimeMs;
|
|
54
|
-
const isCurrentlyActive = currentMs >= startTime && currentMs < endTime;
|
|
55
|
-
if (isCurrentlyActive && !sourceInfo.connected) await this.activateElement(mediaElement, sourceInfo);
|
|
56
|
-
else if (!isCurrentlyActive && sourceInfo.connected) await this.deactivateElement(mediaElement, sourceInfo);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Activate an element (connect to destination and start playback)
|
|
61
|
-
*/
|
|
62
|
-
async activateElement(mediaElement, sourceInfo) {
|
|
63
|
-
sourceInfo.mediaElementSource.connect(sourceInfo.mediaElementSource.context.destination);
|
|
64
|
-
sourceInfo.connected = true;
|
|
65
|
-
if (mediaElement.audioElement) {
|
|
66
|
-
const mediaTimeMs = mediaElement.currentSourceTimeMs;
|
|
67
|
-
mediaElement.audioElement.currentTime = mediaTimeMs / 1e3;
|
|
68
|
-
await mediaElement.audioElement.play();
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Deactivate an element (disconnect but keep prepared)
|
|
73
|
-
*/
|
|
74
|
-
async deactivateElement(mediaElement, sourceInfo) {
|
|
75
|
-
sourceInfo.mediaElementSource.disconnect();
|
|
76
|
-
sourceInfo.connected = false;
|
|
77
|
-
if (mediaElement.audioElement) mediaElement.audioElement.pause();
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Clean up elements that are far in the past
|
|
81
|
-
*/
|
|
82
|
-
cleanupOldElements(currentMs) {
|
|
83
|
-
const cleanupThresholdMs = currentMs - this.lookaheadMs;
|
|
84
|
-
for (const [mediaElement, sourceInfo] of this.connectedMediaSources.entries()) {
|
|
85
|
-
const endTime = mediaElement.endTimeMs;
|
|
86
|
-
if (endTime < cleanupThresholdMs) {
|
|
87
|
-
if (sourceInfo.connected) sourceInfo.mediaElementSource.disconnect();
|
|
88
|
-
this.connectedMediaSources.delete(mediaElement);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Clear all connected media sources (for cleanup)
|
|
94
|
-
*/
|
|
95
|
-
clearAll() {
|
|
96
|
-
for (const [, sourceInfo] of this.connectedMediaSources.entries()) try {
|
|
97
|
-
if (sourceInfo.connected) sourceInfo.mediaElementSource.disconnect();
|
|
98
|
-
} catch (_error) {}
|
|
99
|
-
this.connectedMediaSources.clear();
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Get connection status for testing/debugging
|
|
103
|
-
*/
|
|
104
|
-
getConnectionInfo() {
|
|
105
|
-
let connected = 0;
|
|
106
|
-
let prepared = 0;
|
|
107
|
-
for (const [, sourceInfo] of this.connectedMediaSources.entries()) if (sourceInfo.connected) connected++;
|
|
108
|
-
else prepared++;
|
|
109
|
-
return {
|
|
110
|
-
total: this.connectedMediaSources.size,
|
|
111
|
-
connected,
|
|
112
|
-
prepared
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* Set lookahead time
|
|
117
|
-
*/
|
|
118
|
-
setLookaheadMs(lookaheadMs) {
|
|
119
|
-
this.lookaheadMs = lookaheadMs;
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* Get current lookahead time
|
|
123
|
-
*/
|
|
124
|
-
getLookaheadMs() {
|
|
125
|
-
return this.lookaheadMs;
|
|
126
|
-
}
|
|
127
|
-
};
|
|
128
|
-
export { ElementConnectionManager };
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
export interface PlaybackControllerOptions {
|
|
2
|
-
fps?: number;
|
|
3
|
-
onTimeUpdate?: (timeMs: number) => void;
|
|
4
|
-
onPlayStateChange?: (playing: boolean) => void;
|
|
5
|
-
onError?: (error: Error) => void;
|
|
6
|
-
}
|
|
7
|
-
/**
|
|
8
|
-
* Manages playback timing, AudioContext lifecycle, and timeline synchronization
|
|
9
|
-
* Extracted from ContextMixin to improve separation of concerns and testability
|
|
10
|
-
*/
|
|
11
|
-
export declare class PlaybackController {
|
|
12
|
-
private playbackAudioContext;
|
|
13
|
-
private animationFrameRequest;
|
|
14
|
-
private options;
|
|
15
|
-
private playing;
|
|
16
|
-
private currentTimeMs;
|
|
17
|
-
private msPerFrame;
|
|
18
|
-
private audioStartTime;
|
|
19
|
-
private playbackStartTimeMs;
|
|
20
|
-
private activeChunks;
|
|
21
|
-
private chunkDurationMs;
|
|
22
|
-
private lookaheadChunks;
|
|
23
|
-
private currentChunkIndex;
|
|
24
|
-
private renderingChunks;
|
|
25
|
-
constructor(options?: PlaybackControllerOptions);
|
|
26
|
-
/**
|
|
27
|
-
* Start playback for the given timegroup
|
|
28
|
-
*/
|
|
29
|
-
startPlayback(timegroup: any, fromMs?: number): Promise<void>;
|
|
30
|
-
/**
|
|
31
|
-
* Stop playback and clean up resources
|
|
32
|
-
*/
|
|
33
|
-
stopPlayback(): Promise<void>;
|
|
34
|
-
/**
|
|
35
|
-
* Pause playback (can be resumed)
|
|
36
|
-
*/
|
|
37
|
-
pausePlayback(): Promise<void>;
|
|
38
|
-
/**
|
|
39
|
-
* Resume paused playback
|
|
40
|
-
*/
|
|
41
|
-
resumePlayback(): Promise<void>;
|
|
42
|
-
/**
|
|
43
|
-
* Seek to a specific time (restarts progressive playback from new position)
|
|
44
|
-
*/
|
|
45
|
-
seekTo(timeMs: number, timegroup?: any): Promise<void>;
|
|
46
|
-
/**
|
|
47
|
-
* Internal method to sync playhead with unified audio buffer timing
|
|
48
|
-
*/
|
|
49
|
-
private syncPlayheadToAudioBuffer;
|
|
50
|
-
/**
|
|
51
|
-
* Update playing state and notify observers
|
|
52
|
-
*/
|
|
53
|
-
private setPlaying;
|
|
54
|
-
/**
|
|
55
|
-
* Get current playback state
|
|
56
|
-
*/
|
|
57
|
-
isPlaying(): boolean;
|
|
58
|
-
/**
|
|
59
|
-
* Get current time
|
|
60
|
-
*/
|
|
61
|
-
getCurrentTime(): number;
|
|
62
|
-
/**
|
|
63
|
-
* Get current AudioContext
|
|
64
|
-
*/
|
|
65
|
-
getAudioContext(): AudioContext | null;
|
|
66
|
-
/**
|
|
67
|
-
* Check if AudioContext is ready
|
|
68
|
-
*/
|
|
69
|
-
isAudioContextReady(): boolean;
|
|
70
|
-
/**
|
|
71
|
-
* Get playback statistics for debugging
|
|
72
|
-
*/
|
|
73
|
-
getPlaybackInfo(): {
|
|
74
|
-
playing: boolean;
|
|
75
|
-
currentTimeMs: number;
|
|
76
|
-
audioContextState: string | null;
|
|
77
|
-
hasElementManager: boolean;
|
|
78
|
-
};
|
|
79
|
-
/**
|
|
80
|
-
* Update playback options
|
|
81
|
-
*/
|
|
82
|
-
updateOptions(options: Partial<PlaybackControllerOptions>): void;
|
|
83
|
-
/**
|
|
84
|
-
* Start progressive chunk rendering and playback
|
|
85
|
-
*/
|
|
86
|
-
private startProgressivePlayback;
|
|
87
|
-
/**
|
|
88
|
-
* Render and schedule a single audio chunk
|
|
89
|
-
*/
|
|
90
|
-
private renderAndScheduleChunk;
|
|
91
|
-
/**
|
|
92
|
-
* Update chunk rendering as playhead advances - now handles all chunk management
|
|
93
|
-
*/
|
|
94
|
-
private updateProgressiveChunks;
|
|
95
|
-
/**
|
|
96
|
-
* Systematically ensure chunks are ready ahead of current playback (synchronous)
|
|
97
|
-
*/
|
|
98
|
-
private ensureChunksAhead;
|
|
99
|
-
/**
|
|
100
|
-
* Clean up chunks that are behind the current playhead
|
|
101
|
-
*/
|
|
102
|
-
private cleanupOldChunks;
|
|
103
|
-
}
|