diff --git a/dev/demux.html b/dev/demux.html
index ebe2944..d7a8a32 100644
--- a/dev/demux.html
+++ b/dev/demux.html
@@ -11,7 +11,6 @@
document.body.append(fileInput);
fileInput.addEventListener('change', async () => {
- /*
const file = fileInput.files[0];
const input = new Mediabunny.Input({
formats: Mediabunny.ALL_FORMATS,
@@ -19,11 +18,13 @@
});
const track = await input.getPrimaryAudioTrack();
+ const sink3 = new Mediabunny.EncodedPacketSink(track);
+ console.log((await sink3.getFirstPacket()).data.join(', '));
+ return;
console.log(await track.getDurationFromMetadata(), await track.computeDuration());
//console.log(await input.getDurationFromMetadata(), await input.computeDuration());
return;
- */
const manifest = new Mediabunny.Input({
entryPath: 'https://playertest.longtailvideo.com/adaptive/alt-audio-no-video/sintel/playlist.m3u8',
diff --git a/src/hls/hls-muxer.ts b/src/hls/hls-muxer.ts
index ec55751..796661e 100644
--- a/src/hls/hls-muxer.ts
+++ b/src/hls/hls-muxer.ts
@@ -1,6 +1,6 @@
import { MediaCodec, validateAudioChunkMetadata, validateVideoChunkMetadata } from '../codec';
-import { EncodedAudioPacketSource, EncodedVideoPacketSource, MediaSource } from '../media-source';
-import { assert, joinPaths, textEncoder, UNDETERMINED_LANGUAGE } from '../misc';
+import { EncodedAudioPacketSource, EncodedVideoPacketSource } from '../media-source';
+import { arrayArgmax, assert, findLastIndex, joinPaths, textEncoder, UNDETERMINED_LANGUAGE } from '../misc';
import { Muxer } from '../muxer';
import {
Output,
@@ -32,7 +32,6 @@ type HlsAudioTrackData = HlsTrackData & { info: { type: 'audio' } };
type Playlist = {
id: number;
- key: string;
path: string;
tracks: OutputTrack[];
segmentFormat: OutputFormat;
@@ -349,6 +348,10 @@ export class HlsMuxer extends Muxer {
};
const registerPlaylist = async (tracks: OutputTrack[]) => {
+ if (tracks.some(track => this.playlists.some(playlist => playlist.tracks.includes(track)))) {
+ throw new Error('Internal error: track is already registered in a playlist.'); // Should be unreachable
+ }
+
const id = this.playlists.length + 1;
const path = await this.getPlaylistPath({
n: id,
@@ -363,12 +366,10 @@ export class HlsMuxer extends Muxer {
);
}
- const key = tracks.map(x => x.id).join('-');
const format = deduceSegmentFormat(tracks);
const playlist: Playlist = {
id: this.playlists.length + 1,
- key,
path,
tracks,
segmentFormat: format,
@@ -393,8 +394,7 @@ export class HlsMuxer extends Muxer {
for (let i = 0; i < group.tracks.length; i++) {
const track = group.tracks[i]!;
- const key = track.id.toString();
- let playlist = this.playlists.find(x => x.key === key);
+ let playlist = this.playlists.find(x => x.tracks[0]!.id === track.id);
playlist ??= await registerPlaylist([track]);
this.playlistDeclarations.push({
@@ -407,8 +407,8 @@ export class HlsMuxer extends Muxer {
}
for (const variant of variantStreams) {
- const key = variant.tracks.map(x => x.id).join('-');
- let playlist = this.playlists.find(x => x.key === key);
+ // Since tracks can only be assigned to one playlist, the first track's ID acts as a "playlist key"
+ let playlist = this.playlists.find(x => x.tracks[0]!.id === variant.tracks[0]!.id);
playlist ??= await registerPlaylist(variant.tracks);
this.playlistDeclarations.push({
@@ -423,7 +423,7 @@ export class HlsMuxer extends Muxer {
}
async getMimeType(): Promise {
- throw new Error('TODO');
+ return 'application/vnd.apple.mpegurl';
}
private allTracksAreKnown(playlist: Playlist) {
@@ -590,85 +590,131 @@ export class HlsMuxer extends Muxer {
return;
}
+ const trackDatas = this.trackDatas.filter(x => playlist.tracks.includes(x.track));
+ const videoTrack = trackDatas.find(x => x.info.type === 'video') as HlsVideoTrackData | undefined;
+ const audioTrack = trackDatas.find(x => x.info.type === 'audio') as HlsAudioTrackData | undefined;
+
+ const videoClosed = videoTrack
+ ? videoTrack.track.source._closed || isFinalCall
+ : true;
+ const audioClosed = audioTrack
+ ? audioTrack.track.source._closed || isFinalCall
+ : true;
+
+ // Loop in case we can finalize multiple segments
while (true) {
const currentSegmentEndTimestamp = playlist.currentSegmentStartTimestamp + this.targetSegmentDuration;
- let videoKeyEndTimestamp: number | null = null;
- let videoEndTimestamp: number | null = null;
- let audioEndTimestamp: number | null = null;
- let flushAllVideo = false;
- let flushAllAudio = false;
+ // These store the index (exclusive) until when packets can be added to the next segment
+ let videoEndIndex = 0;
+ let audioEndIndex = 0;
- const trackDatas = this.trackDatas.filter(x => playlist.tracks.includes(x.track));
+ if (videoTrack && (!videoClosed || videoTrack.packets.length > 0)) {
+ const allBelow = videoTrack.packets.every(x => x.timestamp < currentSegmentEndTimestamp);
- for (const trackData of trackDatas) {
- for (let i = 0; i < trackData.packets.length; i++) {
- const packet = trackData.packets[i]!;
- const endTimestamp = packet.timestamp + packet.duration;
+ let bestKeyPacket: EncodedPacket | null = null;
+ let bestKeyPacketIndex: number | null = null;
- if (trackData.info.type === 'video') {
- videoEndTimestamp = Math.max(videoEndTimestamp ?? -Infinity, endTimestamp);
+ if (allBelow) {
+ if (!videoClosed) {
+ // Not enough data yet
+ return;
+ }
+ } else {
+ // Find the best key packet timestamp
+ for (let i = 0; i < videoTrack.packets.length; i++) {
+ const packet = videoTrack.packets[i]!;
- if (i > 0 && packet.type === 'key') {
- if (
- videoKeyEndTimestamp !== null
- && packet.timestamp > currentSegmentEndTimestamp
- ) {
- break;
- }
-
- videoKeyEndTimestamp = packet.timestamp;
- }
- } else if (trackData.info.type === 'audio') {
- if (
- audioEndTimestamp !== null
- && packet.timestamp > currentSegmentEndTimestamp
- ) {
+ if (bestKeyPacket !== null && packet.timestamp > currentSegmentEndTimestamp) {
break;
}
- audioEndTimestamp = packet.timestamp;
+ if (i > 0 && packet.type === 'key') {
+ bestKeyPacket = packet;
+ bestKeyPacketIndex = i;
+ }
+ }
+ }
- const endTimestamp = packet.timestamp + packet.duration;
- if (endTimestamp <= currentSegmentEndTimestamp) {
- audioEndTimestamp = Math.max(audioEndTimestamp ?? -Infinity, endTimestamp);
- if (i === trackData.packets.length - 1) {
- flushAllAudio = true;
+ if (bestKeyPacketIndex !== null) {
+ videoEndIndex = bestKeyPacketIndex;
+
+ if (audioTrack) {
+ // The audio track must go at least until the video key frame
+ const index = audioTrack.packets.findIndex(x => x.timestamp >= bestKeyPacket!.timestamp);
+ if (index !== -1) {
+ audioEndIndex = index;
+ } else {
+ if (audioClosed) {
+ audioEndIndex = audioTrack.packets.length;
+ } else {
+ return;
+ }
+ }
+ }
+ } else {
+ if (!videoClosed) {
+ return;
+ }
+
+ // Include the entire rest of the video (since there's no key frame to split it on)
+ videoEndIndex = videoTrack.packets.length;
+ const maxIndex = arrayArgmax(videoTrack.packets, x => x.timestamp);
+ const maxPacket = videoTrack.packets[maxIndex];
+ assert(maxPacket);
+
+ if (audioTrack) {
+ if (maxPacket.timestamp < currentSegmentEndTimestamp) {
+ // The audio must go until at least the start of the next segment
+ const index = audioTrack.packets.findIndex(x => x.timestamp >= currentSegmentEndTimestamp);
+ if (index !== -1) {
+ audioEndIndex = index;
+ } else {
+ if (audioClosed) {
+ audioEndIndex = audioTrack.packets.length;
+ } else {
+ return;
+ }
+ }
+ } else {
+ // The audio must go beyond the last video packet
+ const index = audioTrack.packets.findIndex(x => x.timestamp > maxPacket.timestamp);
+ if (index !== -1) {
+ audioEndIndex = index;
+ } else {
+ if (audioClosed) {
+ audioEndIndex = audioTrack.packets.length;
+ } else {
+ return;
+ }
}
}
}
}
- }
+ } else if (audioTrack && (!audioClosed || audioTrack.packets.length > 0)) {
+ const allBelow = audioTrack.packets.every(x => x.timestamp < currentSegmentEndTimestamp);
- let endTimestamp: number | null = null;
- if (videoKeyEndTimestamp !== null && videoEndTimestamp! > currentSegmentEndTimestamp) {
- endTimestamp = videoKeyEndTimestamp;
- } else {
- if (isFinalCall) {
- endTimestamp = videoEndTimestamp;
- flushAllVideo = true;
- }
-
- if (audioEndTimestamp !== null) {
- endTimestamp = Math.max(endTimestamp ?? -Infinity, audioEndTimestamp);
- }
- }
-
- if (endTimestamp === null) {
- return;
- } else {
- if (!isFinalCall && endTimestamp < currentSegmentEndTimestamp) {
- return;
- }
-
- for (const trackData of trackDatas) {
- const closed = trackData.track.source._closed || isFinalCall;
- if (!closed && !trackData.packets.some(x => x.timestamp >= endTimestamp)) {
+ if (allBelow) {
+ if (audioClosed) {
+ audioEndIndex = audioTrack.packets.length;
+ } else {
return;
}
+ } else {
+ const index = findLastIndex(audioTrack.packets, x => x.timestamp <= currentSegmentEndTimestamp);
+ if (index !== -1) {
+ audioEndIndex = index;
+ } else {
+ audioEndIndex = 1;
+ }
}
}
+ if (videoEndIndex === 0 && audioEndIndex === 0) {
+ return;
+ }
+
+ // We can finalize a new segment! Let's first get the path
const relativeSegmentPath = await this.getSegmentPath({
n: playlist.nextSegmentId,
format: playlist.segmentFormat,
@@ -705,67 +751,49 @@ export class HlsMuxer extends Muxer {
},
});
+ let maxEndTimestamp = -Infinity;
+
try {
- const packetSources = new Map();
+ let videoSource: EncodedVideoPacketSource | null = null;
+ let audioSource: EncodedAudioPacketSource | null = null;
- for (const trackData of trackDatas) {
- if (trackData.packets.length === 0) {
- continue;
- }
+ if (videoTrack) {
+ // Always add the track, no matter if it has packets or not (maintains underlying IDs)
+ videoSource = new EncodedVideoPacketSource((videoTrack.track as OutputVideoTrack).source._codec);
+ output.addVideoTrack(videoSource, videoTrack.track.metadata);
+ }
- if (trackData.info.type === 'video') {
- const outputTrack = trackData.track as OutputVideoTrack;
- const source = new EncodedVideoPacketSource(outputTrack.source._codec);
- output.addVideoTrack(source, outputTrack.metadata);
-
- packetSources.set(trackData, source);
- } else if (trackData.info.type === 'audio') {
- const outputTrack = trackData.track as OutputAudioTrack;
- const source = new EncodedAudioPacketSource(outputTrack.source._codec);
- output.addAudioTrack(source, outputTrack.metadata);
-
- packetSources.set(trackData, source);
- }
+ if (audioTrack) {
+ // Always add the track, no matter if it has packets or not (maintains underlying IDs)
+ audioSource = new EncodedAudioPacketSource((audioTrack.track as OutputAudioTrack).source._codec);
+ output.addAudioTrack(audioSource, audioTrack.track.metadata);
}
await output.start();
- for (const trackData of trackDatas) {
- const source = packetSources.get(trackData);
- if (!source) {
- continue;
+ // Add all of the packets
+
+ if (videoTrack) {
+ assert(videoSource);
+ const meta = { decoderConfig: videoTrack.info.decoderConfig };
+
+ for (let i = 0; i < videoEndIndex; i++) {
+ const packet = videoTrack.packets[i]!;
+
+ await videoSource.add(packet, meta);
+ maxEndTimestamp = Math.max(maxEndTimestamp, packet.timestamp + packet.duration);
}
+ }
- if (trackData.info.type === 'video') {
- const videoPacketSource = source as EncodedVideoPacketSource;
- const meta = { decoderConfig: trackData.info.decoderConfig };
+ if (audioTrack) {
+ assert(audioSource);
+ const meta = { decoderConfig: audioTrack.info.decoderConfig };
- while (trackData.packets.length > 0) {
- const nextPacket = trackData.packets[0]!;
- if (!flushAllVideo && nextPacket.timestamp >= endTimestamp) {
- break;
- }
+ for (let i = 0; i < audioEndIndex; i++) {
+ const packet = audioTrack.packets[i]!;
- trackData.packets.shift();
- await videoPacketSource.add(nextPacket, meta);
- }
-
- videoPacketSource.close();
- } else if (trackData.info.type === 'audio') {
- const audioPacketSource = source as EncodedAudioPacketSource;
- const meta = { decoderConfig: trackData.info.decoderConfig };
-
- while (trackData.packets.length > 0) {
- const nextPacket = trackData.packets[0]!;
- if (!flushAllAudio && nextPacket.timestamp >= endTimestamp) {
- break;
- }
-
- trackData.packets.shift();
- await audioPacketSource.add(nextPacket, meta);
- }
-
- audioPacketSource.close();
+ await audioSource.add(packet, meta);
+ maxEndTimestamp = Math.max(maxEndTimestamp, packet.timestamp + packet.duration);
}
}
@@ -775,13 +803,35 @@ export class HlsMuxer extends Muxer {
throw e;
}
+ if (videoEndIndex > 0) {
+ assert(videoTrack);
+ videoTrack.packets.splice(0, videoEndIndex);
+ }
+ if (audioEndIndex > 0) {
+ assert(audioTrack);
+ audioTrack.packets.splice(0, audioEndIndex);
+ }
+
+ let minNextTimestamp = Infinity;
+ if (videoTrack && videoTrack.packets.length > 0) {
+ minNextTimestamp = videoTrack.packets[0]!.timestamp;
+ }
+ if (audioTrack && audioTrack.packets.length > 0) {
+ minNextTimestamp = Math.min(minNextTimestamp, audioTrack.packets[0]!.timestamp);
+ }
+
+ const nextSegmentStartTimestamp = minNextTimestamp < Infinity
+ ? minNextTimestamp
+ : maxEndTimestamp; // Happens for the last segment for example
+ assert(Number.isFinite(nextSegmentStartTimestamp));
+
playlist.writtenSegments.push({
path: relativeSegmentPath,
- duration: endTimestamp - playlist.currentSegmentStartTimestamp,
+ duration: nextSegmentStartTimestamp - playlist.currentSegmentStartTimestamp,
byteSize: segmentSize,
});
- playlist.currentSegmentStartTimestamp = endTimestamp;
+ playlist.currentSegmentStartTimestamp = nextSegmentStartTimestamp;
}
}
@@ -822,11 +872,11 @@ export class HlsMuxer extends Muxer {
const playlistText = '#EXTM3U\n'
+ '#EXT-X-VERSION:3\n'
+ '#EXT-X-PLAYLIST-TYPE:VOD\n'
- + `#EXT-X-TARGETDURATION:${+targetDuration.toPrecision(13)}\n`
+ + `#EXT-X-TARGETDURATION:${Math.ceil(targetDuration)}\n` // Must be a "decimal-integer"
+ '\n'
+ (playlist.writtenSegments
.map(segment => (
- `#EXTINF:${+segment.duration.toPrecision(13)}\n`
+ `#EXTINF:${+segment.duration.toFixed(12)},\n` // Trailing comma mandated by spec
+ `${segment.path}\n`
))
.join(''))
@@ -914,7 +964,7 @@ export class HlsMuxer extends Muxer {
}
if (videoTrack.metadata.frameRate !== undefined) {
- masterPlaylistText += `,FRAME-RATE=${videoTrack.metadata.frameRate}`;
+ masterPlaylistText += `,FRAME-RATE=${+videoTrack.metadata.frameRate.toFixed(16)}`;
}
}
diff --git a/src/index.ts b/src/index.ts
index e174d18..977f0e7 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -35,6 +35,7 @@ export {
FlacOutputFormat,
FlacOutputFormatOptions,
HlsOutputFormat,
+ HlsOutputFormatOptions,
IsobmffOutputFormat,
IsobmffOutputFormatOptions,
MkvOutputFormat,
@@ -158,6 +159,7 @@ export {
AdtsInputFormat,
FlacInputFormat,
IsobmffInputFormat,
+ HlsInputFormat,
MatroskaInputFormat,
Mp3InputFormat,
Mp4InputFormat,
@@ -167,8 +169,10 @@ export {
WaveInputFormat,
WebMInputFormat,
ALL_FORMATS,
+ HLS_FORMATS,
ADTS,
FLAC,
+ HLS,
MATROSKA,
MP3,
MP4,
diff --git a/src/input-format.ts b/src/input-format.ts
index 9a54dc3..4a4fc99 100644
--- a/src/input-format.ts
+++ b/src/input-format.ts
@@ -703,3 +703,5 @@ export const HLS = /* #__PURE__ */ new HlsInputFormat();
* @public
*/
export const ALL_FORMATS: InputFormat[] = [HLS, MP4, QTFF, MATROSKA, WEBM, WAVE, OGG, FLAC, MP3, ADTS, MPEG_TS];
+
+export const HLS_FORMATS: InputFormat[] = [HLS, MP4, QTFF, MP3, ADTS, MPEG_TS];
diff --git a/src/media-source.ts b/src/media-source.ts
index e47679b..39b136c 100644
--- a/src/media-source.ts
+++ b/src/media-source.ts
@@ -22,12 +22,14 @@ import { OutputAudioTrack, OutputSubtitleTrack, OutputTrack, OutputVideoTrack }
import {
assert,
assertNever,
+ binarySearchLessOrEqual,
CallSerializer,
clamp,
clearIntervalUnthrottled,
isFirefox,
last,
promiseWithResolvers,
+ roundToDivisor,
setInt24,
setIntervalUnthrottled,
setUint24,
@@ -213,6 +215,16 @@ class VideoEncoderWrapper {
private codedHeight: number | null = null;
private resizeCanvas: HTMLCanvasElement | OffscreenCanvas | null = null;
+ // VideoEncoder converts everything to microseconds, so we need to do some bookkeeping to restore the original
+ // timing information
+ private preciseTimings: {
+ microsecondTimestamp: number;
+ timestamp: number;
+ duration: number;
+ timestampIsValid: boolean;
+ durationIsValid: boolean;
+ }[] = [];
+
private customEncoder: CustomVideoEncoder | null = null;
private customEncoderCallSerializer = new CallSerializer();
private customEncoderQueueSize = 0;
@@ -353,6 +365,38 @@ class VideoEncoderWrapper {
const videoFrame = videoSample.toVideoFrame();
+ const preciseTimingIndex = binarySearchLessOrEqual(
+ this.preciseTimings,
+ videoFrame.timestamp,
+ x => x.microsecondTimestamp,
+ );
+ const existingEntry = preciseTimingIndex !== -1
+ ? this.preciseTimings[preciseTimingIndex]
+ : null;
+ if (existingEntry && existingEntry.microsecondTimestamp === videoFrame.timestamp) {
+ if (existingEntry.timestamp !== videoSample.timestamp) {
+ // Mapping isn't unique, can't use the timestamp
+ existingEntry.timestampIsValid = false;
+ }
+ if (existingEntry.duration !== videoSample.duration) {
+ // Mapping isn't unique, can't use the duration
+ existingEntry.durationIsValid = false;
+ }
+ } else {
+ this.preciseTimings.splice(preciseTimingIndex + 1, 0, {
+ microsecondTimestamp: videoFrame.timestamp,
+ timestamp: videoSample.timestamp,
+ duration: videoSample.duration,
+ timestampIsValid: true,
+ durationIsValid: true,
+ });
+
+ // Make sure it doesn't grow indefinitely
+ if (this.preciseTimings.length > 128) {
+ this.preciseTimings.shift();
+ }
+ }
+
if (!this.alphaEncoder) {
// No alpha encoder, simple case
this.encoder.encode(videoFrame, finalEncodeOptions);
@@ -515,7 +559,25 @@ class VideoEncoderWrapper {
sideData.alpha = alphaData;
}
- const packet = EncodedPacket.fromEncodedChunk(colorChunk, sideData);
+ let packet = EncodedPacket.fromEncodedChunk(colorChunk, sideData);
+
+ const preciseTimingIndex = binarySearchLessOrEqual(
+ this.preciseTimings,
+ colorChunk.timestamp,
+ x => x.microsecondTimestamp,
+ );
+ const entry = preciseTimingIndex !== -1
+ ? this.preciseTimings[preciseTimingIndex]
+ : null;
+
+ // If there's a relevant timing entry, refine the packet's timing data to get better accuracy than
+ // microseconds
+ if (entry && entry.microsecondTimestamp === colorChunk.timestamp) {
+ packet = packet.clone({
+ timestamp: entry.timestampIsValid ? entry.timestamp : undefined,
+ duration: entry.durationIsValid ? entry.duration : undefined,
+ });
+ }
this.encodingConfig.onEncodedPacket?.(packet, meta);
void this.muxer!.addEncodedVideoPacket(this.source._connectedTrack!, packet, meta)
@@ -1766,7 +1828,16 @@ class AudioEncoderWrapper {
}
}
- const packet = EncodedPacket.fromEncodedChunk(chunk);
+ let packet = EncodedPacket.fromEncodedChunk(chunk);
+
+ // Snap the timing information to the sample rate to recover information lost in microsecond
+ // conversion
+ packet = packet.clone({
+ timestamp: roundToDivisor(packet.timestamp, encoderConfig.sampleRate),
+ duration: chunk.duration != null
+ ? roundToDivisor(packet.duration, encoderConfig.sampleRate)
+ : undefined,
+ });
this.encodingConfig.onEncodedPacket?.(packet, meta);
void this.muxer!.addEncodedAudioPacket(this.source._connectedTrack!, packet, meta)
diff --git a/src/misc.ts b/src/misc.ts
index 0aa8fb0..ba82b56 100644
--- a/src/misc.ts
+++ b/src/misc.ts
@@ -791,6 +791,14 @@ export const joinPaths = (basePath: string, relativePath: string) => {
return relativePath;
}
+ // Strip query parameters from URL base paths so their contents don't mess up the join
+ if (basePath.includes('://')) {
+ const queryIndex = basePath.indexOf('?');
+ if (queryIndex !== -1) {
+ basePath = basePath.slice(0, queryIndex);
+ }
+ }
+
let result: string;
if (relativePath.startsWith('/')) {
diff --git a/src/output-format.ts b/src/output-format.ts
index 15ade17..6e5541b 100644
--- a/src/output-format.ts
+++ b/src/output-format.ts
@@ -1189,6 +1189,11 @@ export class HlsOutputFormat extends OutputFormat {
get supportsTimestampedMediaData(): boolean {
return true; // I guess??
}
+
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ override _codecUnsupportedHint(codec: MediaCodec): string {
+ return ` Using different segment formats may grant support for this codec.`;
+ }
}
export const HLS_OUTPUT_FORMATS_DEFAULT = [new AdtsOutputFormat(), new MpegTsOutputFormat()];
diff --git a/test/node/hls-output.test.ts b/test/node/hls-output.test.ts
index 8339206..70acd07 100644
--- a/test/node/hls-output.test.ts
+++ b/test/node/hls-output.test.ts
@@ -1,10 +1,17 @@
import { expect, test, vi } from 'vitest';
import { Output, OutputTrackGroup } from '../../src/output.js';
-import { HLS_OUTPUT_FORMATS_DEFAULT, HlsOutputFormat } from '../../src/output-format.js';
-import { NullTarget } from '../../src/target.js';
+import { HLS_OUTPUT_FORMATS_DEFAULT, HlsOutputFormat, MpegTsOutputFormat } from '../../src/output-format.js';
+import { BufferTarget, NullTarget } from '../../src/target.js';
import { EncodedAudioPacketSource, EncodedVideoPacketSource } from '../../src/media-source.js';
import { HlsMuxer } from '../../src/hls/hls-muxer.js';
import { AudioCodec, VideoCodec } from '../../src/codec.js';
+import { EncodedPacket, PacketType } from '../../src/packet.js';
+import { promiseWithResolvers } from '../../src/misc.js';
+import { Input } from '../../src/input.js';
+import { BufferSource } from '../../src/source.js';
+import { ALL_FORMATS } from '../../src/input-format.js';
+import { InputAudioTrack, InputVideoTrack } from '../../src/input-track.js';
+import { EncodedPacketSink } from '../../src/media-sink.js';
const videoSource = (codec: VideoCodec = 'avc') => new EncodedVideoPacketSource(codec);
const audioSource = (codec: AudioCodec = 'aac') => new EncodedAudioPacketSource(codec);
@@ -791,3 +798,936 @@ test('Playlist assignment, audio paired with audio', async () => {
expect(consoleSpy.mock.calls).toHaveLength(1);
expect(consoleSpy.mock.calls[0]![0]).toContain('Illegal pairing');
});
+
+// eslint-disable-next-line @stylistic/max-len
+const avcPacketData = new Uint8Array([0, 0, 0, 1, 9, 240, 0, 0, 0, 1, 39, 77, 64, 41, 169, 24, 15, 0, 68, 252, 184, 3, 80, 16, 16, 27, 108, 43, 94, 247, 192, 64, 0, 0, 0, 1, 40, 222, 9, 200, 0, 0, 1, 6, 0, 7, 128, 175, 200, 0, 0, 3, 0, 64, 128, 0, 0, 1, 6, 5, 17, 3, 135, 244, 78, 205, 10, 75, 220, 161, 148, 58, 195, 212, 155, 23, 31, 0, 128, 0, 0, 1, 37, 184, 32, 32, 33, 68, 197, 0, 1, 87, 155, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 175, 0, 0, 1, 37, 0, 127, 174, 8, 8, 8, 81, 49, 64, 0, 85, 230, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 147, 20, 0, 5, 94, 111, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 250, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 188, 0, 0, 1, 37, 0, 63, 203, 130, 2, 2, 20, 76, 80, 0, 21, 121, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 240, 0, 0, 1, 37, 0, 23, 234, 224, 128, 128, 133, 19, 20, 0, 5, 94, 111, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 249, 49, 64, 0, 85, 230, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 192, 0, 0, 1, 37, 0, 31, 226, 224, 128, 128, 133, 19, 20, 0, 5, 94, 111, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 250, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 188, 0, 0, 1, 37, 0, 9, 246, 184, 32, 32, 33, 68, 197, 0, 1, 87, 155, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 76, 80, 0, 21, 121, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 240, 0, 0, 1, 37, 0, 11, 244, 184, 32, 32, 33, 68, 197, 0, 1, 87, 155, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 175, 0, 0, 1, 37, 0, 13, 242, 184, 32, 32, 33, 68, 197, 0, 1, 87, 155, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 76, 80, 0, 21, 121, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 239, 190, 251, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 235, 174, 186, 255, 248, 255, 4, 17, 64, 0, 69, 19, 223, 125, 247, 223, 125, 247, 223, 125, 247, 223, 125, 247, 223, 125, 247, 223, 125, 247, 223, 125, 247, 223, 125, 247, 223, 125, 247, 223, 125, 247, 223, 125, 247, 223, 125, 247, 223, 125, 247, 223, 125, 247, 223, 125, 247, 223, 125, 247, 223, 125, 247, 223, 125, 247, 223, 125, 247, 223, 125, 247, 223, 125, 247, 223, 125, 247, 223, 125, 247, 223, 125, 247, 223, 125, 247, 223, 125, 247, 223, 125, 247, 223, 125, 247, 223, 125, 247, 223, 125, 248]); ;
+const avcMetadata: EncodedVideoChunkMetadata = {
+ decoderConfig: {
+ codec: 'avc1.4d401e',
+ codedWidth: 1280,
+ codedHeight: 720,
+ },
+};
+
+// eslint-disable-next-line @stylistic/max-len
+const aacPacketData = new Uint8Array([255, 241, 77, 128, 3, 159, 252, 0, 208, 0, 1, 3, 64, 0, 13, 0, 0, 17, 52, 0, 0, 208, 0, 3, 6, 128, 0, 56]);
+const aacMetadata: EncodedAudioChunkMetadata = {
+ decoderConfig: {
+ codec: 'mp4a.40.2',
+ numberOfChannels: 2,
+ sampleRate: 48000,
+ },
+};
+
+const setUpSegmentationEnvironment = async (options: {
+ video?: boolean;
+ audio?: boolean;
+} = {}) => {
+ let result: string | null = null;
+ let segmentCount = 0;
+ let lastSegmentVideoTimestamps: Promise = Promise.resolve([]);
+ let lastSegmentAudioTimestamps: Promise = Promise.resolve([]);
+
+ const output = new Output({
+ format: new HlsOutputFormat({
+ segmentFormats: [new MpegTsOutputFormat()], // No ADTS for simplicity
+ }),
+ target: (request) => {
+ const target = new BufferTarget();
+ if (request.path.includes('playlist')) {
+ target.onfinalized = () => {
+ result = new TextDecoder().decode(target.buffer!);
+ };
+ } else if (request.path.includes('segment')) {
+ segmentCount++;
+
+ const videoBundle = promiseWithResolvers();
+ lastSegmentVideoTimestamps = videoBundle.promise;
+ const audioBundle = promiseWithResolvers();
+ lastSegmentAudioTimestamps = audioBundle.promise;
+
+ target.onfinalized = async () => {
+ try {
+ using input = new Input({
+ source: new BufferSource(target.buffer!),
+ formats: ALL_FORMATS,
+ });
+
+ const videoTrack = await input.getPrimaryVideoTrack() as InputVideoTrack;
+ if (videoTrack) {
+ const sink = new EncodedPacketSink(videoTrack);
+ const timestamps: number[] = [];
+ for await (const packet of sink.packets()) {
+ timestamps.push(packet.timestamp);
+ }
+ videoBundle.resolve(timestamps);
+ } else {
+ videoBundle.resolve([]);
+ }
+
+ const audioTrack = await input.getPrimaryAudioTrack() as InputAudioTrack;
+ if (audioTrack) {
+ const sink = new EncodedPacketSink(audioTrack);
+ const timestamps: number[] = [];
+ for await (const packet of sink.packets()) {
+ timestamps.push(packet.timestamp);
+ }
+ audioBundle.resolve(timestamps);
+ } else {
+ audioBundle.resolve([]);
+ }
+ } catch {
+ videoBundle.resolve([]);
+ audioBundle.resolve([]);
+ }
+ };
+ }
+
+ return target;
+ },
+ rootPath: '',
+ });
+
+ const _videoSource = options.video ? new EncodedVideoPacketSource('avc') : null;
+ const _audioSource = options.audio ? new EncodedAudioPacketSource('aac') : null;
+
+ if (_videoSource) {
+ output.addVideoTrack(_videoSource);
+ }
+ if (_audioSource) {
+ output.addAudioTrack(_audioSource);
+ }
+
+ await output.start();
+
+ const addVideoPacket = _videoSource
+ ? (timestamp: number, type: PacketType, duration = 0) => {
+ return _videoSource.add(
+ new EncodedPacket(avcPacketData, type, timestamp, duration),
+ avcMetadata,
+ );
+ }
+ : null;
+
+ const addAudioPacket = _audioSource
+ ? (timestamp: number, duration = 0) => {
+ return _audioSource.add(
+ new EncodedPacket(aacPacketData, 'key', timestamp, duration),
+ aacMetadata,
+ );
+ }
+ : null;
+
+ return {
+ output,
+ videoSource: _videoSource,
+ audioSource: _audioSource,
+ addVideoPacket,
+ addAudioPacket,
+ get segmentCount() { return segmentCount; },
+ get result() { return result; },
+ get lastSegmentVideoTimestamps() { return lastSegmentVideoTimestamps; },
+ get lastSegmentAudioTimestamps() { return lastSegmentAudioTimestamps; },
+ };
+};
+
+test('Segmentation, simple', async () => {
+ const env = await setUpSegmentationEnvironment({ video: true });
+
+ await env.addVideoPacket!(0, 'key');
+ await env.addVideoPacket!(0.5, 'delta');
+ await env.addVideoPacket!(1, 'delta');
+ await env.addVideoPacket!(1.5, 'delta');
+ expect(env.segmentCount).toBe(0);
+ await env.addVideoPacket!(2, 'key');
+ expect(env.segmentCount).toBe(1);
+ expect(await env.lastSegmentVideoTimestamps).toEqual([0, 0.5, 1, 1.5]);
+ await env.addVideoPacket!(2.5, 'delta');
+ await env.addVideoPacket!(3, 'delta');
+ await env.addVideoPacket!(3.5, 'delta');
+
+ await env.output.finalize();
+ expect(env.segmentCount).toBe(2);
+ expect(await env.lastSegmentVideoTimestamps).toEqual([2, 2.5, 3, 3.5]);
+
+ expect(env.result).toBe(`#EXTM3U
+#EXT-X-VERSION:3
+#EXT-X-PLAYLIST-TYPE:VOD
+#EXT-X-TARGETDURATION:2
+
+#EXTINF:2,
+segment-1-1.ts
+#EXTINF:1.5,
+segment-1-2.ts
+
+#EXT-X-ENDLIST
+`,
+ );
+});
+
+test('Segmentation, reaching until end of second segment', async () => {
+ const env = await setUpSegmentationEnvironment({ video: true });
+
+ await env.addVideoPacket!(0, 'key');
+ await env.addVideoPacket!(0.5, 'delta');
+ await env.addVideoPacket!(1, 'delta');
+ await env.addVideoPacket!(1.5, 'delta');
+ expect(env.segmentCount).toBe(0);
+ await env.addVideoPacket!(2, 'key');
+ expect(env.segmentCount).toBe(1);
+ expect(await env.lastSegmentVideoTimestamps).toEqual([0, 0.5, 1, 1.5]);
+ await env.addVideoPacket!(2.5, 'delta');
+ await env.addVideoPacket!(3, 'delta');
+ await env.addVideoPacket!(3.5, 'delta');
+ await env.addVideoPacket!(4, 'delta');
+ expect(env.segmentCount).toBe(1);
+
+ await env.output.finalize();
+ expect(env.segmentCount).toBe(2);
+ expect(await env.lastSegmentVideoTimestamps).toEqual([2, 2.5, 3, 3.5, 4]);
+
+ expect(env.result).toBe(`#EXTM3U
+#EXT-X-VERSION:3
+#EXT-X-PLAYLIST-TYPE:VOD
+#EXT-X-TARGETDURATION:2
+
+#EXTINF:2,
+segment-1-1.ts
+#EXTINF:2,
+segment-1-2.ts
+
+#EXT-X-ENDLIST
+`,
+ );
+});
+
+test('Segmentation, reaching until end of second segment with a final key packet', async () => {
+ const env = await setUpSegmentationEnvironment({ video: true });
+
+ await env.addVideoPacket!(0, 'key');
+ await env.addVideoPacket!(0.5, 'delta');
+ await env.addVideoPacket!(1, 'delta');
+ await env.addVideoPacket!(1.5, 'delta');
+ expect(env.segmentCount).toBe(0);
+ await env.addVideoPacket!(2, 'key');
+ expect(env.segmentCount).toBe(1);
+ expect(await env.lastSegmentVideoTimestamps).toEqual([0, 0.5, 1, 1.5]);
+ await env.addVideoPacket!(2.5, 'delta');
+ await env.addVideoPacket!(3, 'delta');
+ await env.addVideoPacket!(3.5, 'delta');
+ expect(env.segmentCount).toBe(1);
+ await env.addVideoPacket!(4, 'key');
+ expect(env.segmentCount).toBe(2);
+ expect(await env.lastSegmentVideoTimestamps).toEqual([2, 2.5, 3, 3.5]);
+
+ await env.output.finalize();
+ expect(env.segmentCount).toBe(3);
+ expect(await env.lastSegmentVideoTimestamps).toEqual([4]);
+
+ expect(env.result).toBe(`#EXTM3U
+#EXT-X-VERSION:3
+#EXT-X-PLAYLIST-TYPE:VOD
+#EXT-X-TARGETDURATION:2
+
+#EXTINF:2,
+segment-1-1.ts
+#EXTINF:2,
+segment-1-2.ts
+#EXTINF:0,
+segment-1-3.ts
+
+#EXT-X-ENDLIST
+`,
+ );
+});
+
+test('Segmentation, reaching until end of second segment with a final key packet (audio)', async () => {
+ const env = await setUpSegmentationEnvironment({ audio: true });
+
+ await env.addAudioPacket!(0);
+ await env.addAudioPacket!(0.5);
+ await env.addAudioPacket!(1);
+ await env.addAudioPacket!(1.5);
+ expect(env.segmentCount).toBe(0);
+ await env.addAudioPacket!(2);
+ expect(env.segmentCount).toBe(1);
+ expect(await env.lastSegmentAudioTimestamps).toEqual([0, 0.5, 1, 1.5]);
+
+ await env.output.finalize();
+ expect(env.segmentCount).toBe(2);
+ expect(await env.lastSegmentAudioTimestamps).toEqual([2]);
+
+ expect(env.result).toBe(`#EXTM3U
+#EXT-X-VERSION:3
+#EXT-X-PLAYLIST-TYPE:VOD
+#EXT-X-TARGETDURATION:2
+
+#EXTINF:2,
+segment-1-1.ts
+#EXTINF:0,
+segment-1-2.ts
+
+#EXT-X-ENDLIST
+`,
+ );
+});
+
+test('Segmentation, reaching until end of second segment with packet durations', async () => {
+ const env = await setUpSegmentationEnvironment({ video: true });
+
+ await env.addVideoPacket!(0, 'key', 0.5);
+ await env.addVideoPacket!(0.5, 'delta', 0.5);
+ await env.addVideoPacket!(1, 'delta', 0.5);
+ await env.addVideoPacket!(1.5, 'delta', 0.5);
+ expect(env.segmentCount).toBe(0);
+ await env.addVideoPacket!(2, 'key', 0.5);
+ expect(env.segmentCount).toBe(1);
+ expect(await env.lastSegmentVideoTimestamps).toEqual([0, 0.5, 1, 1.5]);
+ await env.addVideoPacket!(2.5, 'delta', 0.5);
+ await env.addVideoPacket!(3, 'delta', 0.5);
+ await env.addVideoPacket!(3.5, 'delta', 0.5);
+ expect(env.segmentCount).toBe(1);
+ await env.addVideoPacket!(4, 'key', 0.5);
+ expect(env.segmentCount).toBe(2);
+ expect(await env.lastSegmentVideoTimestamps).toEqual([2, 2.5, 3, 3.5]);
+
+ await env.output.finalize();
+ expect(env.segmentCount).toBe(3);
+ expect(await env.lastSegmentVideoTimestamps).toEqual([4]);
+
+ expect(env.result).toBe(`#EXTM3U
+#EXT-X-VERSION:3
+#EXT-X-PLAYLIST-TYPE:VOD
+#EXT-X-TARGETDURATION:2
+
+#EXTINF:2,
+segment-1-1.ts
+#EXTINF:2,
+segment-1-2.ts
+#EXTINF:0.5,
+segment-1-3.ts
+
+#EXT-X-ENDLIST
+`,
+ );
+});
+
+test('Segmentation, reaching until end of second segment with packet durations (audio)', async () => {
+ const env = await setUpSegmentationEnvironment({ audio: true });
+
+ await env.addAudioPacket!(0, 0.5);
+ await env.addAudioPacket!(0.5, 0.5);
+ await env.addAudioPacket!(1, 0.5);
+ await env.addAudioPacket!(1.5, 0.5);
+ expect(env.segmentCount).toBe(0);
+ await env.addAudioPacket!(2, 0.5);
+ expect(env.segmentCount).toBe(1);
+ expect(await env.lastSegmentAudioTimestamps).toEqual([0, 0.5, 1, 1.5]);
+ await env.addAudioPacket!(2.5, 0.5);
+ await env.addAudioPacket!(3, 0.5);
+ await env.addAudioPacket!(3.5, 0.5);
+ expect(env.segmentCount).toBe(1);
+ await env.addAudioPacket!(4, 0.5);
+ expect(env.segmentCount).toBe(2);
+ expect(await env.lastSegmentAudioTimestamps).toEqual([2, 2.5, 3, 3.5]);
+
+ await env.output.finalize();
+ expect(env.segmentCount).toBe(3);
+ expect(await env.lastSegmentAudioTimestamps).toEqual([4]);
+
+ expect(env.result).toBe(`#EXTM3U
+#EXT-X-VERSION:3
+#EXT-X-PLAYLIST-TYPE:VOD
+#EXT-X-TARGETDURATION:2
+
+#EXTINF:2,
+segment-1-1.ts
+#EXTINF:2,
+segment-1-2.ts
+#EXTINF:0.5,
+segment-1-3.ts
+
+#EXT-X-ENDLIST
+`,
+ );
+});
+
+test('Segmentation, only one key packet', async () => {
+ const env = await setUpSegmentationEnvironment({ video: true });
+
+ await env.addVideoPacket!(0, 'key', 0.5);
+ await env.addVideoPacket!(0.5, 'delta', 0.5);
+ await env.addVideoPacket!(1, 'delta', 0.5);
+ await env.addVideoPacket!(1.5, 'delta', 0.5);
+ await env.addVideoPacket!(2, 'delta', 0.5);
+ await env.addVideoPacket!(2.5, 'delta', 0.5);
+ await env.addVideoPacket!(3, 'delta', 0.5);
+ expect(env.segmentCount).toBe(0);
+
+ await env.output.finalize();
+ expect(env.segmentCount).toBe(1);
+ expect(await env.lastSegmentVideoTimestamps).toEqual([0, 0.5, 1, 1.5, 2, 2.5, 3]);
+
+ expect(env.result).toBe(`#EXTM3U
+#EXT-X-VERSION:3
+#EXT-X-PLAYLIST-TYPE:VOD
+#EXT-X-TARGETDURATION:4
+
+#EXTINF:3.5,
+segment-1-1.ts
+
+#EXT-X-ENDLIST
+`,
+ );
+});
+
+test('Segmentation, key packets before the end of a segment (maximized segment duration test)', async () => {
+ const env = await setUpSegmentationEnvironment({ video: true });
+
+ await env.addVideoPacket!(0, 'key', 0.5);
+ await env.addVideoPacket!(0.5, 'delta', 0.5);
+ await env.addVideoPacket!(1, 'delta', 0.5);
+ await env.addVideoPacket!(1.5, 'key', 0.5);
+ expect(env.segmentCount).toBe(0);
+ await env.addVideoPacket!(2, 'delta', 0.5);
+ expect(env.segmentCount).toBe(1);
+ expect(await env.lastSegmentVideoTimestamps).toEqual([0, 0.5, 1]);
+ await env.addVideoPacket!(2.5, 'delta', 0.5);
+ await env.addVideoPacket!(3, 'key', 0.5);
+ expect(env.segmentCount).toBe(1);
+ await env.addVideoPacket!(3.5, 'delta', 0.5);
+ expect(env.segmentCount).toBe(2);
+ expect(await env.lastSegmentVideoTimestamps).toEqual([1.5, 2, 2.5]);
+ await env.addVideoPacket!(4, 'delta', 0.5);
+ await env.addVideoPacket!(4.5, 'key', 0.5);
+ expect(env.segmentCount).toBe(2);
+ await env.addVideoPacket!(5, 'key', 0.5);
+ expect(env.segmentCount).toBe(3);
+ expect(await env.lastSegmentVideoTimestamps).toEqual([3, 3.5, 4, 4.5]);
+
+ await env.output.finalize();
+ expect(env.segmentCount).toBe(4);
+ expect(await env.lastSegmentVideoTimestamps).toEqual([5]);
+
+ expect(env.result).toBe(`#EXTM3U
+#EXT-X-VERSION:3
+#EXT-X-PLAYLIST-TYPE:VOD
+#EXT-X-TARGETDURATION:2
+
+#EXTINF:1.5,
+segment-1-1.ts
+#EXTINF:1.5,
+segment-1-2.ts
+#EXTINF:2,
+segment-1-3.ts
+#EXTINF:0.5,
+segment-1-4.ts
+
+#EXT-X-ENDLIST
+`,
+ );
+});
+
+test('Segmentation, full segment duration recovery after shorter segment', async () => {
+ const env = await setUpSegmentationEnvironment({ video: true });
+
+ await env.addVideoPacket!(0, 'key', 0.5);
+ await env.addVideoPacket!(0.5, 'delta', 0.5);
+ await env.addVideoPacket!(1, 'delta', 0.5);
+ await env.addVideoPacket!(1.5, 'key', 0.5);
+ expect(env.segmentCount).toBe(0);
+ await env.addVideoPacket!(2, 'delta', 0.5);
+ expect(env.segmentCount).toBe(1);
+ expect(await env.lastSegmentVideoTimestamps).toEqual([0, 0.5, 1]);
+ await env.addVideoPacket!(2.5, 'delta', 0.5);
+ await env.addVideoPacket!(3, 'delta', 0.5);
+ expect(env.segmentCount).toBe(1);
+ await env.addVideoPacket!(3.5, 'key', 0.5);
+ expect(env.segmentCount).toBe(2);
+ expect(await env.lastSegmentVideoTimestamps).toEqual([1.5, 2, 2.5, 3]);
+ await env.addVideoPacket!(4, 'delta', 0.5);
+
+ await env.output.finalize();
+ expect(env.segmentCount).toBe(3);
+ expect(await env.lastSegmentVideoTimestamps).toEqual([3.5, 4]);
+
+ expect(env.result).toBe(`#EXTM3U
+#EXT-X-VERSION:3
+#EXT-X-PLAYLIST-TYPE:VOD
+#EXT-X-TARGETDURATION:2
+
+#EXTINF:1.5,
+segment-1-1.ts
+#EXTINF:2,
+segment-1-2.ts
+#EXTINF:1,
+segment-1-3.ts
+
+#EXT-X-ENDLIST
+`,
+ );
+});
+
+test('Segmentation, packet start timestamp intersecting with end timestamp of previous packet', async () => {
+ const env = await setUpSegmentationEnvironment({ video: true });
+
+ await env.addVideoPacket!(0, 'key', 0.5);
+ await env.addVideoPacket!(0.5, 'delta', 0.5);
+ await env.addVideoPacket!(1, 'delta', 0.5);
+ await env.addVideoPacket!(1.5, 'delta', 0.75); // This!
+ expect(env.segmentCount).toBe(0);
+ await env.addVideoPacket!(2, 'key', 0.5);
+ expect(env.segmentCount).toBe(1);
+ expect(await env.lastSegmentVideoTimestamps).toEqual([0, 0.5, 1, 1.5]);
+
+ await env.output.finalize();
+ expect(env.segmentCount).toBe(2);
+ expect(await env.lastSegmentVideoTimestamps).toEqual([2]);
+
+ expect(env.result).toBe(`#EXTM3U
+#EXT-X-VERSION:3
+#EXT-X-PLAYLIST-TYPE:VOD
+#EXT-X-TARGETDURATION:2
+
+#EXTINF:2,
+segment-1-1.ts
+#EXTINF:0.5,
+segment-1-2.ts
+
+#EXT-X-ENDLIST
+`,
+ );
+});
+
+test('Segmentation, last video packet is included', async () => {
+ const env = await setUpSegmentationEnvironment({ video: true });
+
+ await env.addVideoPacket!(0, 'key');
+ await env.addVideoPacket!(0.5, 'delta');
+ await env.addVideoPacket!(1, 'delta');
+ await env.addVideoPacket!(1.5, 'delta');
+ expect(env.segmentCount).toBe(0);
+
+ await env.output.finalize();
+ expect(env.segmentCount).toBe(1);
+ expect(await env.lastSegmentVideoTimestamps).toEqual([0, 0.5, 1, 1.5]);
+
+ expect(env.result).toBe(`#EXTM3U
+#EXT-X-VERSION:3
+#EXT-X-PLAYLIST-TYPE:VOD
+#EXT-X-TARGETDURATION:2
+
+#EXTINF:1.5,
+segment-1-1.ts
+
+#EXT-X-ENDLIST
+`,
+ );
+});
+
+test('Segmentation, video not lining up with segment boundaries', async () => {
+ const env = await setUpSegmentationEnvironment({ video: true });
+
+ await env.addVideoPacket!(0, 'key');
+ await env.addVideoPacket!(0.45, 'key');
+ await env.addVideoPacket!(0.9, 'key');
+ await env.addVideoPacket!(1.35, 'key');
+ await env.addVideoPacket!(1.8, 'key');
+ expect(env.segmentCount).toBe(0);
+ await env.addVideoPacket!(2.25, 'key');
+ expect(env.segmentCount).toBe(1);
+ expect(await env.lastSegmentVideoTimestamps).toEqual([0, 0.45, 0.9, 1.35]);
+ await env.addVideoPacket!(2.7, 'key');
+ await env.addVideoPacket!(3.15, 'key');
+ await env.addVideoPacket!(3.6, 'key');
+ expect(env.segmentCount).toBe(1);
+ await env.addVideoPacket!(4.05, 'key');
+ expect(env.segmentCount).toBe(2);
+ expect(await env.lastSegmentVideoTimestamps).toEqual([1.8, 2.25, 2.7, 3.15]);
+
+ await env.output.finalize();
+ expect(env.segmentCount).toBe(3);
+ console.log(await env.lastSegmentVideoTimestamps);
+ expect(await env.lastSegmentVideoTimestamps).toEqual([3.6, 4.05]);
+
+ expect(env.result).toBe(`#EXTM3U
+#EXT-X-VERSION:3
+#EXT-X-PLAYLIST-TYPE:VOD
+#EXT-X-TARGETDURATION:2
+
+#EXTINF:1.8,
+segment-1-1.ts
+#EXTINF:1.8,
+segment-1-2.ts
+#EXTINF:0.45,
+segment-1-3.ts
+
+#EXT-X-ENDLIST
+`,
+ );
+});
+
+test('Segmentation, audio not lining up with segment boundaries', async () => {
+ const env = await setUpSegmentationEnvironment({ audio: true });
+
+ await env.addAudioPacket!(0);
+ await env.addAudioPacket!(0.45);
+ await env.addAudioPacket!(0.9);
+ await env.addAudioPacket!(1.35);
+ await env.addAudioPacket!(1.8);
+ expect(env.segmentCount).toBe(0);
+ await env.addAudioPacket!(2.25);
+ expect(env.segmentCount).toBe(1);
+ expect(await env.lastSegmentAudioTimestamps).toEqual([0, 0.45, 0.9, 1.35]);
+ await env.addAudioPacket!(2.7);
+ await env.addAudioPacket!(3.15);
+ await env.addAudioPacket!(3.6);
+ expect(env.segmentCount).toBe(1);
+ await env.addAudioPacket!(4.05);
+ expect(env.segmentCount).toBe(2);
+ expect(await env.lastSegmentAudioTimestamps).toEqual([1.8, 2.25, 2.7, 3.15]);
+
+ await env.output.finalize();
+ expect(env.segmentCount).toBe(3);
+ console.log(await env.lastSegmentAudioTimestamps);
+ expect(await env.lastSegmentAudioTimestamps).toEqual([3.6, 4.05]);
+
+ expect(env.result).toBe(`#EXTM3U
+#EXT-X-VERSION:3
+#EXT-X-PLAYLIST-TYPE:VOD
+#EXT-X-TARGETDURATION:2
+
+#EXTINF:1.8,
+segment-1-1.ts
+#EXTINF:1.8,
+segment-1-2.ts
+#EXTINF:0.45,
+segment-1-3.ts
+
+#EXT-X-ENDLIST
+`,
+ );
+});
+
+test('Segmentation, dual-track, single segment', async () => {
+ const env = await setUpSegmentationEnvironment({ video: true, audio: true });
+
+ await env.addVideoPacket!(0, 'key');
+ await env.addVideoPacket!(0.5, 'delta');
+ await env.addVideoPacket!(1, 'delta');
+ await env.addVideoPacket!(1.5, 'delta');
+ await env.addVideoPacket!(2, 'key');
+
+ await env.addAudioPacket!(0);
+ await env.addAudioPacket!(0.5);
+ await env.addAudioPacket!(1);
+ await env.addAudioPacket!(1.5);
+ expect(env.segmentCount).toBe(0);
+ await env.addAudioPacket!(2);
+ expect(env.segmentCount).toBe(1);
+ expect(await env.lastSegmentVideoTimestamps).toEqual([0, 0.5, 1, 1.5]);
+ expect(await env.lastSegmentAudioTimestamps).toEqual([0, 0.5, 1, 1.5]);
+
+ await env.output.finalize();
+ expect(env.segmentCount).toBe(2);
+ expect(await env.lastSegmentVideoTimestamps).toEqual([2]);
+ expect(await env.lastSegmentAudioTimestamps).toEqual([2]);
+
+ expect(env.result).toBe(`#EXTM3U
+#EXT-X-VERSION:3
+#EXT-X-PLAYLIST-TYPE:VOD
+#EXT-X-TARGETDURATION:2
+
+#EXTINF:2,
+segment-1-1.ts
+#EXTINF:0,
+segment-1-2.ts
+
+#EXT-X-ENDLIST
+`,
+ );
+});
+
+test('Segmentation, dual-track, video dictates the segmentation', async () => {
+ const env = await setUpSegmentationEnvironment({ video: true, audio: true });
+
+ await env.addVideoPacket!(0, 'key');
+ await env.addVideoPacket!(0.5, 'delta');
+ await env.addVideoPacket!(1, 'delta');
+ await env.addVideoPacket!(1.5, 'key');
+ await env.addVideoPacket!(2, 'delta');
+ await env.addVideoPacket!(2.5, 'delta');
+ await env.addVideoPacket!(3, 'delta');
+ await env.addVideoPacket!(3.5, 'delta');
+ await env.addVideoPacket!(4, 'delta');
+ await env.addVideoPacket!(4.5, 'key');
+
+ await env.addAudioPacket!(0);
+ await env.addAudioPacket!(0.5);
+ await env.addAudioPacket!(1);
+ expect(env.segmentCount).toBe(0);
+ await env.addAudioPacket!(1.5);
+ expect(env.segmentCount).toBe(1);
+ expect(await env.lastSegmentVideoTimestamps).toEqual([0, 0.5, 1]);
+ expect(await env.lastSegmentAudioTimestamps).toEqual([0, 0.5, 1]);
+ await env.addAudioPacket!(2);
+ await env.addAudioPacket!(2.5);
+ await env.addAudioPacket!(3);
+ await env.addAudioPacket!(3.5);
+ await env.addAudioPacket!(4);
+ expect(env.segmentCount).toBe(1);
+ await env.addAudioPacket!(4.5);
+ expect(env.segmentCount).toBe(2);
+ expect(await env.lastSegmentVideoTimestamps).toEqual([1.5, 2, 2.5, 3, 3.5, 4]);
+ expect(await env.lastSegmentAudioTimestamps).toEqual([1.5, 2, 2.5, 3, 3.5, 4]);
+
+ await env.output.finalize();
+ expect(env.segmentCount).toBe(3);
+ expect(await env.lastSegmentVideoTimestamps).toEqual([4.5]);
+ expect(await env.lastSegmentAudioTimestamps).toEqual([4.5]);
+
+ expect(env.result).toBe(`#EXTM3U
+#EXT-X-VERSION:3
+#EXT-X-PLAYLIST-TYPE:VOD
+#EXT-X-TARGETDURATION:3
+
+#EXTINF:1.5,
+segment-1-1.ts
+#EXTINF:3,
+segment-1-2.ts
+#EXTINF:0,
+segment-1-3.ts
+
+#EXT-X-ENDLIST
+`,
+ );
+});
+
+test('Segmentation, dual-track, video dictates the segmentation, inverted', async () => {
+ const env = await setUpSegmentationEnvironment({ video: true, audio: true });
+
+ await env.addAudioPacket!(0);
+ await env.addAudioPacket!(0.5);
+ await env.addAudioPacket!(1);
+ await env.addAudioPacket!(1.5);
+ await env.addAudioPacket!(2);
+ await env.addAudioPacket!(2.5);
+ await env.addAudioPacket!(3);
+ await env.addAudioPacket!(3.5);
+ await env.addAudioPacket!(4);
+ await env.addAudioPacket!(4.5);
+
+ await env.addVideoPacket!(0, 'key');
+ await env.addVideoPacket!(0.5, 'delta');
+ await env.addVideoPacket!(1, 'delta');
+ await env.addVideoPacket!(1.5, 'key');
+ expect(env.segmentCount).toBe(0);
+ await env.addVideoPacket!(2, 'delta');
+ expect(env.segmentCount).toBe(1);
+ expect(await env.lastSegmentVideoTimestamps).toEqual([0, 0.5, 1]);
+ expect(await env.lastSegmentAudioTimestamps).toEqual([0, 0.5, 1]);
+ await env.addVideoPacket!(2.5, 'delta');
+ await env.addVideoPacket!(3, 'delta');
+ await env.addVideoPacket!(3.5, 'delta');
+ await env.addVideoPacket!(4, 'delta');
+ expect(env.segmentCount).toBe(1);
+ await env.addVideoPacket!(4.5, 'key');
+ expect(env.segmentCount).toBe(2);
+ expect(await env.lastSegmentVideoTimestamps).toEqual([1.5, 2, 2.5, 3, 3.5, 4]);
+ expect(await env.lastSegmentAudioTimestamps).toEqual([1.5, 2, 2.5, 3, 3.5, 4]);
+
+ await env.output.finalize();
+ expect(env.segmentCount).toBe(3);
+ expect(await env.lastSegmentVideoTimestamps).toEqual([4.5]);
+ expect(await env.lastSegmentAudioTimestamps).toEqual([4.5]);
+
+ expect(env.result).toBe(`#EXTM3U
+#EXT-X-VERSION:3
+#EXT-X-PLAYLIST-TYPE:VOD
+#EXT-X-TARGETDURATION:3
+
+#EXTINF:1.5,
+segment-1-1.ts
+#EXTINF:3,
+segment-1-2.ts
+#EXTINF:0,
+segment-1-3.ts
+
+#EXT-X-ENDLIST
+`,
+ );
+});
+
+test('Segmentation, dual-track, audio ending after video', async () => {
+ const env = await setUpSegmentationEnvironment({ video: true, audio: true });
+
+ await env.addVideoPacket!(0, 'key');
+ await env.addVideoPacket!(0.5, 'delta');
+ await env.addVideoPacket!(1, 'delta');
+ await env.addVideoPacket!(1.5, 'delta');
+ await env.addVideoPacket!(2, 'key');
+ await env.addVideoPacket!(2.5, 'delta');
+
+ await env.addAudioPacket!(0);
+ await env.addAudioPacket!(0.5);
+ await env.addAudioPacket!(1);
+ await env.addAudioPacket!(1.5);
+ expect(env.segmentCount).toBe(0);
+ await env.addAudioPacket!(2);
+ expect(env.segmentCount).toBe(1);
+ expect(await env.lastSegmentVideoTimestamps).toEqual([0, 0.5, 1, 1.5]);
+ expect(await env.lastSegmentAudioTimestamps).toEqual([0, 0.5, 1, 1.5]);
+ await env.addAudioPacket!(2.5);
+ await env.addAudioPacket!(3);
+ await env.addAudioPacket!(3.5);
+ expect(env.segmentCount).toBe(1);
+
+ await env.output.finalize();
+ expect(env.segmentCount).toBe(2);
+ expect(await env.lastSegmentVideoTimestamps).toEqual([2, 2.5]);
+ expect(await env.lastSegmentAudioTimestamps).toEqual([2, 2.5, 3.0, 3.5]);
+
+ expect(env.result).toBe(`#EXTM3U
+#EXT-X-VERSION:3
+#EXT-X-PLAYLIST-TYPE:VOD
+#EXT-X-TARGETDURATION:2
+
+#EXTINF:2,
+segment-1-1.ts
+#EXTINF:1.5,
+segment-1-2.ts
+
+#EXT-X-ENDLIST
+`,
+ );
+});
+
+test('Segmentation, dual-track, audio ending after video in separate segment', async () => {
+ const env = await setUpSegmentationEnvironment({ video: true, audio: true });
+
+ await env.addVideoPacket!(0, 'key');
+ await env.addVideoPacket!(0.5, 'delta');
+ await env.addVideoPacket!(1, 'delta');
+ await env.addVideoPacket!(1.5, 'delta');
+ await env.addVideoPacket!(2, 'key');
+ await env.addVideoPacket!(2.5, 'delta');
+ await env.addVideoPacket!(3, 'delta');
+ await env.addVideoPacket!(3.5, 'delta');
+ await env.addVideoPacket!(4, 'delta');
+ await env.addVideoPacket!(4.5, 'delta');
+ env.videoSource!.close();
+
+ await env.addAudioPacket!(0);
+ await env.addAudioPacket!(0.5);
+ await env.addAudioPacket!(1);
+ await env.addAudioPacket!(1.5);
+ expect(env.segmentCount).toBe(0);
+ await env.addAudioPacket!(2);
+ expect(env.segmentCount).toBe(1);
+ expect(await env.lastSegmentVideoTimestamps).toEqual([0, 0.5, 1, 1.5]);
+ expect(await env.lastSegmentAudioTimestamps).toEqual([0, 0.5, 1, 1.5]);
+ await env.addAudioPacket!(2.5);
+ await env.addAudioPacket!(3);
+ await env.addAudioPacket!(3.5);
+ await env.addAudioPacket!(4);
+ await env.addAudioPacket!(4.5);
+ expect(env.segmentCount).toBe(1);
+ await env.addAudioPacket!(5);
+ expect(env.segmentCount).toBe(2);
+ expect(await env.lastSegmentVideoTimestamps).toEqual([2, 2.5, 3, 3.5, 4, 4.5]);
+ expect(await env.lastSegmentAudioTimestamps).toEqual([2, 2.5, 3, 3.5, 4, 4.5]);
+ await env.addAudioPacket!(5.5);
+
+ await env.output.finalize();
+ expect(env.segmentCount).toBe(3);
+ expect(await env.lastSegmentVideoTimestamps).toEqual([]);
+ expect(await env.lastSegmentAudioTimestamps).toEqual([5, 5.5]);
+
+ expect(env.result).toBe(`#EXTM3U
+#EXT-X-VERSION:3
+#EXT-X-PLAYLIST-TYPE:VOD
+#EXT-X-TARGETDURATION:3
+
+#EXTINF:2,
+segment-1-1.ts
+#EXTINF:3,
+segment-1-2.ts
+#EXTINF:0.5,
+segment-1-3.ts
+
+#EXT-X-ENDLIST
+`,
+ );
+});
+
+test('Segmentation, dual-track, end timestamp with duration', async () => {
+ const env = await setUpSegmentationEnvironment({ video: true, audio: true });
+
+ await env.addVideoPacket!(0, 'key');
+ await env.addVideoPacket!(0.5, 'delta');
+ await env.addVideoPacket!(1, 'delta');
+ await env.addVideoPacket!(1.5, 'delta', 0.25);
+
+ await env.addAudioPacket!(0);
+ await env.addAudioPacket!(0.5);
+ await env.addAudioPacket!(1);
+ await env.addAudioPacket!(1.5, 0.3);
+ expect(env.segmentCount).toBe(0);
+
+ await env.output.finalize();
+ expect(env.segmentCount).toBe(1);
+ expect(await env.lastSegmentVideoTimestamps).toEqual([0, 0.5, 1, 1.5]);
+ expect(await env.lastSegmentAudioTimestamps).toEqual([0, 0.5, 1, 1.5]);
+
+ expect(env.result).toBe(`#EXTM3U
+#EXT-X-VERSION:3
+#EXT-X-PLAYLIST-TYPE:VOD
+#EXT-X-TARGETDURATION:2
+
+#EXTINF:1.8,
+segment-1-1.ts
+
+#EXT-X-ENDLIST
+`,
+ );
+});
+
+test('Segmentation, dual-track, closing writes segment', async () => {
+ const env = await setUpSegmentationEnvironment({ video: true, audio: true });
+
+ await env.addVideoPacket!(0, 'key');
+ await env.addVideoPacket!(0.5, 'delta');
+ await env.addVideoPacket!(1, 'delta');
+ await env.addVideoPacket!(1.5, 'delta');
+
+ await env.addAudioPacket!(0);
+ await env.addAudioPacket!(0.5);
+ await env.addAudioPacket!(1);
+ await env.addAudioPacket!(1.5);
+
+ expect(env.segmentCount).toBe(0);
+
+ env.videoSource!.close();
+ await new Promise(resolve => setTimeout(resolve, 4));
+
+ expect(env.segmentCount).toBe(0);
+
+ env.audioSource!.close();
+ await new Promise(resolve => setTimeout(resolve, 4));
+
+ expect(env.segmentCount).toBe(1);
+
+ await env.output.finalize();
+
+ expect(env.result).toBe(`#EXTM3U
+#EXT-X-VERSION:3
+#EXT-X-PLAYLIST-TYPE:VOD
+#EXT-X-TARGETDURATION:2
+
+#EXTINF:1.5,
+segment-1-1.ts
+
+#EXT-X-ENDLIST
+`,
+ );
+});
diff --git a/test/node/join-paths.test.ts b/test/node/join-paths.test.ts
index 946d121..1c391ce 100644
--- a/test/node/join-paths.test.ts
+++ b/test/node/join-paths.test.ts
@@ -1,7 +1,7 @@
import { expect, test } from 'vitest';
import { joinPaths } from '../../src/misc.js';
-test('joinPaths handles all path combinations correctly', () => {
+test('joinPaths', () => {
// Simple relative paths
expect(joinPaths('path/to/entry.m3u8', 'other.m3u8')).toBe('path/to/other.m3u8');
expect(joinPaths('entry.m3u8', 'other.m3u8')).toBe('other.m3u8');
@@ -36,6 +36,19 @@ test('joinPaths handles all path combinations correctly', () => {
expect(joinPaths('path/to/entry.m3u8', './../other.m3u8')).toBe('path/other.m3u8');
expect(joinPaths('path/to/entry.m3u8', '../foo/../other.m3u8')).toBe('path/other.m3u8');
+ // URL base path with query parameters
+ expect(joinPaths('https://example.com/path/to/entry.m3u8?token=abc', 'other.m3u8'))
+ .toBe('https://example.com/path/to/other.m3u8');
+ expect(joinPaths('https://example.com/path/to/entry.m3u8?redirect=/foo/bar', 'other.m3u8'))
+ .toBe('https://example.com/path/to/other.m3u8');
+ expect(joinPaths('https://example.com/path/to/entry.m3u8?token=abc', '/other.m3u8'))
+ .toBe('https://example.com/other.m3u8');
+ expect(joinPaths('https://example.com/path/to/entry.m3u8?token=abc', '../other.m3u8'))
+ .toBe('https://example.com/path/other.m3u8');
+
+ // Filesystem paths with ? in the name are preserved
+ expect(joinPaths('path/to/sus?directory/file.m3u8', 'other.m3u8')).toBe('path/to/sus?directory/other.m3u8');
+
// Second argument is a full URL with protocol
expect(joinPaths('path/to/entry.m3u8', 'https://example.com/other.m3u8'))
.toBe('https://example.com/other.m3u8');
diff --git a/todo.txt b/todo.txt
index d7be6ec..6979828 100644
--- a/todo.txt
+++ b/todo.txt
@@ -1,9 +1,15 @@
-- Retaining Input instances for segments without clogging up memory indefinitely
-
idea: discontinuities with extra decoder config on the packet.
Also, why not just have the packet metadata on the packet? I think that would make things easier overall.
-- ALL_FORMATS but for HLS only
-
- keep input/demuxer.isSupported()?
-- getCodecParamterString() hack feels dirty; idk. well its not really a hack but, surface it over a different field?
\ No newline at end of file
+- getCodecParamterString() hack feels dirty; idk. well its not really a hack but, surface it over a different field?
+
+- Add an HLS "live mode". Challenge, where does bitrate come from?
+
+events:
+- onPlaylist
+- onSegment
+- onMaster
+
+- hls muxer starting offset
+- hls demuxer offset by segment number * targetduration
\ No newline at end of file