mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-09-27 02:43:48 +02:00
Add HLS master playlist generation, add multi-playlistadd output track grouping, add multi-playlist HLS files, add configurable playlist and segment names, add TrackDisposition.primary
This commit is contained in:
@@ -414,6 +414,46 @@ test.concurrent('fMP4', { timeout: 15_000 }, async () => {
|
||||
expect(sourceCount).toBe(1 + 2 * (1 + 1 + 1 + 1));
|
||||
});
|
||||
|
||||
test.concurrent('Track disposition & metadata', { timeout: 15_000 }, async () => {
|
||||
using input = new Input({
|
||||
entryPath: 'https://storage.googleapis.com/shaka-demo-assets/angel-one-hls/hls.m3u8',
|
||||
source: ({ path }) => new UrlSource(path),
|
||||
formats: ALL_FORMATS,
|
||||
});
|
||||
|
||||
const audioTracks = await input.getAudioTracks();
|
||||
|
||||
expect(audioTracks).toHaveLength(6);
|
||||
|
||||
expect(audioTracks[0]!.languageCode).toBe('en');
|
||||
expect(audioTracks[1]!.languageCode).toBe('de');
|
||||
expect(audioTracks[2]!.languageCode).toBe('it');
|
||||
expect(audioTracks[3]!.languageCode).toBe('fr');
|
||||
expect(audioTracks[4]!.languageCode).toBe('es');
|
||||
expect(audioTracks[5]!.languageCode).toBe('en');
|
||||
|
||||
expect(audioTracks[0]!.disposition.primary).toBe(true);
|
||||
expect(audioTracks[1]!.disposition.primary).toBe(false);
|
||||
expect(audioTracks[2]!.disposition.primary).toBe(false);
|
||||
expect(audioTracks[3]!.disposition.primary).toBe(false);
|
||||
expect(audioTracks[4]!.disposition.primary).toBe(false);
|
||||
expect(audioTracks[5]!.disposition.primary).toBe(false);
|
||||
|
||||
expect(audioTracks[0]!.disposition.default).toBe(true);
|
||||
expect(audioTracks[1]!.disposition.default).toBe(true);
|
||||
expect(audioTracks[2]!.disposition.default).toBe(true);
|
||||
expect(audioTracks[3]!.disposition.default).toBe(true);
|
||||
expect(audioTracks[4]!.disposition.default).toBe(true);
|
||||
expect(audioTracks[5]!.disposition.default).toBe(false);
|
||||
|
||||
expect(audioTracks[0]!.name).toBe('stream_5');
|
||||
expect(audioTracks[1]!.name).toBe('stream_4');
|
||||
expect(audioTracks[2]!.name).toBe('stream_8');
|
||||
expect(audioTracks[3]!.name).toBe('stream_7');
|
||||
expect(audioTracks[4]!.name).toBe('stream_9');
|
||||
expect(audioTracks[5]!.name).toBe('stream_6');
|
||||
});
|
||||
|
||||
test.concurrent('fMP4 Bitmovin', { timeout: 15_000 }, async () => {
|
||||
using input = new Input({
|
||||
entryPath: 'https://bitdash-a.akamaihd.net/content/MI201109210084_1/m3u8s-fmp4/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.m3u8',
|
||||
|
||||
@@ -0,0 +1,793 @@
|
||||
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 { EncodedAudioPacketSource, EncodedVideoPacketSource } from '../../src/media-source.js';
|
||||
import { HlsMuxer } from '../../src/hls/hls-muxer.js';
|
||||
import { AudioCodec, VideoCodec } from '../../src/codec.js';
|
||||
|
||||
const videoSource = (codec: VideoCodec = 'avc') => new EncodedVideoPacketSource(codec);
|
||||
const audioSource = (codec: AudioCodec = 'aac') => new EncodedAudioPacketSource(codec);
|
||||
|
||||
test('Playlist assignment, single video', async () => {
|
||||
const output = new Output({
|
||||
format: new HlsOutputFormat({
|
||||
segmentFormats: HLS_OUTPUT_FORMATS_DEFAULT,
|
||||
}),
|
||||
target: () => new NullTarget(),
|
||||
rootPath: '',
|
||||
});
|
||||
|
||||
output.addVideoTrack(videoSource());
|
||||
|
||||
await output.start();
|
||||
|
||||
const muxer = output._muxer as HlsMuxer;
|
||||
const decl = muxer.playlistDeclarations;
|
||||
|
||||
expect(decl).toHaveLength(1);
|
||||
expect(decl[0]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[0]!.groupId).toBeNull();
|
||||
expect(decl[0]!.references).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('Playlist assignment, single audio', async () => {
|
||||
const output = new Output({
|
||||
format: new HlsOutputFormat({
|
||||
segmentFormats: HLS_OUTPUT_FORMATS_DEFAULT,
|
||||
}),
|
||||
target: () => new NullTarget(),
|
||||
rootPath: '',
|
||||
});
|
||||
|
||||
output.addAudioTrack(audioSource());
|
||||
|
||||
await output.start();
|
||||
|
||||
const muxer = output._muxer as HlsMuxer;
|
||||
const decl = muxer.playlistDeclarations;
|
||||
|
||||
expect(decl).toHaveLength(1);
|
||||
expect(decl[0]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[0]!.groupId).toBeNull();
|
||||
expect(decl[0]!.references).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('Playlist assignment, multiple video', async () => {
|
||||
const output = new Output({
|
||||
format: new HlsOutputFormat({
|
||||
segmentFormats: HLS_OUTPUT_FORMATS_DEFAULT,
|
||||
}),
|
||||
target: () => new NullTarget(),
|
||||
rootPath: '',
|
||||
});
|
||||
|
||||
output.addVideoTrack(videoSource());
|
||||
output.addVideoTrack(videoSource());
|
||||
|
||||
await output.start();
|
||||
|
||||
const muxer = output._muxer as HlsMuxer;
|
||||
const decl = muxer.playlistDeclarations;
|
||||
|
||||
expect(decl).toHaveLength(2);
|
||||
|
||||
expect(decl[0]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[0]!.groupId).toBeNull();
|
||||
expect(decl[0]!.references).toHaveLength(0);
|
||||
|
||||
expect(decl[1]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[1]!.groupId).toBeNull();
|
||||
expect(decl[1]!.references).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('Playlist assignment, multiple audio', async () => {
|
||||
const output = new Output({
|
||||
format: new HlsOutputFormat({
|
||||
segmentFormats: HLS_OUTPUT_FORMATS_DEFAULT,
|
||||
}),
|
||||
target: () => new NullTarget(),
|
||||
rootPath: '',
|
||||
});
|
||||
|
||||
output.addAudioTrack(audioSource());
|
||||
output.addAudioTrack(audioSource());
|
||||
|
||||
await output.start();
|
||||
|
||||
const muxer = output._muxer as HlsMuxer;
|
||||
const decl = muxer.playlistDeclarations;
|
||||
|
||||
expect(decl).toHaveLength(2);
|
||||
|
||||
expect(decl[0]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[0]!.groupId).toBeNull();
|
||||
expect(decl[0]!.references).toHaveLength(0);
|
||||
|
||||
expect(decl[1]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[1]!.groupId).toBeNull();
|
||||
expect(decl[1]!.references).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('Playlist assignment, multiple video with different metadata #1', async () => {
|
||||
const output = new Output({
|
||||
format: new HlsOutputFormat({
|
||||
segmentFormats: HLS_OUTPUT_FORMATS_DEFAULT,
|
||||
}),
|
||||
target: () => new NullTarget(),
|
||||
rootPath: '',
|
||||
});
|
||||
|
||||
output.addVideoTrack(videoSource(), { languageCode: 'eng' });
|
||||
output.addVideoTrack(videoSource(), { languageCode: 'esp' });
|
||||
|
||||
await output.start();
|
||||
|
||||
const muxer = output._muxer as HlsMuxer;
|
||||
const decl = muxer.playlistDeclarations;
|
||||
|
||||
expect(decl).toHaveLength(3);
|
||||
|
||||
expect(decl[0]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[0]!.groupId).toBe('video-1');
|
||||
expect(decl[0]!.references).toHaveLength(0);
|
||||
expect(decl[0]!.noUri).toBe(true);
|
||||
|
||||
expect(decl[1]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[1]!.groupId).toBe('video-1');
|
||||
expect(decl[1]!.references).toHaveLength(0);
|
||||
expect(decl[1]!.noUri).toBe(false);
|
||||
|
||||
expect(decl[2]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[2]!.groupId).toBeNull();
|
||||
expect(decl[2]!.references).toEqual(decl.slice(0, 2));
|
||||
|
||||
expect(decl[2]!.playlist).toBe(decl[0]!.playlist);
|
||||
});
|
||||
|
||||
test('Playlist assignment, multiple video with different metadata #2', async () => {
|
||||
const output = new Output({
|
||||
format: new HlsOutputFormat({
|
||||
segmentFormats: HLS_OUTPUT_FORMATS_DEFAULT,
|
||||
}),
|
||||
target: () => new NullTarget(),
|
||||
rootPath: '',
|
||||
});
|
||||
|
||||
output.addVideoTrack(videoSource(), { disposition: { primary: true } });
|
||||
output.addVideoTrack(videoSource(), { disposition: { primary: false } });
|
||||
|
||||
await output.start();
|
||||
|
||||
const muxer = output._muxer as HlsMuxer;
|
||||
const decl = muxer.playlistDeclarations;
|
||||
|
||||
expect(decl).toHaveLength(3);
|
||||
|
||||
expect(decl[0]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[0]!.groupId).toBe('video-1');
|
||||
expect(decl[0]!.references).toHaveLength(0);
|
||||
expect(decl[0]!.noUri).toBe(true);
|
||||
|
||||
expect(decl[1]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[1]!.groupId).toBe('video-1');
|
||||
expect(decl[1]!.references).toHaveLength(0);
|
||||
expect(decl[1]!.noUri).toBe(false);
|
||||
|
||||
expect(decl[2]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[2]!.groupId).toBeNull();
|
||||
expect(decl[2]!.references).toEqual(decl.slice(0, 2));
|
||||
|
||||
expect(decl[2]!.playlist).toBe(decl[0]!.playlist);
|
||||
});
|
||||
|
||||
test('Playlist assignment, multiple audio with different metadata', async () => {
|
||||
const output = new Output({
|
||||
format: new HlsOutputFormat({
|
||||
segmentFormats: HLS_OUTPUT_FORMATS_DEFAULT,
|
||||
}),
|
||||
target: () => new NullTarget(),
|
||||
rootPath: '',
|
||||
});
|
||||
|
||||
output.addAudioTrack(audioSource(), { languageCode: 'eng' });
|
||||
output.addAudioTrack(audioSource(), { languageCode: 'esp' });
|
||||
|
||||
await output.start();
|
||||
|
||||
const muxer = output._muxer as HlsMuxer;
|
||||
const decl = muxer.playlistDeclarations;
|
||||
|
||||
expect(decl).toHaveLength(3);
|
||||
|
||||
expect(decl[0]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[0]!.groupId).toBe('audio-1');
|
||||
expect(decl[0]!.references).toHaveLength(0);
|
||||
expect(decl[0]!.noUri).toBe(true);
|
||||
|
||||
expect(decl[1]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[1]!.groupId).toBe('audio-1');
|
||||
expect(decl[1]!.references).toHaveLength(0);
|
||||
expect(decl[1]!.noUri).toBe(false);
|
||||
|
||||
expect(decl[2]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[2]!.groupId).toBeNull();
|
||||
expect(decl[2]!.references).toEqual(decl.slice(0, 2));
|
||||
|
||||
expect(decl[2]!.playlist).toBe(decl[0]!.playlist);
|
||||
});
|
||||
|
||||
test('Playlist assignment, video and audio', async () => {
|
||||
const output = new Output({
|
||||
format: new HlsOutputFormat({
|
||||
segmentFormats: HLS_OUTPUT_FORMATS_DEFAULT,
|
||||
}),
|
||||
target: () => new NullTarget(),
|
||||
rootPath: '',
|
||||
});
|
||||
|
||||
output.addVideoTrack(videoSource());
|
||||
output.addAudioTrack(audioSource());
|
||||
|
||||
await output.start();
|
||||
|
||||
const muxer = output._muxer as HlsMuxer;
|
||||
const decl = muxer.playlistDeclarations;
|
||||
|
||||
expect(decl).toHaveLength(1);
|
||||
expect(decl[0]!.playlist.tracks).toHaveLength(2);
|
||||
expect(decl[0]!.groupId).toBeNull();
|
||||
expect(decl[0]!.references).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('Playlist assignment, one video and multiple audio', async () => {
|
||||
const output = new Output({
|
||||
format: new HlsOutputFormat({
|
||||
segmentFormats: HLS_OUTPUT_FORMATS_DEFAULT,
|
||||
}),
|
||||
target: () => new NullTarget(),
|
||||
rootPath: '',
|
||||
});
|
||||
|
||||
output.addVideoTrack(videoSource());
|
||||
output.addAudioTrack(audioSource());
|
||||
output.addAudioTrack(audioSource());
|
||||
output.addAudioTrack(audioSource());
|
||||
|
||||
await output.start();
|
||||
|
||||
const muxer = output._muxer as HlsMuxer;
|
||||
const decl = muxer.playlistDeclarations;
|
||||
|
||||
expect(decl).toHaveLength(4);
|
||||
|
||||
expect(decl[0]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[0]!.playlist.tracks[0]!.type).toBe('audio');
|
||||
expect(decl[0]!.groupId).toBe('audio-1');
|
||||
expect(decl[0]!.noUri).toBe(false);
|
||||
|
||||
expect(decl[1]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[1]!.playlist.tracks[0]!.type).toBe('audio');
|
||||
expect(decl[0]!.groupId).toBe('audio-1');
|
||||
expect(decl[1]!.noUri).toBe(false);
|
||||
|
||||
expect(decl[2]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[2]!.playlist.tracks[0]!.type).toBe('audio');
|
||||
expect(decl[0]!.groupId).toBe('audio-1');
|
||||
expect(decl[2]!.noUri).toBe(false);
|
||||
|
||||
expect(decl[3]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[3]!.playlist.tracks[0]!.type).toBe('video');
|
||||
expect(decl[3]!.groupId).toBeNull();
|
||||
expect(decl[3]!.references).toEqual(decl.slice(0, 3));
|
||||
});
|
||||
|
||||
test('Playlist assignment, multiple video and one audio', async () => {
|
||||
const output = new Output({
|
||||
format: new HlsOutputFormat({
|
||||
segmentFormats: HLS_OUTPUT_FORMATS_DEFAULT,
|
||||
}),
|
||||
target: () => new NullTarget(),
|
||||
rootPath: '',
|
||||
});
|
||||
|
||||
output.addVideoTrack(videoSource());
|
||||
output.addVideoTrack(videoSource());
|
||||
output.addVideoTrack(videoSource());
|
||||
output.addAudioTrack(audioSource());
|
||||
|
||||
await output.start();
|
||||
|
||||
const muxer = output._muxer as HlsMuxer;
|
||||
const decl = muxer.playlistDeclarations;
|
||||
|
||||
expect(decl).toHaveLength(4);
|
||||
|
||||
expect(decl[0]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[0]!.playlist.tracks[0]!.type).toBe('audio');
|
||||
expect(decl[0]!.groupId).toBe('audio-1');
|
||||
expect(decl[0]!.noUri).toBe(false);
|
||||
|
||||
expect(decl[1]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[1]!.playlist.tracks[0]!.type).toBe('video');
|
||||
expect(decl[1]!.groupId).toBeNull();
|
||||
expect(decl[1]!.references).toEqual([decl[0]!]);
|
||||
|
||||
expect(decl[2]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[2]!.playlist.tracks[0]!.type).toBe('video');
|
||||
expect(decl[2]!.groupId).toBeNull();
|
||||
expect(decl[2]!.references).toEqual([decl[0]!]);
|
||||
|
||||
expect(decl[3]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[3]!.playlist.tracks[0]!.type).toBe('video');
|
||||
expect(decl[3]!.groupId).toBeNull();
|
||||
expect(decl[3]!.references).toEqual([decl[0]!]);
|
||||
});
|
||||
|
||||
test('Playlist assignment, multiple video and audio', async () => {
|
||||
const output = new Output({
|
||||
format: new HlsOutputFormat({
|
||||
segmentFormats: HLS_OUTPUT_FORMATS_DEFAULT,
|
||||
}),
|
||||
target: () => new NullTarget(),
|
||||
rootPath: '',
|
||||
});
|
||||
|
||||
output.addVideoTrack(videoSource());
|
||||
output.addVideoTrack(videoSource());
|
||||
output.addVideoTrack(videoSource());
|
||||
output.addAudioTrack(audioSource());
|
||||
output.addAudioTrack(audioSource());
|
||||
output.addAudioTrack(audioSource());
|
||||
|
||||
await output.start();
|
||||
|
||||
const muxer = output._muxer as HlsMuxer;
|
||||
const decl = muxer.playlistDeclarations;
|
||||
|
||||
expect(decl).toHaveLength(6);
|
||||
|
||||
expect(decl[0]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[0]!.playlist.tracks[0]!.type).toBe('audio');
|
||||
expect(decl[0]!.groupId).toBe('audio-1');
|
||||
expect(decl[0]!.noUri).toBe(false);
|
||||
|
||||
expect(decl[1]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[1]!.playlist.tracks[0]!.type).toBe('audio');
|
||||
expect(decl[1]!.groupId).toBe('audio-1');
|
||||
expect(decl[1]!.noUri).toBe(false);
|
||||
|
||||
expect(decl[2]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[2]!.playlist.tracks[0]!.type).toBe('audio');
|
||||
expect(decl[2]!.groupId).toBe('audio-1');
|
||||
expect(decl[2]!.noUri).toBe(false);
|
||||
|
||||
expect(decl[3]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[3]!.playlist.tracks[0]!.type).toBe('video');
|
||||
expect(decl[3]!.groupId).toBeNull();
|
||||
expect(decl[3]!.references).toEqual(decl.slice(0, 3));
|
||||
|
||||
expect(decl[4]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[4]!.playlist.tracks[0]!.type).toBe('video');
|
||||
expect(decl[4]!.groupId).toBeNull();
|
||||
expect(decl[4]!.references).toEqual(decl.slice(0, 3));
|
||||
|
||||
expect(decl[5]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[5]!.playlist.tracks[0]!.type).toBe('video');
|
||||
expect(decl[5]!.groupId).toBeNull();
|
||||
expect(decl[5]!.references).toEqual(decl.slice(0, 3));
|
||||
});
|
||||
|
||||
test('Playlist assignment, video and audio in different groups', async () => {
|
||||
const output = new Output({
|
||||
format: new HlsOutputFormat({
|
||||
segmentFormats: HLS_OUTPUT_FORMATS_DEFAULT,
|
||||
}),
|
||||
target: () => new NullTarget(),
|
||||
rootPath: '',
|
||||
});
|
||||
|
||||
const a = new OutputTrackGroup();
|
||||
const b = new OutputTrackGroup();
|
||||
|
||||
output.addVideoTrack(videoSource(), { group: a });
|
||||
output.addAudioTrack(audioSource(), { group: b });
|
||||
|
||||
await output.start();
|
||||
|
||||
const muxer = output._muxer as HlsMuxer;
|
||||
const decl = muxer.playlistDeclarations;
|
||||
|
||||
expect(decl).toHaveLength(2);
|
||||
|
||||
expect(decl[0]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[0]!.playlist.tracks[0]!.type).toBe('video');
|
||||
expect(decl[0]!.groupId).toBeNull();
|
||||
expect(decl[0]!.references).toHaveLength(0);
|
||||
|
||||
expect(decl[1]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[1]!.playlist.tracks[0]!.type).toBe('audio');
|
||||
expect(decl[1]!.groupId).toBeNull();
|
||||
expect(decl[1]!.references).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('Playlist assignment, multiple video and audio in pairs', async () => {
|
||||
const output = new Output({
|
||||
format: new HlsOutputFormat({
|
||||
segmentFormats: HLS_OUTPUT_FORMATS_DEFAULT,
|
||||
}),
|
||||
target: () => new NullTarget(),
|
||||
rootPath: '',
|
||||
});
|
||||
|
||||
const a = new OutputTrackGroup();
|
||||
const b = new OutputTrackGroup();
|
||||
const c = new OutputTrackGroup();
|
||||
|
||||
output.addVideoTrack(videoSource(), { group: a });
|
||||
output.addVideoTrack(videoSource(), { group: b });
|
||||
output.addVideoTrack(videoSource(), { group: c });
|
||||
output.addAudioTrack(audioSource(), { group: a });
|
||||
output.addAudioTrack(audioSource(), { group: b });
|
||||
output.addAudioTrack(audioSource(), { group: c });
|
||||
|
||||
await output.start();
|
||||
|
||||
const muxer = output._muxer as HlsMuxer;
|
||||
const decl = muxer.playlistDeclarations;
|
||||
|
||||
expect(decl).toHaveLength(3);
|
||||
|
||||
expect(decl[0]!.playlist.tracks).toHaveLength(2);
|
||||
expect(decl[0]!.playlist.tracks[0]!.type).toBe('video');
|
||||
expect(decl[0]!.playlist.tracks[1]!.type).toBe('audio');
|
||||
expect(decl[0]!.groupId).toBeNull();
|
||||
expect(decl[0]!.references).toHaveLength(0);
|
||||
|
||||
expect(decl[1]!.playlist.tracks).toHaveLength(2);
|
||||
expect(decl[1]!.playlist.tracks[0]!.type).toBe('video');
|
||||
expect(decl[1]!.playlist.tracks[1]!.type).toBe('audio');
|
||||
expect(decl[1]!.groupId).toBeNull();
|
||||
expect(decl[1]!.references).toHaveLength(0);
|
||||
|
||||
expect(decl[2]!.playlist.tracks).toHaveLength(2);
|
||||
expect(decl[2]!.playlist.tracks[0]!.type).toBe('video');
|
||||
expect(decl[2]!.playlist.tracks[1]!.type).toBe('audio');
|
||||
expect(decl[2]!.groupId).toBeNull();
|
||||
expect(decl[2]!.references).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('Playlist assignment, multiple video and audio with some unpaired', async () => {
|
||||
const output = new Output({
|
||||
format: new HlsOutputFormat({
|
||||
segmentFormats: HLS_OUTPUT_FORMATS_DEFAULT,
|
||||
}),
|
||||
target: () => new NullTarget(),
|
||||
rootPath: '',
|
||||
});
|
||||
|
||||
const a = new OutputTrackGroup();
|
||||
const b = new OutputTrackGroup();
|
||||
const c = new OutputTrackGroup();
|
||||
|
||||
output.addVideoTrack(videoSource(), { group: a });
|
||||
output.addVideoTrack(videoSource(), { group: a });
|
||||
output.addVideoTrack(videoSource(), { group: b });
|
||||
output.addAudioTrack(audioSource(), { group: a });
|
||||
output.addAudioTrack(audioSource(), { group: a });
|
||||
output.addAudioTrack(audioSource(), { group: c });
|
||||
|
||||
await output.start();
|
||||
|
||||
const muxer = output._muxer as HlsMuxer;
|
||||
const decl = muxer.playlistDeclarations;
|
||||
|
||||
expect(decl).toHaveLength(6);
|
||||
|
||||
expect(decl[0]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[0]!.playlist.tracks[0]!.type).toBe('audio');
|
||||
expect(decl[0]!.groupId).toBe('audio-1');
|
||||
expect(decl[0]!.noUri).toBe(false);
|
||||
|
||||
expect(decl[1]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[1]!.playlist.tracks[0]!.type).toBe('audio');
|
||||
expect(decl[1]!.groupId).toBe('audio-1');
|
||||
expect(decl[1]!.noUri).toBe(false);
|
||||
|
||||
expect(decl[2]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[2]!.playlist.tracks[0]!.type).toBe('video');
|
||||
expect(decl[2]!.groupId).toBeNull();
|
||||
expect(decl[2]!.references).toEqual(decl.slice(0, 2));
|
||||
|
||||
expect(decl[3]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[3]!.playlist.tracks[0]!.type).toBe('video');
|
||||
expect(decl[3]!.groupId).toBeNull();
|
||||
expect(decl[3]!.references).toEqual(decl.slice(0, 2));
|
||||
|
||||
expect(decl[4]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[4]!.playlist.tracks[0]!.type).toBe('video');
|
||||
expect(decl[4]!.groupId).toBeNull();
|
||||
expect(decl[4]!.references).toHaveLength(0);
|
||||
|
||||
expect(decl[5]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[5]!.playlist.tracks[0]!.type).toBe('audio');
|
||||
expect(decl[5]!.groupId).toBeNull();
|
||||
expect(decl[5]!.references).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('Playlist assignment, multiple video and audio with multiple groups', async () => {
|
||||
const output = new Output({
|
||||
format: new HlsOutputFormat({
|
||||
segmentFormats: HLS_OUTPUT_FORMATS_DEFAULT,
|
||||
}),
|
||||
target: () => new NullTarget(),
|
||||
rootPath: '',
|
||||
});
|
||||
|
||||
const a = new OutputTrackGroup();
|
||||
const b = new OutputTrackGroup();
|
||||
|
||||
output.addVideoTrack(videoSource(), { group: a });
|
||||
output.addVideoTrack(videoSource(), { group: a });
|
||||
output.addVideoTrack(videoSource(), { group: b });
|
||||
output.addVideoTrack(videoSource(), { group: b });
|
||||
output.addAudioTrack(audioSource(), { group: a });
|
||||
output.addAudioTrack(audioSource(), { group: a });
|
||||
output.addAudioTrack(audioSource(), { group: b });
|
||||
output.addAudioTrack(audioSource(), { group: b });
|
||||
|
||||
await output.start();
|
||||
|
||||
const muxer = output._muxer as HlsMuxer;
|
||||
const decl = muxer.playlistDeclarations;
|
||||
|
||||
expect(decl).toHaveLength(8);
|
||||
|
||||
expect(decl[0]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[0]!.playlist.tracks[0]!.type).toBe('audio');
|
||||
expect(decl[0]!.groupId).toBe('audio-1');
|
||||
expect(decl[0]!.noUri).toBe(false);
|
||||
|
||||
expect(decl[1]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[1]!.playlist.tracks[0]!.type).toBe('audio');
|
||||
expect(decl[1]!.groupId).toBe('audio-1');
|
||||
expect(decl[1]!.noUri).toBe(false);
|
||||
|
||||
expect(decl[2]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[2]!.playlist.tracks[0]!.type).toBe('audio');
|
||||
expect(decl[2]!.groupId).toBe('audio-2');
|
||||
expect(decl[2]!.noUri).toBe(false);
|
||||
|
||||
expect(decl[3]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[3]!.playlist.tracks[0]!.type).toBe('audio');
|
||||
expect(decl[3]!.groupId).toBe('audio-2');
|
||||
expect(decl[3]!.noUri).toBe(false);
|
||||
|
||||
expect(decl[4]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[4]!.playlist.tracks[0]!.type).toBe('video');
|
||||
expect(decl[4]!.groupId).toBeNull();
|
||||
expect(decl[4]!.references).toEqual(decl.slice(0, 2));
|
||||
|
||||
expect(decl[5]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[5]!.playlist.tracks[0]!.type).toBe('video');
|
||||
expect(decl[5]!.groupId).toBeNull();
|
||||
expect(decl[5]!.references).toEqual(decl.slice(0, 2));
|
||||
|
||||
expect(decl[6]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[6]!.playlist.tracks[0]!.type).toBe('video');
|
||||
expect(decl[6]!.groupId).toBeNull();
|
||||
expect(decl[6]!.references).toEqual(decl.slice(2, 4));
|
||||
|
||||
expect(decl[7]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[7]!.playlist.tracks[0]!.type).toBe('video');
|
||||
expect(decl[7]!.groupId).toBeNull();
|
||||
expect(decl[7]!.references).toEqual(decl.slice(2, 4));
|
||||
});
|
||||
|
||||
test('Playlist assignment, video with multiple audio codecs', async () => {
|
||||
const output = new Output({
|
||||
format: new HlsOutputFormat({
|
||||
segmentFormats: HLS_OUTPUT_FORMATS_DEFAULT,
|
||||
}),
|
||||
target: () => new NullTarget(),
|
||||
rootPath: '',
|
||||
});
|
||||
|
||||
output.addVideoTrack(videoSource());
|
||||
output.addAudioTrack(audioSource('aac'));
|
||||
output.addAudioTrack(audioSource('ac3'));
|
||||
|
||||
await output.start();
|
||||
|
||||
const muxer = output._muxer as HlsMuxer;
|
||||
const decl = muxer.playlistDeclarations;
|
||||
|
||||
expect(decl).toHaveLength(4);
|
||||
|
||||
expect(decl[0]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[0]!.playlist.tracks[0]!.type).toBe('audio');
|
||||
expect(decl[0]!.playlist.tracks[0]!.source._codec).toBe('aac');
|
||||
expect(decl[0]!.groupId).toBe('audio-1');
|
||||
expect(decl[0]!.noUri).toBe(false);
|
||||
|
||||
expect(decl[1]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[1]!.playlist.tracks[0]!.type).toBe('audio');
|
||||
expect(decl[1]!.playlist.tracks[0]!.source._codec).toBe('ac3');
|
||||
expect(decl[1]!.groupId).toBe('audio-2');
|
||||
expect(decl[1]!.noUri).toBe(false);
|
||||
|
||||
expect(decl[2]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[2]!.playlist.tracks[0]!.type).toBe('video');
|
||||
expect(decl[2]!.groupId).toBeNull();
|
||||
expect(decl[2]!.references).toEqual([decl[0]!]);
|
||||
|
||||
expect(decl[3]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[3]!.playlist.tracks[0]!.type).toBe('video');
|
||||
expect(decl[3]!.groupId).toBeNull();
|
||||
expect(decl[3]!.references).toEqual([decl[1]!]);
|
||||
|
||||
expect(decl[2]!.playlist).toBe(decl[3]!.playlist);
|
||||
});
|
||||
|
||||
test('Playlist assignment, audio with multiple video codecs', async () => {
|
||||
const output = new Output({
|
||||
format: new HlsOutputFormat({
|
||||
segmentFormats: HLS_OUTPUT_FORMATS_DEFAULT,
|
||||
}),
|
||||
target: () => new NullTarget(),
|
||||
rootPath: '',
|
||||
});
|
||||
|
||||
output.addVideoTrack(videoSource('avc'));
|
||||
output.addVideoTrack(videoSource('hevc'));
|
||||
output.addAudioTrack(audioSource());
|
||||
|
||||
await output.start();
|
||||
|
||||
const muxer = output._muxer as HlsMuxer;
|
||||
const decl = muxer.playlistDeclarations;
|
||||
|
||||
expect(decl).toHaveLength(3);
|
||||
|
||||
expect(decl[0]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[0]!.playlist.tracks[0]!.type).toBe('audio');
|
||||
expect(decl[0]!.groupId).toBe('audio-1');
|
||||
expect(decl[0]!.noUri).toBe(false);
|
||||
|
||||
expect(decl[1]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[1]!.playlist.tracks[0]!.type).toBe('video');
|
||||
expect(decl[1]!.playlist.tracks[0]!.source._codec).toBe('avc');
|
||||
expect(decl[1]!.groupId).toBeNull();
|
||||
expect(decl[1]!.references).toEqual([decl[0]!]);
|
||||
|
||||
expect(decl[2]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[2]!.playlist.tracks[0]!.type).toBe('video');
|
||||
expect(decl[2]!.playlist.tracks[0]!.source._codec).toBe('hevc');
|
||||
expect(decl[2]!.groupId).toBeNull();
|
||||
expect(decl[2]!.references).toEqual([decl[0]!]);
|
||||
});
|
||||
|
||||
test('Playlist assignment, multiple video with conflicting audio interests', async () => {
|
||||
const output = new Output({
|
||||
format: new HlsOutputFormat({
|
||||
segmentFormats: HLS_OUTPUT_FORMATS_DEFAULT,
|
||||
}),
|
||||
target: () => new NullTarget(),
|
||||
rootPath: '',
|
||||
});
|
||||
|
||||
const a = new OutputTrackGroup();
|
||||
const b = new OutputTrackGroup();
|
||||
|
||||
output.addVideoTrack(videoSource(), { group: a });
|
||||
output.addVideoTrack(videoSource(), { group: b });
|
||||
output.addAudioTrack(audioSource(), { group: [a, b] });
|
||||
output.addAudioTrack(audioSource(), { group: a });
|
||||
|
||||
await output.start();
|
||||
|
||||
const muxer = output._muxer as HlsMuxer;
|
||||
const decl = muxer.playlistDeclarations;
|
||||
|
||||
expect(decl).toHaveLength(5);
|
||||
|
||||
expect(decl[0]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[0]!.playlist.tracks[0]!.type).toBe('audio');
|
||||
expect(decl[0]!.groupId).toBe('audio-1');
|
||||
expect(decl[0]!.noUri).toBe(false);
|
||||
|
||||
expect(decl[1]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[1]!.playlist.tracks[0]!.type).toBe('audio');
|
||||
expect(decl[1]!.groupId).toBe('audio-1');
|
||||
expect(decl[1]!.noUri).toBe(false);
|
||||
|
||||
expect(decl[2]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[2]!.playlist.tracks[0]!.type).toBe('audio');
|
||||
expect(decl[2]!.playlist).toBe(decl[0]!.playlist);
|
||||
expect(decl[2]!.groupId).toBe('audio-2');
|
||||
expect(decl[2]!.noUri).toBe(false);
|
||||
|
||||
expect(decl[3]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[3]!.playlist.tracks[0]!.type).toBe('video');
|
||||
expect(decl[3]!.groupId).toBeNull();
|
||||
expect(decl[3]!.references).toEqual([decl[0]!, decl[1]!]);
|
||||
|
||||
expect(decl[4]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[4]!.playlist.tracks[0]!.type).toBe('video');
|
||||
expect(decl[4]!.groupId).toBeNull();
|
||||
expect(decl[4]!.references).toEqual([decl[2]!]);
|
||||
});
|
||||
|
||||
test('Playlist assignment, video paired with video', async () => {
|
||||
const consoleSpy = vi.spyOn(console, 'warn');
|
||||
|
||||
const output = new Output({
|
||||
format: new HlsOutputFormat({
|
||||
segmentFormats: HLS_OUTPUT_FORMATS_DEFAULT,
|
||||
}),
|
||||
target: () => new NullTarget(),
|
||||
rootPath: '',
|
||||
});
|
||||
|
||||
const a = new OutputTrackGroup();
|
||||
const b = new OutputTrackGroup();
|
||||
a.pairWith(b);
|
||||
|
||||
output.addVideoTrack(videoSource(), { group: a });
|
||||
output.addVideoTrack(videoSource(), { group: b });
|
||||
|
||||
await output.start();
|
||||
|
||||
const muxer = output._muxer as HlsMuxer;
|
||||
const decl = muxer.playlistDeclarations;
|
||||
|
||||
expect(decl).toHaveLength(2);
|
||||
|
||||
expect(decl[0]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[0]!.groupId).toBeNull();
|
||||
expect(decl[0]!.references).toHaveLength(0);
|
||||
|
||||
expect(decl[1]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[1]!.groupId).toBeNull();
|
||||
expect(decl[1]!.references).toHaveLength(0);
|
||||
|
||||
expect(consoleSpy.mock.calls).toHaveLength(1);
|
||||
expect(consoleSpy.mock.calls[0]![0]).toContain('Illegal pairing');
|
||||
});
|
||||
|
||||
test('Playlist assignment, audio paired with audio', async () => {
|
||||
const consoleSpy = vi.spyOn(console, 'warn');
|
||||
|
||||
const output = new Output({
|
||||
format: new HlsOutputFormat({
|
||||
segmentFormats: HLS_OUTPUT_FORMATS_DEFAULT,
|
||||
}),
|
||||
target: () => new NullTarget(),
|
||||
rootPath: '',
|
||||
});
|
||||
|
||||
const a = new OutputTrackGroup();
|
||||
const b = new OutputTrackGroup();
|
||||
a.pairWith(b);
|
||||
|
||||
output.addAudioTrack(audioSource(), { group: a });
|
||||
output.addAudioTrack(audioSource(), { group: b });
|
||||
|
||||
await output.start();
|
||||
|
||||
const muxer = output._muxer as HlsMuxer;
|
||||
const decl = muxer.playlistDeclarations;
|
||||
|
||||
expect(decl).toHaveLength(2);
|
||||
|
||||
expect(decl[0]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[0]!.groupId).toBeNull();
|
||||
expect(decl[0]!.references).toHaveLength(0);
|
||||
|
||||
expect(decl[1]!.playlist.tracks).toHaveLength(1);
|
||||
expect(decl[1]!.groupId).toBeNull();
|
||||
expect(decl[1]!.references).toHaveLength(0);
|
||||
|
||||
expect(consoleSpy.mock.calls).toHaveLength(1);
|
||||
expect(consoleSpy.mock.calls[0]![0]).toContain('Illegal pairing');
|
||||
});
|
||||
Reference in New Issue
Block a user