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'); });