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

CmafMultiVariantOutputSettings

Configuration for the CMAF Multi Variant Manifest

Returns:

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}`);
  audioOutput.url().then(logMediaPlaylist("audio"));
  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}`);
  audioOutput.url().then(logMediaPlaylist("audio"));
  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",
  ip: "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() {
  webServer.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}`);
  audioOutput.url().then(logMediaPlaylist("audio"));
  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;
  webServer.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: [],
    }
  }

  let audioPlaylistLocal: HlsPlaylist = emptyHlsPlaylist();
  let 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;
    }
  }

  let videoPlaylistLocal: HlsPlaylist = emptyHlsPlaylist();
  let 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}`);
  audioOutput.url().then(logMediaPlaylist("audio"));
  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