NorskOutput.cmafMultiVariant() method
Produces a multi variant (used to be known as master) hls and/or dash manifest for a collection of media streams
This can optionally be served via the Norsk web server or be pushed to other locations - see CmafDestinationSettings
Signature:
cmafMultiVariant(settings: CmafMultiVariantOutputSettings): Promise<CmafMultiVariantOutputNode>;
Parameters
Parameter | Type | Description |
---|---|---|
settings |
Configuration for the CMAF Multi Variant Manifest |
Returns:
Promise<CmafMultiVariantOutputNode>
Example [03_rtmp_to_hls_passthrough.ts]
Package an inbound RTMP stream into LL-HLS
export async function main() {
const norsk = await Norsk.connect();
const input = await norsk.input.rtmpServer({ id: "rtmpInput" });
// Receive an inbound stream and segment it as CMAF chunks for publication as HLS and DASH
// Note that as this is passthrough we don't necessarily know the bitrate of the stream
// for the HLS multi variant (master) playlist. Here we just set them by hand in the CMAF Audio and CMAF
// video segmenters. Other examples show how you can measure bitrates and use that in the multi variant playlist.
// If a transcode is happening (take a look at the various _to_ladder examples) then
// each streams will have well known bitrates that automatically flow through the workflow
// Note that from here on down, the code is identical to the code in rtmp_to_hls_passthrough
// With Norsk you only need to describe the desired media flow - it takes care of the differences
// between various input types.
const audioOutput = await norsk.output.cmafAudio({ id: "audio", bitrate: 20_000, ...segmentSettings });
const videoOutput = await norsk.output.cmafVideo({ id: "video", bitrate: 1_500_000, ...segmentSettings });
const mvOutput = await norsk.output.cmafMultiVariant({ id: "multi-variant", playlistName: "multi-variant", destinations });
mvOutput.subscribe([
{ source: audioOutput, sourceSelector: selectPlaylist },
{ source: videoOutput, sourceSelector: selectPlaylist },
]);
audioOutput.subscribe([{ source: input, sourceSelector: selectAudio }]);
videoOutput.subscribe([{ source: input, sourceSelector: selectVideo }]);
console.log(`Multi variant playlist: ${mvOutput.url}`);
void audioOutput.url().then(logMediaPlaylist("audio"));
void videoOutput.url().then(logMediaPlaylist("video"));
}
const destinations: CmafDestinationSettings[] =
[{ id: "local", type: "local", retentionPeriodSeconds: 10 }];
const segmentSettings: CmafOutputSettings = {
partDurationSeconds: 1.0,
segmentDurationSeconds: 4.0,
destinations
};
function logMediaPlaylist(name: string): (url: string) => void {
return (
url => { console.log(`${name} playlistUrl: ${url}`); }
);
}
Run the following command to generate example input at url rtmp://127.0.0.1:1935/norsk/default
:
ffmpeg -v error -re -stream_loop -1 -i data/InkDrop.ts -vcodec copy -codec copy -f flv 'rtmp://127.0.0.1:1935/norsk/default'
Example [04_srt_to_hls_passthrough.ts]
Package an SRT stream into LL-HLS
export async function main() {
const norsk = await Norsk.connect();
const input = await norsk.input.srt(srtInputSettings);
// Receive an inbound stream and segment it as CMAF chunks for publication as HLS and DASH
// Note that as this is passthrough we don't necessarily know the bitrate of the stream
// for the HLS multi variant (master) playlist. Here we just set them by hand in the CMAF Audio and CMAF
// video segmenters. Other examples show how you can measure bitrates and use that in the multi variant playlist.
// If a transcode is happening (take a look at the various _to_ladder examples) then
// each streams will have well known bitrates that automatically flow through the workflow
// Note that from here on down, the code is identical to the code in srt_to_hls_passthrough
// With Norsk you only need to describe the desired media flow - it takes care of the differences
// between various input types.
const audioOutput = await norsk.output.cmafAudio({ id: "audio", bitrate: 20_000, ...segmentSettings });
const videoOutput = await norsk.output.cmafVideo({ id: "video", bitrate: 1_500_000, ...segmentSettings });
const mvOutput = await norsk.output.cmafMultiVariant({ id: "multi-variant", playlistName: "multi-variant", destinations });
mvOutput.subscribe([
{ source: audioOutput, sourceSelector: selectPlaylist },
{ source: videoOutput, sourceSelector: selectPlaylist },
]);
audioOutput.subscribe([{ source: input, sourceSelector: selectAudio }]);
videoOutput.subscribe([{ source: input, sourceSelector: selectVideo }]);
console.log(`Multi variant playlist: ${mvOutput.url}`);
void audioOutput.url().then(logMediaPlaylist("audio"));
void videoOutput.url().then(logMediaPlaylist("video"));
}
const destinations: CmafDestinationSettings[] =
[{ id: "local", type: "local", retentionPeriodSeconds: 10 }];
const segmentSettings: CmafOutputSettings = {
partDurationSeconds: 1.0,
segmentDurationSeconds: 4.0,
destinations
};
const srtInputSettings: SrtInputSettings = {
id: "srtInput",
host: "0.0.0.0",
port: 5001,
mode: "listener",
sourceName: "camera1",
};
function logMediaPlaylist(name: string): (url: string) => void {
return (
url => { console.log(`${name} playlistUrl: ${url}`); }
);
}
Run the following command to generate example input at url srt://127.0.0.1:5001?pkt_size=1316
:
ffmpeg -v error -re -stream_loop -1 -i data/InkDrop.ts -vcodec copy -codec copy -f mpegts -flush_packets 0 'srt://127.0.0.1:5001?pkt_size=1316'
Example [20_cmaf_pull_push.ts]
TODO
export async function main() {
await runWebServer(port);
const norsk = await Norsk.connect();
const input = await norsk.input.rtmpServer({ id: "rtmpInput" });
const destinations: CmafDestinationSettings[] = [
{ type: "local", retentionPeriodSeconds: 60, id: "local" },
{ type: "generic", id: "genericPullPush", host: "localhost", port, pathPrefix: "/push/", retentionPeriodSeconds: 60 }
]
const audioOutput = await norsk.output.cmafAudio({ id: "audio", destinations, ...segmentSettings });
const videoOutput = await norsk.output.cmafVideo({ id: "video", destinations, ...segmentSettings });
const masterOutput = await norsk.output.cmafMultiVariant({ id: "multi-variant", playlistName: "multi-variant", destinations });
audioOutput.subscribe([{ source: input, sourceSelector: selectAudio }]);
videoOutput.subscribe([{ source: input, sourceSelector: selectVideo }]);
masterOutput.subscribe([
{ source: audioOutput, sourceSelector: selectPlaylist },
{ source: videoOutput, sourceSelector: selectPlaylist }
]);
console.log(`Master playlist: ${masterOutput.url}`);
void audioOutput.url().then(logMediaPlaylist("audio"));
void videoOutput.url().then(logMediaPlaylist("video"));
}
Run the following command to generate example input at url rtmp://127.0.0.1:1935/acme/high
:
ffmpeg -v error -re -stream_loop -1 -i data/InkDrop.ts -vcodec copy -codec copy -f flv 'rtmp://127.0.0.1:1935/acme/high'
Example [27_playlist_manipulation.ts]
Manipulate an HLS playlist
export async function main() {
const port = 3210;
await runWebServer(port, { logPlaylists: true });
const norsk = await Norsk.connect();
const input = await norsk.input.rtmpServer({ id: "rtmpInput" });
const localDestination: CmafDestinationSettings = { type: "local", retentionPeriodSeconds: 10, id: "local" };
const pushDestination: CmafDestinationSettings = { type: "generic", id: "gen", host: "localhost", port, pathPrefix: "/push/", retentionPeriodSeconds: 60 }
const destinations: CmafDestinationSettings[] = [
localDestination,
pushDestination
]
const segmentSettings = {
partDurationSeconds: 2.0,
segmentDurationSeconds: 2.0,
};
const audioOutput = await norsk.output.cmafAudio({ id: "audio", destinations, ...segmentSettings });
const videoOutput = await norsk.output.cmafVideo({ id: "video", destinations, ...segmentSettings });
const masterOutput = await norsk.output.cmafMultiVariant({ id: "multi-variant", playlistName: "multi-variant", destinations });
const emptyHlsPlaylist: () => HlsPlaylist = () => {
return {
hlsStandardPlaylist: [],
hlsByteRangePlaylist: [],
hlsFilePartPlaylist: [],
}
}
const audioPlaylistLocal: HlsPlaylist = emptyHlsPlaylist();
const audioPlaylistPush: HlsPlaylist = emptyHlsPlaylist();
audioOutput.onPlaylistAddition = (destinationId: DestinationId, playlistAdditions: HlsPlaylistAdditions): HlsPlaylist => {
if (destinationId == localDestination.id) {
audioPlaylistLocal.hlsFilePartPlaylist.push(...playlistAdditions.hlsFilePartPlaylist);
return audioPlaylistLocal;
} else {
audioPlaylistPush.hlsStandardPlaylist.push(...playlistAdditions.hlsStandardPlaylist, { tag: "# Additional comment line for audio push" });
return audioPlaylistPush;
}
}
const videoPlaylistLocal: HlsPlaylist = emptyHlsPlaylist();
const videoPlaylistPush: HlsPlaylist = emptyHlsPlaylist();
videoOutput.onPlaylistAddition = (destinationId: DestinationId, playlistAdditions: HlsPlaylistAdditions): HlsPlaylist => {
if (destinationId == localDestination.id) {
videoPlaylistLocal.hlsStandardPlaylist.push(...playlistAdditions.hlsStandardPlaylist);
videoPlaylistLocal.hlsFilePartPlaylist.push(...playlistAdditions.hlsFilePartPlaylist);
videoPlaylistLocal.hlsByteRangePlaylist.push(...playlistAdditions.hlsByteRangePlaylist);
return videoPlaylistLocal;
} else {
videoPlaylistPush.hlsStandardPlaylist.push(...playlistAdditions.hlsStandardPlaylist, { tag: "# Additional comment line for video push" });
return videoPlaylistPush;
}
}
audioOutput.subscribe([{ source: input, sourceSelector: selectAudio }]);
videoOutput.subscribe([{ source: input, sourceSelector: selectVideo }]);
masterOutput.subscribe([
{ source: audioOutput, sourceSelector: selectPlaylist },
{ source: videoOutput, sourceSelector: selectPlaylist }
]);
console.log(`Master playlist: ${masterOutput.url}`);
void audioOutput.url().then(logMediaPlaylist("audio"));
void videoOutput.url().then(logMediaPlaylist("video"));
}
Run the following command to generate example input at url rtmp://127.0.0.1:1935/acme/high
:
ffmpeg -v error -re -stream_loop -1 -i data/InkDrop.ts -vcodec copy -codec copy -f flv 'rtmp://127.0.0.1:1935/acme/high'
Find Examples
Search for examples using cmafMultiVariant in our examples repo.