@editframe/elements 0.18.3-beta.0 → 0.18.7-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/EFMedia/AssetMediaEngine.browsertest.d.ts +0 -0
- package/dist/elements/EFMedia/AssetMediaEngine.d.ts +2 -4
- package/dist/elements/EFMedia/AssetMediaEngine.js +22 -3
- 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 +10 -2
- 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/EFMedia/AssetMediaEngine.browsertest.ts +100 -0
- package/src/elements/EFMedia/AssetMediaEngine.ts +52 -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 +25 -3
- 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
|
File without changes
|
|
@@ -3,6 +3,7 @@ import { AudioRendition, InitSegmentPaths, MediaEngine, SegmentTimeRange } from
|
|
|
3
3
|
import { UrlGenerator } from '../../transcoding/utils/UrlGenerator';
|
|
4
4
|
import { EFMedia } from '../EFMedia';
|
|
5
5
|
import { BaseMediaEngine } from './BaseMediaEngine';
|
|
6
|
+
import { MediaRendition } from './shared/MediaTaskUtils';
|
|
6
7
|
export declare class AssetMediaEngine extends BaseMediaEngine implements MediaEngine {
|
|
7
8
|
host: EFMedia;
|
|
8
9
|
src: string;
|
|
@@ -40,8 +41,5 @@ export declare class AssetMediaEngine extends BaseMediaEngine implements MediaEn
|
|
|
40
41
|
* Calculate audio segments for variable-duration segments using track fragment index
|
|
41
42
|
*/
|
|
42
43
|
calculateAudioSegmentRange(fromMs: number, toMs: number, rendition: AudioRendition, _durationMs: number): SegmentTimeRange[];
|
|
43
|
-
computeSegmentId(desiredSeekTimeMs: number, rendition:
|
|
44
|
-
trackId: number | undefined;
|
|
45
|
-
src: string;
|
|
46
|
-
}): number;
|
|
44
|
+
computeSegmentId(desiredSeekTimeMs: number, rendition: MediaRendition): number;
|
|
47
45
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BaseMediaEngine } from "./BaseMediaEngine.js";
|
|
2
|
+
import { convertToScaledTime, roundToMilliseconds } from "./shared/PrecisionUtils.js";
|
|
2
3
|
var AssetMediaEngine = class AssetMediaEngine extends BaseMediaEngine {
|
|
3
4
|
static async fetch(host, urlGenerator, src) {
|
|
4
5
|
const url = urlGenerator.generateTrackFragmentIndexUrl(src);
|
|
@@ -105,12 +106,30 @@ var AssetMediaEngine = class AssetMediaEngine extends BaseMediaEngine {
|
|
|
105
106
|
const track = this.data[rendition.trackId];
|
|
106
107
|
if (!track) throw new Error("Track not found");
|
|
107
108
|
const { timescale, segments } = track;
|
|
108
|
-
const
|
|
109
|
+
const startTimeOffsetMs = "startTimeOffsetMs" in rendition && rendition.startTimeOffsetMs || 0;
|
|
110
|
+
const mediaTimeMs = roundToMilliseconds(desiredSeekTimeMs + startTimeOffsetMs);
|
|
111
|
+
const scaledSeekTime = convertToScaledTime(mediaTimeMs, timescale);
|
|
109
112
|
for (let i = segments.length - 1; i >= 0; i--) {
|
|
110
113
|
const segment = segments[i];
|
|
111
|
-
|
|
114
|
+
const segmentEndTime = segment.cts + segment.duration;
|
|
115
|
+
if (segment.cts <= scaledSeekTime && scaledSeekTime < segmentEndTime) return i;
|
|
116
|
+
}
|
|
117
|
+
let nearestSegmentIndex = 0;
|
|
118
|
+
let nearestDistance = Number.MAX_SAFE_INTEGER;
|
|
119
|
+
for (let i = 0; i < segments.length; i++) {
|
|
120
|
+
const segment = segments[i];
|
|
121
|
+
const segmentStartTime = segment.cts;
|
|
122
|
+
const segmentEndTime = segment.cts + segment.duration;
|
|
123
|
+
let distance;
|
|
124
|
+
if (scaledSeekTime < segmentStartTime) distance = segmentStartTime - scaledSeekTime;
|
|
125
|
+
else if (scaledSeekTime >= segmentEndTime) distance = scaledSeekTime - segmentEndTime;
|
|
126
|
+
else return i;
|
|
127
|
+
if (distance < nearestDistance) {
|
|
128
|
+
nearestDistance = distance;
|
|
129
|
+
nearestSegmentIndex = i;
|
|
130
|
+
}
|
|
112
131
|
}
|
|
113
|
-
return
|
|
132
|
+
return nearestSegmentIndex;
|
|
114
133
|
}
|
|
115
134
|
};
|
|
116
135
|
export { AssetMediaEngine };
|
|
@@ -76,14 +76,33 @@ var BaseMediaEngine = class {
|
|
|
76
76
|
*/
|
|
77
77
|
calculateAudioSegmentRange(fromMs, toMs, rendition, durationMs) {
|
|
78
78
|
if (fromMs >= toMs) return [];
|
|
79
|
-
const segmentDurationMs = rendition.segmentDurationMs || 1e3;
|
|
80
79
|
const segments = [];
|
|
80
|
+
if (rendition.segmentDurationsMs && rendition.segmentDurationsMs.length > 0) {
|
|
81
|
+
let cumulativeTime = 0;
|
|
82
|
+
for (let i = 0; i < rendition.segmentDurationsMs.length; i++) {
|
|
83
|
+
const segmentDuration = rendition.segmentDurationsMs[i];
|
|
84
|
+
if (segmentDuration === void 0) continue;
|
|
85
|
+
const segmentStartMs = cumulativeTime;
|
|
86
|
+
const segmentEndMs = Math.min(cumulativeTime + segmentDuration, durationMs);
|
|
87
|
+
if (segmentStartMs >= durationMs) break;
|
|
88
|
+
if (segmentStartMs < toMs && segmentEndMs > fromMs) segments.push({
|
|
89
|
+
segmentId: i + 1,
|
|
90
|
+
startMs: segmentStartMs,
|
|
91
|
+
endMs: segmentEndMs
|
|
92
|
+
});
|
|
93
|
+
cumulativeTime += segmentDuration;
|
|
94
|
+
if (cumulativeTime >= durationMs) break;
|
|
95
|
+
}
|
|
96
|
+
return segments;
|
|
97
|
+
}
|
|
98
|
+
const segmentDurationMs = rendition.segmentDurationMs || 1e3;
|
|
81
99
|
const startSegmentIndex = Math.floor(fromMs / segmentDurationMs);
|
|
82
100
|
const endSegmentIndex = Math.floor(toMs / segmentDurationMs);
|
|
83
101
|
for (let i = startSegmentIndex; i <= endSegmentIndex; i++) {
|
|
84
102
|
const segmentId = i + 1;
|
|
85
103
|
const segmentStartMs = i * segmentDurationMs;
|
|
86
104
|
const segmentEndMs = Math.min((i + 1) * segmentDurationMs, durationMs);
|
|
105
|
+
if (segmentStartMs >= durationMs) break;
|
|
87
106
|
if (segmentStartMs < toMs && segmentEndMs > fromMs) segments.push({
|
|
88
107
|
segmentId,
|
|
89
108
|
startMs: segmentStartMs,
|
|
@@ -3,8 +3,8 @@ interface BufferedSeekingInputOptions {
|
|
|
3
3
|
videoBufferSize?: number;
|
|
4
4
|
audioBufferSize?: number;
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
* Applied during seeking to
|
|
6
|
+
* Timeline offset in milliseconds to map user timeline to media timeline.
|
|
7
|
+
* Applied during seeking to handle media that doesn't start at 0ms.
|
|
8
8
|
*/
|
|
9
9
|
startTimeOffsetMs?: number;
|
|
10
10
|
}
|
|
@@ -18,8 +18,8 @@ export declare class BufferedSeekingInput {
|
|
|
18
18
|
private trackIteratorCreationPromises;
|
|
19
19
|
private trackSeekPromises;
|
|
20
20
|
/**
|
|
21
|
-
*
|
|
22
|
-
* Applied during seeking to
|
|
21
|
+
* Timeline offset in milliseconds to map user timeline to media timeline.
|
|
22
|
+
* Applied during seeking to handle media that doesn't start at 0ms.
|
|
23
23
|
*/
|
|
24
24
|
private readonly startTimeOffsetMs;
|
|
25
25
|
constructor(arrayBuffer: ArrayBuffer, options?: BufferedSeekingInputOptions);
|
|
@@ -36,7 +36,7 @@ export declare class BufferedSeekingInput {
|
|
|
36
36
|
getTrackIterator(trackId: number): Promise<AsyncIterator<MediaSample, any, undefined>>;
|
|
37
37
|
private createIteratorSafe;
|
|
38
38
|
createTrackBuffer(trackId: number): Promise<void>;
|
|
39
|
-
seek(trackId: number, timeMs: number): Promise<MediaSample>;
|
|
39
|
+
seek(trackId: number, timeMs: number): Promise<MediaSample | undefined>;
|
|
40
40
|
private resetIterator;
|
|
41
41
|
private seekSafe;
|
|
42
42
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { roundToMilliseconds } from "./shared/PrecisionUtils.js";
|
|
1
2
|
import { SampleBuffer } from "../SampleBuffer.js";
|
|
2
3
|
import { AudioSampleSink, BufferSource, Input, MP4, VideoSampleSink } from "mediabunny";
|
|
3
4
|
const defaultOptions = {
|
|
@@ -113,10 +114,11 @@ var BufferedSeekingInput = class {
|
|
|
113
114
|
}
|
|
114
115
|
}
|
|
115
116
|
async seek(trackId, timeMs) {
|
|
116
|
-
const
|
|
117
|
+
const mediaTimeMs = timeMs + this.startTimeOffsetMs;
|
|
118
|
+
const roundedMediaTimeMs = roundToMilliseconds(mediaTimeMs);
|
|
117
119
|
const existingSeek = this.trackSeekPromises.get(trackId);
|
|
118
120
|
if (existingSeek) await existingSeek;
|
|
119
|
-
const seekPromise = this.seekSafe(trackId,
|
|
121
|
+
const seekPromise = this.seekSafe(trackId, roundedMediaTimeMs);
|
|
120
122
|
this.trackSeekPromises.set(trackId, seekPromise);
|
|
121
123
|
try {
|
|
122
124
|
return await seekPromise;
|
|
@@ -138,12 +140,24 @@ var BufferedSeekingInput = class {
|
|
|
138
140
|
async seekSafe(trackId, timeMs) {
|
|
139
141
|
if (!this.trackBuffers.has(trackId)) await this.createTrackBuffer(trackId);
|
|
140
142
|
const trackBuffer = this.trackBuffers.get(trackId);
|
|
141
|
-
if (timeMs < trackBuffer.firstTimestamp * 1e3) await this.resetIterator(trackId);
|
|
142
|
-
const alreadyInBuffer = trackBuffer.find(timeMs);
|
|
143
143
|
const track = await this.getTrack(trackId);
|
|
144
|
-
const firstTimestampMs = await track.getFirstTimestamp() * 1e3;
|
|
145
|
-
|
|
146
|
-
if (
|
|
144
|
+
const firstTimestampMs = roundToMilliseconds(await track.getFirstTimestamp() * 1e3);
|
|
145
|
+
let roundedTimeMs = roundToMilliseconds(timeMs);
|
|
146
|
+
if (roundedTimeMs < firstTimestampMs) {
|
|
147
|
+
const bufferContents$1 = trackBuffer.getContents();
|
|
148
|
+
if (bufferContents$1.length > 0) {
|
|
149
|
+
timeMs = firstTimestampMs;
|
|
150
|
+
roundedTimeMs = roundToMilliseconds(timeMs);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const bufferContents = trackBuffer.getContents();
|
|
154
|
+
if (bufferContents.length > 0) {
|
|
155
|
+
const bufferStartMs = roundToMilliseconds(trackBuffer.firstTimestamp * 1e3);
|
|
156
|
+
const lastSample = bufferContents[bufferContents.length - 1];
|
|
157
|
+
const bufferEndMs = lastSample ? roundToMilliseconds((lastSample.timestamp + (lastSample.duration || 0)) * 1e3) : bufferStartMs;
|
|
158
|
+
if (roundedTimeMs < bufferStartMs || roundedTimeMs > bufferEndMs) await this.resetIterator(trackId);
|
|
159
|
+
}
|
|
160
|
+
const alreadyInBuffer = trackBuffer.find(timeMs);
|
|
147
161
|
if (alreadyInBuffer) return alreadyInBuffer;
|
|
148
162
|
const iterator = await this.getTrackIterator(trackId);
|
|
149
163
|
while (true) {
|
|
@@ -153,6 +167,12 @@ var BufferedSeekingInput = class {
|
|
|
153
167
|
if (foundSample) return foundSample;
|
|
154
168
|
if (done) break;
|
|
155
169
|
}
|
|
170
|
+
const finalBufferContents = trackBuffer.getContents();
|
|
171
|
+
if (finalBufferContents.length > 0) {
|
|
172
|
+
const lastSample = finalBufferContents[finalBufferContents.length - 1];
|
|
173
|
+
const lastSampleEndMs = roundToMilliseconds(((lastSample?.timestamp || 0) + (lastSample?.duration || 0)) * 1e3);
|
|
174
|
+
if (roundToMilliseconds(timeMs) >= lastSampleEndMs) return lastSample;
|
|
175
|
+
}
|
|
156
176
|
throw new NoSample(`Sample not found for time ${timeMs} in ${track.type} track ${trackId}`);
|
|
157
177
|
}
|
|
158
178
|
};
|
|
@@ -27,5 +27,5 @@ export declare class JitMediaEngine extends BaseMediaEngine implements MediaEngi
|
|
|
27
27
|
trackId: number | undefined;
|
|
28
28
|
src: string;
|
|
29
29
|
}): Promise<ArrayBuffer>;
|
|
30
|
-
computeSegmentId(desiredSeekTimeMs: number, rendition: VideoRendition | AudioRendition): number;
|
|
30
|
+
computeSegmentId(desiredSeekTimeMs: number, rendition: VideoRendition | AudioRendition): number | undefined;
|
|
31
31
|
}
|
|
@@ -24,7 +24,8 @@ var JitMediaEngine = class JitMediaEngine extends BaseMediaEngine {
|
|
|
24
24
|
id: rendition.id,
|
|
25
25
|
trackId: void 0,
|
|
26
26
|
src: this.data.sourceUrl,
|
|
27
|
-
segmentDurationMs: rendition.segmentDurationMs
|
|
27
|
+
segmentDurationMs: rendition.segmentDurationMs,
|
|
28
|
+
segmentDurationsMs: rendition.segmentDurationsMs
|
|
28
29
|
};
|
|
29
30
|
}
|
|
30
31
|
get videoRendition() {
|
|
@@ -34,7 +35,8 @@ var JitMediaEngine = class JitMediaEngine extends BaseMediaEngine {
|
|
|
34
35
|
id: rendition.id,
|
|
35
36
|
trackId: void 0,
|
|
36
37
|
src: this.data.sourceUrl,
|
|
37
|
-
segmentDurationMs: rendition.segmentDurationMs
|
|
38
|
+
segmentDurationMs: rendition.segmentDurationMs,
|
|
39
|
+
segmentDurationsMs: rendition.segmentDurationsMs
|
|
38
40
|
};
|
|
39
41
|
}
|
|
40
42
|
get templates() {
|
|
@@ -53,9 +55,26 @@ var JitMediaEngine = class JitMediaEngine extends BaseMediaEngine {
|
|
|
53
55
|
return this.fetchMediaCache(url);
|
|
54
56
|
}
|
|
55
57
|
computeSegmentId(desiredSeekTimeMs, rendition) {
|
|
58
|
+
if (desiredSeekTimeMs > this.durationMs) return void 0;
|
|
59
|
+
if (rendition.segmentDurationsMs && rendition.segmentDurationsMs.length > 0) {
|
|
60
|
+
let cumulativeTime = 0;
|
|
61
|
+
for (let i = 0; i < rendition.segmentDurationsMs.length; i++) {
|
|
62
|
+
const segmentDuration = rendition.segmentDurationsMs[i];
|
|
63
|
+
if (segmentDuration === void 0) throw new Error("Segment duration is required for JIT metadata");
|
|
64
|
+
const segmentStartMs$1 = cumulativeTime;
|
|
65
|
+
const segmentEndMs = cumulativeTime + segmentDuration;
|
|
66
|
+
const isLastSegment = i === rendition.segmentDurationsMs.length - 1;
|
|
67
|
+
const includesEndTime = isLastSegment && desiredSeekTimeMs === this.durationMs;
|
|
68
|
+
if (desiredSeekTimeMs >= segmentStartMs$1 && (desiredSeekTimeMs < segmentEndMs || includesEndTime)) return i + 1;
|
|
69
|
+
cumulativeTime += segmentDuration;
|
|
70
|
+
if (cumulativeTime >= this.durationMs) break;
|
|
71
|
+
}
|
|
72
|
+
return void 0;
|
|
73
|
+
}
|
|
56
74
|
if (!rendition.segmentDurationMs) throw new Error("Segment duration is required for JIT metadata");
|
|
57
75
|
const segmentIndex = Math.floor(desiredSeekTimeMs / rendition.segmentDurationMs);
|
|
58
|
-
|
|
76
|
+
const segmentStartMs = segmentIndex * rendition.segmentDurationMs;
|
|
77
|
+
if (segmentStartMs >= this.durationMs) return void 0;
|
|
59
78
|
return segmentIndex + 1;
|
|
60
79
|
}
|
|
61
80
|
};
|
|
@@ -57,7 +57,10 @@ function makeAudioFrequencyAnalysisTask(element) {
|
|
|
57
57
|
const currentTimeMs = element.currentSourceTimeMs;
|
|
58
58
|
const analysisWindowMs = 5e3;
|
|
59
59
|
const fromMs = Math.max(0, currentTimeMs);
|
|
60
|
-
const
|
|
60
|
+
const maxToMs = fromMs + analysisWindowMs;
|
|
61
|
+
const videoDurationMs = element.intrinsicDurationMs || 0;
|
|
62
|
+
const toMs = videoDurationMs > 0 ? Math.min(maxToMs, videoDurationMs) : maxToMs;
|
|
63
|
+
if (fromMs >= toMs) return null;
|
|
61
64
|
const { fetchAudioSpanningTime: fetchAudioSpan } = await import("../shared/AudioSpanUtils.js");
|
|
62
65
|
const audioSpan = await fetchAudioSpan(element, fromMs, toMs, new AbortController().signal);
|
|
63
66
|
if (!audioSpan || !audioSpan.blob) {
|
|
@@ -8,13 +8,21 @@ const makeAudioInputTask = (host) => {
|
|
|
8
8
|
console.error("audioInputTask error", error);
|
|
9
9
|
},
|
|
10
10
|
onComplete: (_value) => {},
|
|
11
|
-
task: async () => {
|
|
11
|
+
task: async (_, { signal }) => {
|
|
12
12
|
const initSegment = await host.audioInitSegmentFetchTask.taskComplete;
|
|
13
|
+
signal.throwIfAborted();
|
|
13
14
|
const segment = await host.audioSegmentFetchTask.taskComplete;
|
|
15
|
+
signal.throwIfAborted();
|
|
14
16
|
if (!initSegment || !segment) throw new Error("Init segment or segment is not available");
|
|
15
|
-
|
|
17
|
+
const mediaEngine = await host.mediaEngineTask.taskComplete;
|
|
18
|
+
const audioRendition = mediaEngine?.audioRendition;
|
|
19
|
+
const startTimeOffsetMs = audioRendition?.startTimeOffsetMs;
|
|
20
|
+
const arrayBuffer = await new Blob([initSegment, segment]).arrayBuffer();
|
|
21
|
+
signal.throwIfAborted();
|
|
22
|
+
return new BufferedSeekingInput(arrayBuffer, {
|
|
16
23
|
videoBufferSize: EFMedia.VIDEO_SAMPLE_BUFFER_SIZE,
|
|
17
|
-
audioBufferSize: EFMedia.AUDIO_SAMPLE_BUFFER_SIZE
|
|
24
|
+
audioBufferSize: EFMedia.AUDIO_SAMPLE_BUFFER_SIZE,
|
|
25
|
+
startTimeOffsetMs
|
|
18
26
|
});
|
|
19
27
|
}
|
|
20
28
|
});
|
package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.d.ts
ADDED
|
File without changes
|
|
@@ -8,15 +8,23 @@ const makeAudioSeekTask = (host) => {
|
|
|
8
8
|
console.error("audioSeekTask error", error);
|
|
9
9
|
},
|
|
10
10
|
onComplete: (_value) => {},
|
|
11
|
-
task: async (
|
|
11
|
+
task: async ([targetSeekTimeMs], { signal }) => {
|
|
12
12
|
await host.audioSegmentIdTask.taskComplete;
|
|
13
|
+
signal.throwIfAborted();
|
|
13
14
|
await host.audioSegmentFetchTask.taskComplete;
|
|
15
|
+
signal.throwIfAborted();
|
|
14
16
|
await host.audioInitSegmentFetchTask.taskComplete;
|
|
17
|
+
signal.throwIfAborted();
|
|
15
18
|
const audioInput = await host.audioInputTask.taskComplete;
|
|
19
|
+
signal.throwIfAborted();
|
|
16
20
|
if (!audioInput) throw new Error("Audio input is not available");
|
|
17
21
|
const audioTrack = await audioInput.getFirstAudioTrack();
|
|
18
22
|
if (!audioTrack) throw new Error("Audio track is not available");
|
|
19
|
-
|
|
23
|
+
signal.throwIfAborted();
|
|
24
|
+
const sample = await audioInput.seek(audioTrack.id, targetSeekTimeMs);
|
|
25
|
+
signal.throwIfAborted();
|
|
26
|
+
if (sample === void 0 && signal.aborted) return void 0;
|
|
27
|
+
if (sample === void 0) throw new Error("Audio seek failed to find sample");
|
|
20
28
|
return sample;
|
|
21
29
|
}
|
|
22
30
|
});
|
|
@@ -10,7 +10,17 @@ const makeAudioSegmentFetchTask = (host) => {
|
|
|
10
10
|
task: async (_, { signal }) => {
|
|
11
11
|
const mediaEngine = await getLatestMediaEngine(host, signal);
|
|
12
12
|
const segmentId = await host.audioSegmentIdTask.taskComplete;
|
|
13
|
-
if (segmentId === void 0)
|
|
13
|
+
if (segmentId === void 0) {
|
|
14
|
+
const rendition = mediaEngine.audioRendition;
|
|
15
|
+
const debugInfo = {
|
|
16
|
+
hasRendition: !!rendition,
|
|
17
|
+
segmentDurationMs: rendition?.segmentDurationMs,
|
|
18
|
+
segmentDurationsMs: rendition?.segmentDurationsMs?.length || 0,
|
|
19
|
+
desiredSeekTimeMs: host.desiredSeekTimeMs,
|
|
20
|
+
intrinsicDurationMs: host.intrinsicDurationMs
|
|
21
|
+
};
|
|
22
|
+
throw new Error(`Segment ID is not available for audio. Debug info: ${JSON.stringify(debugInfo)}`);
|
|
23
|
+
}
|
|
14
24
|
return mediaEngine.fetchMediaSegment(segmentId, mediaEngine.getAudioRendition(), signal);
|
|
15
25
|
}
|
|
16
26
|
});
|
|
@@ -7,9 +7,10 @@ const makeAudioSegmentIdTask = (host) => {
|
|
|
7
7
|
console.error("audioSegmentIdTask error", error);
|
|
8
8
|
},
|
|
9
9
|
onComplete: (_value) => {},
|
|
10
|
-
task: async (
|
|
10
|
+
task: async ([, targetSeekTimeMs], { signal }) => {
|
|
11
11
|
const mediaEngine = await getLatestMediaEngine(host, signal);
|
|
12
|
-
|
|
12
|
+
signal.throwIfAborted();
|
|
13
|
+
return mediaEngine.computeSegmentId(targetSeekTimeMs, mediaEngine.getAudioRendition());
|
|
13
14
|
}
|
|
14
15
|
});
|
|
15
16
|
};
|
|
@@ -24,7 +24,10 @@ function makeAudioTimeDomainAnalysisTask(element) {
|
|
|
24
24
|
const currentTimeMs = element.currentSourceTimeMs;
|
|
25
25
|
const analysisWindowMs = 5e3;
|
|
26
26
|
const fromMs = Math.max(0, currentTimeMs);
|
|
27
|
-
const
|
|
27
|
+
const maxToMs = fromMs + analysisWindowMs;
|
|
28
|
+
const videoDurationMs = element.intrinsicDurationMs || 0;
|
|
29
|
+
const toMs = videoDurationMs > 0 ? Math.min(maxToMs, videoDurationMs) : maxToMs;
|
|
30
|
+
if (fromMs >= toMs) return null;
|
|
28
31
|
const { fetchAudioSpanningTime: fetchAudioSpan } = await import("../shared/AudioSpanUtils.js");
|
|
29
32
|
const audioSpan = await fetchAudioSpan(element, fromMs, toMs, new AbortController().signal);
|
|
30
33
|
if (!audioSpan || !audioSpan.blob) {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized precision utilities for consistent timing calculations across the media pipeline.
|
|
3
|
+
*
|
|
4
|
+
* The key insight is that floating-point precision errors can cause inconsistencies between:
|
|
5
|
+
* 1. Segment selection logic (in AssetMediaEngine.computeSegmentId)
|
|
6
|
+
* 2. Sample finding logic (in SampleBuffer.find)
|
|
7
|
+
* 3. Timeline mapping (in BufferedSeekingInput.seek)
|
|
8
|
+
*
|
|
9
|
+
* All timing calculations must use the same rounding strategy to ensure consistency.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Round time to millisecond precision to handle floating-point precision issues.
|
|
13
|
+
* Uses Math.round for consistent behavior across the entire pipeline.
|
|
14
|
+
*
|
|
15
|
+
* This function should be used for ALL time-related calculations that need to be
|
|
16
|
+
* compared between different parts of the system.
|
|
17
|
+
*/
|
|
18
|
+
export declare const roundToMilliseconds: (timeMs: number) => number;
|
|
19
|
+
/**
|
|
20
|
+
* Convert media time (in seconds) to scaled time units using consistent rounding.
|
|
21
|
+
* This is used in segment selection to convert from milliseconds to timescale units.
|
|
22
|
+
*/
|
|
23
|
+
export declare const convertToScaledTime: (timeMs: number, timescale: number) => number;
|
|
24
|
+
/**
|
|
25
|
+
* Convert scaled time units back to media time (in milliseconds) using consistent rounding.
|
|
26
|
+
* This is the inverse of convertToScaledTime.
|
|
27
|
+
*/
|
|
28
|
+
export declare const convertFromScaledTime: (scaledTime: number, timescale: number) => number;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized precision utilities for consistent timing calculations across the media pipeline.
|
|
3
|
+
*
|
|
4
|
+
* The key insight is that floating-point precision errors can cause inconsistencies between:
|
|
5
|
+
* 1. Segment selection logic (in AssetMediaEngine.computeSegmentId)
|
|
6
|
+
* 2. Sample finding logic (in SampleBuffer.find)
|
|
7
|
+
* 3. Timeline mapping (in BufferedSeekingInput.seek)
|
|
8
|
+
*
|
|
9
|
+
* All timing calculations must use the same rounding strategy to ensure consistency.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Round time to millisecond precision to handle floating-point precision issues.
|
|
13
|
+
* Uses Math.round for consistent behavior across the entire pipeline.
|
|
14
|
+
*
|
|
15
|
+
* This function should be used for ALL time-related calculations that need to be
|
|
16
|
+
* compared between different parts of the system.
|
|
17
|
+
*/
|
|
18
|
+
const roundToMilliseconds = (timeMs) => {
|
|
19
|
+
return Math.round(timeMs * 1e3) / 1e3;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Convert media time (in seconds) to scaled time units using consistent rounding.
|
|
23
|
+
* This is used in segment selection to convert from milliseconds to timescale units.
|
|
24
|
+
*/
|
|
25
|
+
const convertToScaledTime = (timeMs, timescale) => {
|
|
26
|
+
const scaledTime = timeMs / 1e3 * timescale;
|
|
27
|
+
return Math.round(scaledTime);
|
|
28
|
+
};
|
|
29
|
+
export { convertToScaledTime, roundToMilliseconds };
|
|
@@ -8,16 +8,25 @@ const makeVideoSeekTask = (host) => {
|
|
|
8
8
|
console.error("videoSeekTask error", error);
|
|
9
9
|
},
|
|
10
10
|
onComplete: (_value) => {},
|
|
11
|
-
task: async (
|
|
11
|
+
task: async ([targetSeekTimeMs], { signal }) => {
|
|
12
12
|
await host.mediaEngineTask.taskComplete;
|
|
13
|
+
signal.throwIfAborted();
|
|
13
14
|
await host.videoSegmentIdTask.taskComplete;
|
|
15
|
+
signal.throwIfAborted();
|
|
14
16
|
await host.videoSegmentFetchTask.taskComplete;
|
|
17
|
+
signal.throwIfAborted();
|
|
15
18
|
await host.videoInitSegmentFetchTask.taskComplete;
|
|
19
|
+
signal.throwIfAborted();
|
|
16
20
|
const videoInput = await host.videoInputTask.taskComplete;
|
|
21
|
+
signal.throwIfAborted();
|
|
17
22
|
if (!videoInput) throw new Error("Video input is not available");
|
|
18
23
|
const videoTrack = await videoInput.getFirstVideoTrack();
|
|
19
24
|
if (!videoTrack) throw new Error("Video track is not available");
|
|
20
|
-
|
|
25
|
+
signal.throwIfAborted();
|
|
26
|
+
const sample = await videoInput.seek(videoTrack.id, targetSeekTimeMs);
|
|
27
|
+
signal.throwIfAborted();
|
|
28
|
+
if (sample === void 0 && signal.aborted) return void 0;
|
|
29
|
+
if (sample === void 0) throw new Error("Video seek failed to find sample");
|
|
21
30
|
return sample;
|
|
22
31
|
}
|
|
23
32
|
});
|
|
@@ -10,7 +10,17 @@ const makeVideoSegmentFetchTask = (host) => {
|
|
|
10
10
|
task: async (_, { signal }) => {
|
|
11
11
|
const mediaEngine = await getLatestMediaEngine(host, signal);
|
|
12
12
|
const segmentId = await host.videoSegmentIdTask.taskComplete;
|
|
13
|
-
if (segmentId === void 0)
|
|
13
|
+
if (segmentId === void 0) {
|
|
14
|
+
const rendition = mediaEngine.videoRendition;
|
|
15
|
+
const debugInfo = {
|
|
16
|
+
hasRendition: !!rendition,
|
|
17
|
+
segmentDurationMs: rendition?.segmentDurationMs,
|
|
18
|
+
segmentDurationsMs: rendition?.segmentDurationsMs?.length || 0,
|
|
19
|
+
desiredSeekTimeMs: host.desiredSeekTimeMs,
|
|
20
|
+
intrinsicDurationMs: host.intrinsicDurationMs
|
|
21
|
+
};
|
|
22
|
+
throw new Error(`Segment ID is not available for video. Debug info: ${JSON.stringify(debugInfo)}`);
|
|
23
|
+
}
|
|
14
24
|
return mediaEngine.fetchMediaSegment(segmentId, mediaEngine.getVideoRendition(), signal);
|
|
15
25
|
}
|
|
16
26
|
});
|
|
@@ -7,9 +7,10 @@ const makeVideoSegmentIdTask = (host) => {
|
|
|
7
7
|
console.error("videoSegmentIdTask error", error);
|
|
8
8
|
},
|
|
9
9
|
onComplete: (_value) => {},
|
|
10
|
-
task: async (
|
|
10
|
+
task: async ([, targetSeekTimeMs], { signal }) => {
|
|
11
11
|
const mediaEngine = await getLatestMediaEngine(host, signal);
|
|
12
|
-
|
|
12
|
+
signal.throwIfAborted();
|
|
13
|
+
return mediaEngine.computeSegmentId(targetSeekTimeMs, mediaEngine.getVideoRendition());
|
|
13
14
|
}
|
|
14
15
|
});
|
|
15
16
|
};
|
|
@@ -12,8 +12,6 @@ export declare class EFMedia extends EFMedia_base {
|
|
|
12
12
|
static readonly VIDEO_SAMPLE_BUFFER_SIZE = 30;
|
|
13
13
|
static readonly AUDIO_SAMPLE_BUFFER_SIZE = 120;
|
|
14
14
|
static get observedAttributes(): string[];
|
|
15
|
-
private mediaSourceService;
|
|
16
|
-
private audioElementFactory;
|
|
17
15
|
static styles: import('lit').CSSResult[];
|
|
18
16
|
currentTimeMs: number;
|
|
19
17
|
/**
|
|
@@ -86,10 +84,6 @@ export declare class EFMedia extends EFMedia_base {
|
|
|
86
84
|
* Now powered by clean, testable utility functions
|
|
87
85
|
*/
|
|
88
86
|
fetchAudioSpanningTime(fromMs: number, toMs: number, signal?: AbortSignal): Promise<AudioSpan>;
|
|
89
|
-
/**
|
|
90
|
-
* Get the HTML audio element for ContextMixin integration
|
|
91
|
-
*/
|
|
92
|
-
get audioElement(): HTMLAudioElement | null;
|
|
93
87
|
/**
|
|
94
88
|
* Check if an audio segment is cached in the unified buffer system
|
|
95
89
|
* Now uses the same caching approach as video for consistency
|
|
@@ -100,11 +94,5 @@ export declare class EFMedia extends EFMedia_base {
|
|
|
100
94
|
* Now uses the same caching approach as video for consistency
|
|
101
95
|
*/
|
|
102
96
|
getCachedAudioSegments(segmentIds: number[]): Set<number>;
|
|
103
|
-
/**
|
|
104
|
-
* Get MediaElementAudioSourceNode for ContextMixin integration
|
|
105
|
-
* Uses AudioElementFactory for proper caching and lifecycle management
|
|
106
|
-
*/
|
|
107
|
-
getMediaElementSource(audioContext: AudioContext): Promise<MediaElementAudioSourceNode>;
|
|
108
|
-
disconnectedCallback(): void;
|
|
109
97
|
}
|
|
110
98
|
export {};
|
package/dist/elements/EFMedia.js
CHANGED
|
@@ -8,8 +8,7 @@ import { makeAudioSeekTask } from "./EFMedia/audioTasks/makeAudioSeekTask.js";
|
|
|
8
8
|
import { makeAudioSegmentFetchTask } from "./EFMedia/audioTasks/makeAudioSegmentFetchTask.js";
|
|
9
9
|
import { makeAudioSegmentIdTask } from "./EFMedia/audioTasks/makeAudioSegmentIdTask.js";
|
|
10
10
|
import { makeAudioTimeDomainAnalysisTask } from "./EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js";
|
|
11
|
-
import {
|
|
12
|
-
import { MediaSourceService } from "./EFMedia/services/MediaSourceService.js";
|
|
11
|
+
import { fetchAudioSpanningTime } from "./EFMedia/shared/AudioSpanUtils.js";
|
|
13
12
|
import { EFSourceMixin } from "./EFSourceMixin.js";
|
|
14
13
|
import { EFTemporal } from "./EFTemporal.js";
|
|
15
14
|
import { FetchMixin } from "./FetchMixin.js";
|
|
@@ -28,13 +27,6 @@ const deepGetMediaElements = (element, medias = []) => {
|
|
|
28
27
|
var EFMedia = class extends EFTargetable(EFSourceMixin(EFTemporal(FetchMixin(LitElement)), { assetType: "isobmff_files" })) {
|
|
29
28
|
constructor(..._args) {
|
|
30
29
|
super(..._args);
|
|
31
|
-
this.mediaSourceService = new MediaSourceService({
|
|
32
|
-
onError: (error) => {
|
|
33
|
-
console.error("🎵 [EFMedia] MediaSourceService error:", error);
|
|
34
|
-
},
|
|
35
|
-
onReady: () => {}
|
|
36
|
-
});
|
|
37
|
-
this.audioElementFactory = new AudioElementFactory();
|
|
38
30
|
this.currentTimeMs = 0;
|
|
39
31
|
this.audioBufferDurationMs = 3e4;
|
|
40
32
|
this.maxAudioBufferFetches = 2;
|
|
@@ -112,6 +104,8 @@ var EFMedia = class extends EFTargetable(EFSourceMixin(EFTemporal(FetchMixin(Lit
|
|
|
112
104
|
}
|
|
113
105
|
updated(changedProperties) {
|
|
114
106
|
super.updated(changedProperties);
|
|
107
|
+
const newCurrentSourceTimeMs = this.currentSourceTimeMs;
|
|
108
|
+
if (newCurrentSourceTimeMs !== this.desiredSeekTimeMs) this.executeSeek(newCurrentSourceTimeMs);
|
|
115
109
|
if (changedProperties.has("ownCurrentTimeMs")) this.executeSeek(this.currentSourceTimeMs);
|
|
116
110
|
if (changedProperties.has("currentTime") || changedProperties.has("ownCurrentTimeMs")) updateAnimations(this);
|
|
117
111
|
}
|
|
@@ -132,15 +126,7 @@ var EFMedia = class extends EFTargetable(EFSourceMixin(EFTemporal(FetchMixin(Lit
|
|
|
132
126
|
* Now powered by clean, testable utility functions
|
|
133
127
|
*/
|
|
134
128
|
async fetchAudioSpanningTime(fromMs, toMs, signal = new AbortController().signal) {
|
|
135
|
-
|
|
136
|
-
const { fetchAudioSpanningTime: fetchAudioSpan } = await import("./EFMedia/shared/AudioSpanUtils.js");
|
|
137
|
-
return fetchAudioSpan(this, fromMs, toMs, signal);
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* Get the HTML audio element for ContextMixin integration
|
|
141
|
-
*/
|
|
142
|
-
get audioElement() {
|
|
143
|
-
return this.mediaSourceService.getAudioElement();
|
|
129
|
+
return fetchAudioSpanningTime(this, fromMs, toMs, signal);
|
|
144
130
|
}
|
|
145
131
|
/**
|
|
146
132
|
* Check if an audio segment is cached in the unified buffer system
|
|
@@ -158,18 +144,6 @@ var EFMedia = class extends EFTargetable(EFSourceMixin(EFTemporal(FetchMixin(Lit
|
|
|
158
144
|
if (!bufferState) return /* @__PURE__ */ new Set();
|
|
159
145
|
return new Set(segmentIds.filter((id) => bufferState.cachedSegments.has(id)));
|
|
160
146
|
}
|
|
161
|
-
/**
|
|
162
|
-
* Get MediaElementAudioSourceNode for ContextMixin integration
|
|
163
|
-
* Uses AudioElementFactory for proper caching and lifecycle management
|
|
164
|
-
*/
|
|
165
|
-
async getMediaElementSource(audioContext) {
|
|
166
|
-
return this.audioElementFactory.createMediaElementSource(audioContext, this.mediaSourceService);
|
|
167
|
-
}
|
|
168
|
-
disconnectedCallback() {
|
|
169
|
-
super.disconnectedCallback?.();
|
|
170
|
-
this.mediaSourceService.cleanup();
|
|
171
|
-
this.audioElementFactory.clearCache();
|
|
172
|
-
}
|
|
173
147
|
};
|
|
174
148
|
_decorate([property({ type: Number })], EFMedia.prototype, "currentTimeMs", void 0);
|
|
175
149
|
_decorate([property({
|
|
@@ -301,23 +301,18 @@ let EFTimegroup = class EFTimegroup$1 extends EFTemporal(LitElement) {
|
|
|
301
301
|
* Usage: timegroup.testPlayAudio(0, 5000) // Play first 5 seconds
|
|
302
302
|
*/
|
|
303
303
|
async testPlayAudio(fromMs, toMs) {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
});
|
|
317
|
-
} catch (error) {
|
|
318
|
-
console.error("🎵 [TEST_PLAY_AUDIO] Error:", error);
|
|
319
|
-
throw error;
|
|
320
|
-
}
|
|
304
|
+
const renderedBuffer = await this.renderAudio(fromMs, toMs);
|
|
305
|
+
const playbackContext = new AudioContext();
|
|
306
|
+
const bufferSource = playbackContext.createBufferSource();
|
|
307
|
+
bufferSource.buffer = renderedBuffer;
|
|
308
|
+
bufferSource.connect(playbackContext.destination);
|
|
309
|
+
bufferSource.start(0);
|
|
310
|
+
return new Promise((resolve) => {
|
|
311
|
+
bufferSource.onended = () => {
|
|
312
|
+
playbackContext.close();
|
|
313
|
+
resolve();
|
|
314
|
+
};
|
|
315
|
+
});
|
|
321
316
|
}
|
|
322
317
|
async loadMd5Sums() {
|
|
323
318
|
const efElements = this.efElements;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Task } from '@lit/task';
|
|
2
2
|
import { PropertyValueMap } from 'lit';
|
|
3
|
-
import { CacheStats, ScrubTrackManager } from '../ScrubTrackManager.js';
|
|
4
3
|
import { EFMedia } from './EFMedia.js';
|
|
5
4
|
declare global {
|
|
6
5
|
var EF_FRAMEGEN: import("../EF_FRAMEGEN.js").EFFramegen;
|
|
@@ -36,10 +35,6 @@ export declare class EFVideo extends EFVideo_base {
|
|
|
36
35
|
videoInputTask: import('./EFMedia/shared/MediaTaskUtils.ts').InputTask;
|
|
37
36
|
videoSeekTask: Task<readonly [number, import('./EFMedia/BufferedSeekingInput.ts').BufferedSeekingInput | undefined], import('mediabunny').VideoSample | undefined>;
|
|
38
37
|
videoBufferTask: Task<readonly [number], import('./EFMedia/videoTasks/makeVideoBufferTask.ts').VideoBufferState>;
|
|
39
|
-
/**
|
|
40
|
-
* Scrub track manager for fast timeline navigation
|
|
41
|
-
*/
|
|
42
|
-
scrubTrackManager?: ScrubTrackManager;
|
|
43
38
|
/**
|
|
44
39
|
* Delayed loading state manager for user feedback
|
|
45
40
|
*/
|
|
@@ -84,10 +79,6 @@ export declare class EFVideo extends EFVideo_base {
|
|
|
84
79
|
* Check if EF_FRAMEGEN has explicitly started frame rendering (not just initialization)
|
|
85
80
|
*/
|
|
86
81
|
private isFrameRenderingActive;
|
|
87
|
-
/**
|
|
88
|
-
* Get scrub track performance statistics
|
|
89
|
-
*/
|
|
90
|
-
getScrubTrackStats(): CacheStats | null;
|
|
91
82
|
/**
|
|
92
83
|
* Effective mode - always returns "asset" for EFVideo
|
|
93
84
|
*/
|