@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
package/src/elements/EFVideo.ts
CHANGED
|
@@ -6,7 +6,6 @@ import { createRef, ref } from "lit/directives/ref.js";
|
|
|
6
6
|
|
|
7
7
|
import { DelayedLoadingState } from "../DelayedLoadingState.js";
|
|
8
8
|
import { TWMixin } from "../gui/TWMixin.js";
|
|
9
|
-
import type { CacheStats, ScrubTrackManager } from "../ScrubTrackManager.js";
|
|
10
9
|
import { makeVideoBufferTask } from "./EFMedia/videoTasks/makeVideoBufferTask.ts";
|
|
11
10
|
import { makeVideoInitSegmentFetchTask } from "./EFMedia/videoTasks/makeVideoInitSegmentFetchTask.ts";
|
|
12
11
|
import { makeVideoInputTask } from "./EFMedia/videoTasks/makeVideoInputTask.ts";
|
|
@@ -135,11 +134,6 @@ export class EFVideo extends TWMixin(EFMedia) {
|
|
|
135
134
|
videoSeekTask = makeVideoSeekTask(this);
|
|
136
135
|
videoBufferTask = makeVideoBufferTask(this);
|
|
137
136
|
|
|
138
|
-
/**
|
|
139
|
-
* Scrub track manager for fast timeline navigation
|
|
140
|
-
*/
|
|
141
|
-
scrubTrackManager?: ScrubTrackManager;
|
|
142
|
-
|
|
143
137
|
/**
|
|
144
138
|
* Delayed loading state manager for user feedback
|
|
145
139
|
*/
|
|
@@ -392,13 +386,6 @@ export class EFVideo extends TWMixin(EFMedia) {
|
|
|
392
386
|
return currentTime >= renderStartTime;
|
|
393
387
|
}
|
|
394
388
|
|
|
395
|
-
/**
|
|
396
|
-
* Get scrub track performance statistics
|
|
397
|
-
*/
|
|
398
|
-
getScrubTrackStats(): CacheStats | null {
|
|
399
|
-
return this.scrubTrackManager?.getCacheStats() || null;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
389
|
// Getter properties for backward compatibility with tests
|
|
403
390
|
/**
|
|
404
391
|
* Effective mode - always returns "asset" for EFVideo
|
|
@@ -469,9 +456,6 @@ export class EFVideo extends TWMixin(EFMedia) {
|
|
|
469
456
|
disconnectedCallback(): void {
|
|
470
457
|
super.disconnectedCallback();
|
|
471
458
|
|
|
472
|
-
// Clean up scrub track manager
|
|
473
|
-
this.scrubTrackManager?.cleanup();
|
|
474
|
-
|
|
475
459
|
// Clean up delayed loading state
|
|
476
460
|
this.delayedLoadingState.clearAllLoading();
|
|
477
461
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AudioSample, VideoSample } from "mediabunny";
|
|
2
|
+
import { roundToMilliseconds } from "./EFMedia/shared/PrecisionUtils";
|
|
2
3
|
export type MediaSample = VideoSample | AudioSample;
|
|
3
4
|
|
|
4
5
|
// Generic sample buffer that works with both VideoSample and AudioSample
|
|
@@ -57,22 +58,19 @@ export class SampleBuffer {
|
|
|
57
58
|
|
|
58
59
|
if (currentBuffer.length === 0) return undefined;
|
|
59
60
|
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
const roundToMicroseconds = (timeMs: number) =>
|
|
63
|
-
Math.round(timeMs * 1000) / 1000;
|
|
64
|
-
const targetTimeMs = roundToMicroseconds(desiredSeekTimeMs);
|
|
61
|
+
// Use consistent precision handling across the entire pipeline
|
|
62
|
+
const targetTimeMs = roundToMilliseconds(desiredSeekTimeMs);
|
|
65
63
|
|
|
66
64
|
// Find the sample that contains the target time
|
|
67
65
|
for (const sample of currentBuffer) {
|
|
68
|
-
const sampleStartMs =
|
|
69
|
-
const sampleDurationMs =
|
|
66
|
+
const sampleStartMs = roundToMilliseconds((sample.timestamp || 0) * 1000);
|
|
67
|
+
const sampleDurationMs = roundToMilliseconds(
|
|
70
68
|
(sample.duration || 0) * 1000,
|
|
71
69
|
);
|
|
72
|
-
const sampleEndMs = sampleStartMs + sampleDurationMs;
|
|
70
|
+
const sampleEndMs = roundToMilliseconds(sampleStartMs + sampleDurationMs);
|
|
73
71
|
|
|
74
|
-
// Check if the desired time falls within this sample's time span [start, end
|
|
75
|
-
if (targetTimeMs >= sampleStartMs && targetTimeMs
|
|
72
|
+
// Check if the desired time falls within this sample's time span [start, end], inclusive of end
|
|
73
|
+
if (targetTimeMs >= sampleStartMs && targetTimeMs <= sampleEndMs) {
|
|
76
74
|
return sample;
|
|
77
75
|
}
|
|
78
76
|
}
|
package/src/gui/ContextMixin.ts
CHANGED
|
@@ -11,8 +11,6 @@ import { fetchContext } from "./fetchContext.js";
|
|
|
11
11
|
import { type FocusContext, focusContext } from "./focusContext.js";
|
|
12
12
|
import { focusedElementContext } from "./focusedElementContext.js";
|
|
13
13
|
import { loopContext, playingContext } from "./playingContext.js";
|
|
14
|
-
import { ElementConnectionManager } from "./services/ElementConnectionManager.js";
|
|
15
|
-
import { PlaybackController } from "./services/PlaybackController.js";
|
|
16
14
|
|
|
17
15
|
export const targetTimegroupContext = createContext<EFTimegroup | null>(
|
|
18
16
|
"target-timegroup",
|
|
@@ -136,6 +134,9 @@ export function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {
|
|
|
136
134
|
@state()
|
|
137
135
|
currentTimeMs = 0;
|
|
138
136
|
|
|
137
|
+
#FPS = 30;
|
|
138
|
+
#MS_PER_FRAME = 1000 / this.#FPS;
|
|
139
|
+
|
|
139
140
|
#timegroupObserver = new MutationObserver((mutations) => {
|
|
140
141
|
for (const mutation of mutations) {
|
|
141
142
|
if (mutation.type === "childList") {
|
|
@@ -159,9 +160,6 @@ export function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {
|
|
|
159
160
|
attributes: true,
|
|
160
161
|
});
|
|
161
162
|
|
|
162
|
-
// Preferrably we would use a resizeObserver, but it is difficult to get the first resize
|
|
163
|
-
// timed correctly. So we use requestAnimationFrame as a stop-gap.
|
|
164
|
-
// requestAnimationFrame(this.setStageScale);
|
|
165
163
|
if (this.playing) {
|
|
166
164
|
this.startPlayback();
|
|
167
165
|
}
|
|
@@ -208,37 +206,117 @@ export function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {
|
|
|
208
206
|
this.playing = false;
|
|
209
207
|
}
|
|
210
208
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
this.
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
}
|
|
226
|
-
}
|
|
209
|
+
#playbackAudioContext: AudioContext | null = null;
|
|
210
|
+
#playbackAnimationFrameRequest: number | null = null;
|
|
211
|
+
#AUDIO_PLAYBACK_SLICE_MS = ((47 * 1024) / 48000) * 1000; // AAC-aligned: ~1002.67ms
|
|
212
|
+
|
|
213
|
+
#syncPlayheadToAudioContext(target: EFTimegroup, startMs: number) {
|
|
214
|
+
const rawTimeMs =
|
|
215
|
+
startMs + (this.#playbackAudioContext?.currentTime ?? 0) * 1000;
|
|
216
|
+
const nextTimeMs =
|
|
217
|
+
Math.round(rawTimeMs / this.#MS_PER_FRAME) * this.#MS_PER_FRAME;
|
|
218
|
+
if (nextTimeMs !== this.currentTimeMs) {
|
|
219
|
+
this.currentTimeMs = nextTimeMs;
|
|
220
|
+
}
|
|
221
|
+
this.#playbackAnimationFrameRequest = requestAnimationFrame(() => {
|
|
222
|
+
this.#syncPlayheadToAudioContext(target, startMs);
|
|
223
|
+
});
|
|
224
|
+
}
|
|
227
225
|
|
|
228
226
|
private async stopPlayback() {
|
|
229
|
-
|
|
227
|
+
if (this.#playbackAudioContext) {
|
|
228
|
+
if (this.#playbackAudioContext.state !== "closed") {
|
|
229
|
+
await this.#playbackAudioContext.close();
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (this.#playbackAnimationFrameRequest) {
|
|
233
|
+
cancelAnimationFrame(this.#playbackAnimationFrameRequest);
|
|
234
|
+
}
|
|
235
|
+
this.#playbackAudioContext = null;
|
|
236
|
+
this.#playbackAnimationFrameRequest = null;
|
|
230
237
|
}
|
|
231
238
|
|
|
232
239
|
private async startPlayback() {
|
|
240
|
+
await this.stopPlayback();
|
|
233
241
|
const timegroup = this.targetTimegroup;
|
|
234
242
|
if (!timegroup) {
|
|
235
243
|
return;
|
|
236
244
|
}
|
|
237
245
|
|
|
238
|
-
await
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
246
|
+
await timegroup.waitForMediaDurations();
|
|
247
|
+
|
|
248
|
+
let currentMs = timegroup.currentTimeMs;
|
|
249
|
+
const fromMs = currentMs;
|
|
250
|
+
const toMs = timegroup.endTimeMs;
|
|
251
|
+
|
|
252
|
+
if (fromMs >= toMs) {
|
|
253
|
+
this.pause();
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
let bufferCount = 0;
|
|
258
|
+
this.#playbackAudioContext = new AudioContext({
|
|
259
|
+
latencyHint: "playback",
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
if (this.#playbackAnimationFrameRequest) {
|
|
263
|
+
cancelAnimationFrame(this.#playbackAnimationFrameRequest);
|
|
264
|
+
}
|
|
265
|
+
this.#syncPlayheadToAudioContext(timegroup, currentMs);
|
|
266
|
+
const playbackContext = this.#playbackAudioContext;
|
|
267
|
+
if (playbackContext.state === "suspended") {
|
|
268
|
+
console.warn(
|
|
269
|
+
"AudioContext is suspended, media playback will not work until user has interacted with page.",
|
|
270
|
+
);
|
|
271
|
+
this.playing = false;
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
await playbackContext.suspend();
|
|
275
|
+
|
|
276
|
+
const fillBuffer = async () => {
|
|
277
|
+
if (bufferCount > 2) {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
const canFillBuffer = await queueBufferSource();
|
|
281
|
+
if (canFillBuffer) {
|
|
282
|
+
fillBuffer();
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
const queueBufferSource = async () => {
|
|
287
|
+
if (currentMs >= toMs) {
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
const startMs = currentMs;
|
|
291
|
+
const endMs = currentMs + this.#AUDIO_PLAYBACK_SLICE_MS;
|
|
292
|
+
currentMs += this.#AUDIO_PLAYBACK_SLICE_MS;
|
|
293
|
+
const audioBuffer = await timegroup.renderAudio(startMs, endMs);
|
|
294
|
+
bufferCount++;
|
|
295
|
+
const source = playbackContext.createBufferSource();
|
|
296
|
+
source.buffer = audioBuffer;
|
|
297
|
+
source.connect(playbackContext.destination);
|
|
298
|
+
source.start((startMs - fromMs) / 1000);
|
|
299
|
+
source.onended = () => {
|
|
300
|
+
bufferCount--;
|
|
301
|
+
if (endMs >= toMs) {
|
|
302
|
+
this.pause();
|
|
303
|
+
if (this.loop) {
|
|
304
|
+
this.updateComplete.then(() => {
|
|
305
|
+
this.currentTimeMs = 0;
|
|
306
|
+
this.updateComplete.then(() => {
|
|
307
|
+
this.play();
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
} else {
|
|
312
|
+
fillBuffer();
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
return true;
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
await fillBuffer();
|
|
319
|
+
await playbackContext.resume();
|
|
242
320
|
}
|
|
243
321
|
}
|
|
244
322
|
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* Core types and interfaces for JIT Transcoding System
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import type { MediaRendition } from "../../elements/EFMedia/shared/MediaTaskUtils";
|
|
6
|
+
|
|
5
7
|
export interface QualityPreset {
|
|
6
8
|
name: string;
|
|
7
9
|
width: number;
|
|
@@ -73,6 +75,8 @@ export interface ManifestVideoRendition {
|
|
|
73
75
|
segmentDuration: number;
|
|
74
76
|
/** Duration of each segment in milliseconds */
|
|
75
77
|
segmentDurationMs: number;
|
|
78
|
+
/** Actual segment durations array (overrides fixed segmentDurationMs if provided) */
|
|
79
|
+
segmentDurationsMs?: number[];
|
|
76
80
|
/** Video width in pixels */
|
|
77
81
|
width: number;
|
|
78
82
|
/** Video height in pixels */
|
|
@@ -100,6 +104,8 @@ export interface ManifestAudioRendition {
|
|
|
100
104
|
segmentDuration: number;
|
|
101
105
|
/** Duration of each segment in milliseconds */
|
|
102
106
|
segmentDurationMs: number;
|
|
107
|
+
/** Actual segment durations array (overrides fixed segmentDurationMs if provided) */
|
|
108
|
+
segmentDurationsMs?: number[];
|
|
103
109
|
/** Number of audio channels */
|
|
104
110
|
channels: number;
|
|
105
111
|
/** Sample rate in Hz */
|
|
@@ -183,12 +189,15 @@ export interface AudioRendition {
|
|
|
183
189
|
trackId: number | undefined;
|
|
184
190
|
src: string;
|
|
185
191
|
segmentDurationMs?: number;
|
|
192
|
+
segmentDurationsMs?: number[];
|
|
193
|
+
startTimeOffsetMs?: number;
|
|
186
194
|
}
|
|
187
195
|
export interface VideoRendition {
|
|
188
196
|
id?: RenditionId;
|
|
189
197
|
trackId: number | undefined;
|
|
190
198
|
src: string;
|
|
191
199
|
segmentDurationMs?: number;
|
|
200
|
+
segmentDurationsMs?: number[];
|
|
192
201
|
startTimeOffsetMs?: number;
|
|
193
202
|
}
|
|
194
203
|
export interface MediaEngine {
|
|
@@ -215,12 +224,7 @@ export interface MediaEngine {
|
|
|
215
224
|
) => Promise<ArrayBuffer>;
|
|
216
225
|
computeSegmentId: (
|
|
217
226
|
desiredSeekTimeMs: number,
|
|
218
|
-
rendition:
|
|
219
|
-
id?: RenditionId;
|
|
220
|
-
trackId: number | undefined;
|
|
221
|
-
src: string;
|
|
222
|
-
segmentDurationMs?: number;
|
|
223
|
-
},
|
|
227
|
+
rendition: MediaRendition,
|
|
224
228
|
) => number | undefined;
|
|
225
229
|
|
|
226
230
|
/**
|
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
"cache-control": "public, max-age=3600",
|
|
9
9
|
"content-length": "32957",
|
|
10
10
|
"content-type": "video/iso.segment",
|
|
11
|
-
"date": "
|
|
11
|
+
"date": "Fri, 08 Aug 2025 03:57:07 GMT",
|
|
12
12
|
"x-cache": "HIT",
|
|
13
13
|
"x-powered-by": "Express",
|
|
14
|
-
"x-total-server-time-ms": "
|
|
14
|
+
"x-total-server-time-ms": "0",
|
|
15
15
|
"x-transcode-time-ms": "0"
|
|
16
16
|
},
|
|
17
17
|
"url": "/api/v1/transcode/audio/1.m4s?url=http%3A%2F%2Fweb%3A3000%2Fhead-moov-480p.mp4",
|
|
18
18
|
"method": "GET",
|
|
19
19
|
"range": null,
|
|
20
|
-
"timestamp": "2025-08-
|
|
20
|
+
"timestamp": "2025-08-08T03:57:07.247Z"
|
|
21
21
|
}
|
|
Binary file
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"statusCode": 200,
|
|
3
|
+
"headers": {
|
|
4
|
+
"access-control-allow-headers": "Origin, X-Requested-With, Content-Type, Accept, Authorization, Range",
|
|
5
|
+
"access-control-allow-methods": "GET, POST, PUT, DELETE, OPTIONS",
|
|
6
|
+
"access-control-allow-origin": "*",
|
|
7
|
+
"access-control-expose-headers": "Content-Length, Content-Range, X-Cache, X-Actual-Start-Time, X-Actual-Duration, X-Transcode-Time-Ms, X-Total-Server-Time-Ms",
|
|
8
|
+
"cache-control": "public, max-age=3600",
|
|
9
|
+
"connection": "close",
|
|
10
|
+
"content-length": "33661",
|
|
11
|
+
"content-type": "video/mp4",
|
|
12
|
+
"date": "Tue, 05 Aug 2025 17:32:32 GMT",
|
|
13
|
+
"x-cache": "HIT",
|
|
14
|
+
"x-powered-by": "Express",
|
|
15
|
+
"x-total-server-time-ms": "3",
|
|
16
|
+
"x-transcode-time-ms": "0"
|
|
17
|
+
},
|
|
18
|
+
"url": "/api/v1/transcode/audio/1.mp4?url=http%3A%2F%2Fweb%3A3000%2Fhead-moov-480p.mp4",
|
|
19
|
+
"method": "GET",
|
|
20
|
+
"range": "bytes=0-",
|
|
21
|
+
"timestamp": "2025-08-05T17:32:32.538Z"
|
|
22
|
+
}
|
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
"cache-control": "public, max-age=3600",
|
|
9
9
|
"content-length": "32502",
|
|
10
10
|
"content-type": "video/iso.segment",
|
|
11
|
-
"date": "
|
|
11
|
+
"date": "Fri, 08 Aug 2025 03:57:07 GMT",
|
|
12
12
|
"x-cache": "HIT",
|
|
13
13
|
"x-powered-by": "Express",
|
|
14
|
-
"x-total-server-time-ms": "
|
|
14
|
+
"x-total-server-time-ms": "0",
|
|
15
15
|
"x-transcode-time-ms": "0"
|
|
16
16
|
},
|
|
17
17
|
"url": "/api/v1/transcode/audio/2.m4s?url=http%3A%2F%2Fweb%3A3000%2Fhead-moov-480p.mp4",
|
|
18
18
|
"method": "GET",
|
|
19
19
|
"range": null,
|
|
20
|
-
"timestamp": "2025-08-
|
|
20
|
+
"timestamp": "2025-08-08T03:57:07.247Z"
|
|
21
21
|
}
|
|
Binary file
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"statusCode": 200,
|
|
3
|
+
"headers": {
|
|
4
|
+
"access-control-allow-headers": "Origin, X-Requested-With, Content-Type, Accept, Authorization, Range",
|
|
5
|
+
"access-control-allow-methods": "GET, POST, PUT, DELETE, OPTIONS",
|
|
6
|
+
"access-control-allow-origin": "*",
|
|
7
|
+
"access-control-expose-headers": "Content-Length, Content-Range, X-Cache, X-Actual-Start-Time, X-Actual-Duration, X-Transcode-Time-Ms, X-Total-Server-Time-Ms",
|
|
8
|
+
"cache-control": "public, max-age=3600",
|
|
9
|
+
"connection": "close",
|
|
10
|
+
"content-length": "33206",
|
|
11
|
+
"content-type": "video/mp4",
|
|
12
|
+
"date": "Tue, 05 Aug 2025 17:32:40 GMT",
|
|
13
|
+
"x-cache": "MISS",
|
|
14
|
+
"x-powered-by": "Express",
|
|
15
|
+
"x-total-server-time-ms": "215",
|
|
16
|
+
"x-transcode-time-ms": "209"
|
|
17
|
+
},
|
|
18
|
+
"url": "/api/v1/transcode/audio/2.mp4?url=http%3A%2F%2Fweb%3A3000%2Fhead-moov-480p.mp4",
|
|
19
|
+
"method": "GET",
|
|
20
|
+
"range": "bytes=0-",
|
|
21
|
+
"timestamp": "2025-08-05T17:32:40.581Z"
|
|
22
|
+
}
|
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
"cache-control": "public, max-age=3600",
|
|
9
9
|
"content-length": "32196",
|
|
10
10
|
"content-type": "video/iso.segment",
|
|
11
|
-
"date": "
|
|
11
|
+
"date": "Fri, 08 Aug 2025 03:57:07 GMT",
|
|
12
12
|
"x-cache": "HIT",
|
|
13
13
|
"x-powered-by": "Express",
|
|
14
|
-
"x-total-server-time-ms": "
|
|
14
|
+
"x-total-server-time-ms": "0",
|
|
15
15
|
"x-transcode-time-ms": "0"
|
|
16
16
|
},
|
|
17
17
|
"url": "/api/v1/transcode/audio/3.m4s?url=http%3A%2F%2Fweb%3A3000%2Fhead-moov-480p.mp4",
|
|
18
18
|
"method": "GET",
|
|
19
19
|
"range": null,
|
|
20
|
-
"timestamp": "2025-08-
|
|
20
|
+
"timestamp": "2025-08-08T03:57:07.271Z"
|
|
21
21
|
}
|
|
Binary file
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"statusCode": 200,
|
|
3
|
+
"headers": {
|
|
4
|
+
"access-control-allow-headers": "Origin, X-Requested-With, Content-Type, Accept, Authorization, Range",
|
|
5
|
+
"access-control-allow-methods": "GET, POST, PUT, DELETE, OPTIONS",
|
|
6
|
+
"access-control-allow-origin": "*",
|
|
7
|
+
"access-control-expose-headers": "Content-Length, Content-Range, X-Cache, X-Actual-Start-Time, X-Actual-Duration, X-Transcode-Time-Ms, X-Total-Server-Time-Ms",
|
|
8
|
+
"cache-control": "public, max-age=3600",
|
|
9
|
+
"connection": "close",
|
|
10
|
+
"content-length": "32900",
|
|
11
|
+
"content-type": "video/mp4",
|
|
12
|
+
"date": "Tue, 05 Aug 2025 18:58:23 GMT",
|
|
13
|
+
"x-cache": "MISS",
|
|
14
|
+
"x-powered-by": "Express",
|
|
15
|
+
"x-total-server-time-ms": "280",
|
|
16
|
+
"x-transcode-time-ms": "265"
|
|
17
|
+
},
|
|
18
|
+
"url": "/api/v1/transcode/audio/3.mp4?url=http%3A%2F%2Fweb%3A3000%2Fhead-moov-480p.mp4",
|
|
19
|
+
"method": "GET",
|
|
20
|
+
"range": "bytes=0-",
|
|
21
|
+
"timestamp": "2025-08-05T18:58:23.587Z"
|
|
22
|
+
}
|
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
"cache-control": "public, max-age=3600",
|
|
9
9
|
"content-length": "32570",
|
|
10
10
|
"content-type": "video/iso.segment",
|
|
11
|
-
"date": "
|
|
11
|
+
"date": "Fri, 08 Aug 2025 03:57:07 GMT",
|
|
12
12
|
"x-cache": "HIT",
|
|
13
13
|
"x-powered-by": "Express",
|
|
14
|
-
"x-total-server-time-ms": "
|
|
14
|
+
"x-total-server-time-ms": "0",
|
|
15
15
|
"x-transcode-time-ms": "0"
|
|
16
16
|
},
|
|
17
17
|
"url": "/api/v1/transcode/audio/4.m4s?url=http%3A%2F%2Fweb%3A3000%2Fhead-moov-480p.mp4",
|
|
18
18
|
"method": "GET",
|
|
19
19
|
"range": null,
|
|
20
|
-
"timestamp": "2025-08-
|
|
20
|
+
"timestamp": "2025-08-08T03:57:07.247Z"
|
|
21
21
|
}
|
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
"cache-control": "public, max-age=3600",
|
|
9
9
|
"content-length": "728",
|
|
10
10
|
"content-type": "video/iso.segment",
|
|
11
|
-
"date": "
|
|
11
|
+
"date": "Fri, 08 Aug 2025 03:57:07 GMT",
|
|
12
12
|
"x-cache": "HIT",
|
|
13
13
|
"x-powered-by": "Express",
|
|
14
|
-
"x-total-server-time-ms": "
|
|
14
|
+
"x-total-server-time-ms": "0",
|
|
15
15
|
"x-transcode-time-ms": "0"
|
|
16
16
|
},
|
|
17
17
|
"url": "/api/v1/transcode/audio/init.m4s?url=http%3A%2F%2Fweb%3A3000%2Fhead-moov-480p.mp4",
|
|
18
18
|
"method": "GET",
|
|
19
19
|
"range": null,
|
|
20
|
-
"timestamp": "2025-08-
|
|
20
|
+
"timestamp": "2025-08-08T03:57:07.205Z"
|
|
21
21
|
}
|
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
"cache-control": "public, max-age=3600",
|
|
9
9
|
"content-length": "827216",
|
|
10
10
|
"content-type": "video/iso.segment",
|
|
11
|
-
"date": "
|
|
11
|
+
"date": "Fri, 08 Aug 2025 03:57:07 GMT",
|
|
12
12
|
"x-cache": "HIT",
|
|
13
13
|
"x-powered-by": "Express",
|
|
14
|
-
"x-total-server-time-ms": "
|
|
14
|
+
"x-total-server-time-ms": "0",
|
|
15
15
|
"x-transcode-time-ms": "0"
|
|
16
16
|
},
|
|
17
17
|
"url": "/api/v1/transcode/high/1.m4s?url=http%3A%2F%2Fweb%3A3000%2Fhead-moov-480p.mp4",
|
|
18
18
|
"method": "GET",
|
|
19
19
|
"range": null,
|
|
20
|
-
"timestamp": "2025-08-
|
|
20
|
+
"timestamp": "2025-08-08T03:57:07.256Z"
|
|
21
21
|
}
|
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
"cache-control": "public, max-age=3600",
|
|
9
9
|
"content-length": "905893",
|
|
10
10
|
"content-type": "video/iso.segment",
|
|
11
|
-
"date": "
|
|
11
|
+
"date": "Fri, 08 Aug 2025 03:57:07 GMT",
|
|
12
12
|
"x-cache": "HIT",
|
|
13
13
|
"x-powered-by": "Express",
|
|
14
|
-
"x-total-server-time-ms": "
|
|
14
|
+
"x-total-server-time-ms": "1",
|
|
15
15
|
"x-transcode-time-ms": "0"
|
|
16
16
|
},
|
|
17
17
|
"url": "/api/v1/transcode/high/2.m4s?url=http%3A%2F%2Fweb%3A3000%2Fhead-moov-480p.mp4",
|
|
18
18
|
"method": "GET",
|
|
19
19
|
"range": null,
|
|
20
|
-
"timestamp": "2025-08-
|
|
20
|
+
"timestamp": "2025-08-08T03:57:07.697Z"
|
|
21
21
|
}
|
|
Binary file
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"statusCode": 200,
|
|
3
|
+
"headers": {
|
|
4
|
+
"access-control-allow-headers": "Origin, X-Requested-With, Content-Type, Accept, Authorization, Range",
|
|
5
|
+
"access-control-allow-methods": "GET, POST, PUT, DELETE, OPTIONS",
|
|
6
|
+
"access-control-allow-origin": "*",
|
|
7
|
+
"access-control-expose-headers": "Content-Length, Content-Range, X-Cache, X-Actual-Start-Time, X-Actual-Duration, X-Transcode-Time-Ms, X-Total-Server-Time-Ms",
|
|
8
|
+
"cache-control": "public, max-age=3600",
|
|
9
|
+
"content-length": "911925",
|
|
10
|
+
"content-type": "video/iso.segment",
|
|
11
|
+
"date": "Fri, 08 Aug 2025 03:57:07 GMT",
|
|
12
|
+
"x-cache": "HIT",
|
|
13
|
+
"x-powered-by": "Express",
|
|
14
|
+
"x-total-server-time-ms": "0",
|
|
15
|
+
"x-transcode-time-ms": "0"
|
|
16
|
+
},
|
|
17
|
+
"url": "/api/v1/transcode/high/4.m4s?url=http%3A%2F%2Fweb%3A3000%2Fhead-moov-480p.mp4",
|
|
18
|
+
"method": "GET",
|
|
19
|
+
"range": null,
|
|
20
|
+
"timestamp": "2025-08-08T03:57:07.247Z"
|
|
21
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"statusCode": 200,
|
|
3
|
+
"headers": {
|
|
4
|
+
"access-control-allow-headers": "Origin, X-Requested-With, Content-Type, Accept, Authorization, Range",
|
|
5
|
+
"access-control-allow-methods": "GET, POST, PUT, DELETE, OPTIONS",
|
|
6
|
+
"access-control-allow-origin": "*",
|
|
7
|
+
"access-control-expose-headers": "Content-Length, Content-Range, X-Cache, X-Actual-Start-Time, X-Actual-Duration, X-Transcode-Time-Ms, X-Total-Server-Time-Ms",
|
|
8
|
+
"cache-control": "public, max-age=3600",
|
|
9
|
+
"content-length": "829743",
|
|
10
|
+
"content-type": "video/iso.segment",
|
|
11
|
+
"date": "Tue, 05 Aug 2025 06:16:17 GMT",
|
|
12
|
+
"x-cache": "HIT",
|
|
13
|
+
"x-powered-by": "Express",
|
|
14
|
+
"x-total-server-time-ms": "16",
|
|
15
|
+
"x-transcode-time-ms": "0"
|
|
16
|
+
},
|
|
17
|
+
"url": "/api/v1/transcode/high/5.m4s?url=http%3A%2F%2Fweb%3A3000%2Fhead-moov-480p.mp4",
|
|
18
|
+
"method": "GET",
|
|
19
|
+
"range": null,
|
|
20
|
+
"timestamp": "2025-08-05T06:16:17.550Z"
|
|
21
|
+
}
|
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
"cache-control": "public, max-age=3600",
|
|
9
9
|
"content-length": "782",
|
|
10
10
|
"content-type": "video/iso.segment",
|
|
11
|
-
"date": "
|
|
11
|
+
"date": "Fri, 08 Aug 2025 03:57:07 GMT",
|
|
12
12
|
"x-cache": "HIT",
|
|
13
13
|
"x-powered-by": "Express",
|
|
14
|
-
"x-total-server-time-ms": "
|
|
14
|
+
"x-total-server-time-ms": "0",
|
|
15
15
|
"x-transcode-time-ms": "0"
|
|
16
16
|
},
|
|
17
17
|
"url": "/api/v1/transcode/high/init.m4s?url=http%3A%2F%2Fweb%3A3000%2Fhead-moov-480p.mp4",
|
|
18
18
|
"method": "GET",
|
|
19
19
|
"range": null,
|
|
20
|
-
"timestamp": "2025-08-
|
|
20
|
+
"timestamp": "2025-08-08T03:57:07.214Z"
|
|
21
21
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"1.0","type":"
|
|
1
|
+
{"version":"1.0","type":"com.editframe/manifest","sourceUrl":"http://web:3000/head-moov-480p.mp4","duration":10,"durationMs":10000,"baseUrl":"http://localhost:63315","videoRenditions":[{"id":"high","width":1920,"height":1080,"bitrate":5000000,"codec":"avc1.640029","container":"video/mp4","mimeType":"video/mp4; codecs=\"avc1.640029,mp4a.40.2\"","segmentDuration":2,"segmentDurationMs":2000,"segmentDurationsMs":[2000,2000,2000,2000,2000],"frameRate":30,"profile":"High","level":"4.1"},{"id":"medium","width":1280,"height":720,"bitrate":2500000,"codec":"avc1.640029","container":"video/mp4","mimeType":"video/mp4; codecs=\"avc1.640029,mp4a.40.2\"","segmentDuration":2,"segmentDurationMs":2000,"segmentDurationsMs":[2000,2000,2000,2000,2000],"frameRate":30,"profile":"High","level":"4.1"},{"id":"low","width":854,"height":480,"bitrate":1000000,"codec":"avc1.640029","container":"video/mp4","mimeType":"video/mp4; codecs=\"avc1.640029,mp4a.40.2\"","segmentDuration":2,"segmentDurationMs":2000,"segmentDurationsMs":[2000,2000,2000,2000,2000],"frameRate":30,"profile":"High","level":"4.1"},{"id":"scrub","width":320,"height":180,"bitrate":100000,"codec":"avc1.640029","container":"video/mp4","mimeType":"video/mp4; codecs=\"avc1.640029\"","segmentDuration":30,"segmentDurationMs":30000,"segmentDurationsMs":[30000],"frameRate":15,"profile":"High","level":"4.1"}],"audioRenditions":[{"id":"audio","channels":1,"sampleRate":48000,"bitrate":128000,"codec":"mp4a.40.2","container":"audio/mp4","mimeType":"audio/mp4; codecs=\"mp4a.40.2\"","segmentDuration":2,"segmentDurationMs":2000,"segmentDurationsMs":[2026.66,2005.33,1984,2005.33,2026.66],"language":"en"}],"endpoints":{"initSegment":"http://localhost:63315/api/v1/transcode/{rendition}/init.m4s?url=http%3A%2F%2Fweb%3A3000%2Fhead-moov-480p.mp4","mediaSegment":"http://localhost:63315/api/v1/transcode/{rendition}/{segmentId}.m4s?url=http%3A%2F%2Fweb%3A3000%2Fhead-moov-480p.mp4"},"jitInfo":{"parallelTranscodingSupported":true,"expectedTranscodeLatency":500,"segmentCount":5,"scrubSegmentCount":1}}
|
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
"access-control-allow-origin": "*",
|
|
7
7
|
"access-control-expose-headers": "Content-Length, Content-Range, X-Cache, X-Actual-Start-Time, X-Actual-Duration, X-Transcode-Time-Ms, X-Total-Server-Time-Ms",
|
|
8
8
|
"cache-control": "public, max-age=300",
|
|
9
|
-
"content-length": "
|
|
9
|
+
"content-length": "2045",
|
|
10
10
|
"content-type": "application/json; charset=utf-8",
|
|
11
|
-
"date": "
|
|
12
|
-
"etag": "W/\"
|
|
11
|
+
"date": "Thu, 07 Aug 2025 20:55:37 GMT",
|
|
12
|
+
"etag": "W/\"81b-wi6z588RhWTgs57jivyDs3lEkkA\"",
|
|
13
13
|
"x-powered-by": "Express"
|
|
14
14
|
},
|
|
15
15
|
"url": "/api/v1/transcode/manifest.json?url=http%3A%2F%2Fweb%3A3000%2Fhead-moov-480p.mp4",
|
|
16
16
|
"method": "GET",
|
|
17
17
|
"range": null,
|
|
18
|
-
"timestamp": "2025-08-
|
|
18
|
+
"timestamp": "2025-08-07T20:55:37.385Z"
|
|
19
19
|
}
|