From dc7076d9dfad8b114e9833b5ac5e74f9ddbee6c5 Mon Sep 17 00:00:00 2001 From: Nfrederiksen Date: Wed, 15 Feb 2023 16:07:35 +0100 Subject: [PATCH 1/5] added demux support, not yet cmaf --- .github/workflows/publish.yml | 40 +- .gitignore | 4 +- index.js | 657 ++++-- package-lock.json | 156 +- package.json | 2 +- spec/hls_splice_spec.js | 2057 +++++++++++++---- spec/support/jasmine.json | 22 +- testvectors/ad1/index_0_av.m3u8 | 34 +- testvectors/ad1/index_1_av.m3u8 | 34 +- testvectors/ad1/master.m3u8 | 10 +- testvectors/ad2/index_0_av.m3u8 | 34 +- testvectors/ad2/index_1_av.m3u8 | 34 +- testvectors/ad2/master.m3u8 | 10 +- testvectors/ad3/index_0_av.m3u8 | 18 +- testvectors/ad3/index_1_av.m3u8 | 18 +- testvectors/ad3/master.m3u8 | 10 +- testvectors/ad4/index_0_av.m3u8 | 18 +- testvectors/ad4/index_1_av.m3u8 | 18 +- testvectors/ad4/master.m3u8 | 10 +- testvectors/demux/ad1/index_0_v.m3u8 | 17 + testvectors/demux/ad1/index_1_v.m3u8 | 17 + testvectors/demux/ad1/index_mono-en_a.m3u8 | 17 + testvectors/demux/ad1/index_mono-sv_a.m3u8 | 17 + testvectors/demux/ad1/index_stereo-en_a.m3u8 | 17 + testvectors/demux/ad1/index_stereo-sv_a.m3u8 | 17 + testvectors/demux/ad1/master.m3u8 | 9 + testvectors/demux/ad2/index_0_v.m3u8 | 17 + testvectors/demux/ad2/index_1_v.m3u8 | 17 + testvectors/demux/ad2/index_audio-no_a.m3u8 | 17 + testvectors/demux/ad2/master.m3u8 | 6 + testvectors/demux/ad3/index_0_v.m3u8 | 9 + testvectors/demux/ad3/index_1_v.m3u8 | 9 + testvectors/demux/ad3/index_mono-en_a.m3u8 | 9 + testvectors/demux/ad3/index_mono-sv_a.m3u8 | 9 + testvectors/demux/ad3/index_stereo-en_a.m3u8 | 9 + testvectors/demux/ad3/index_stereo-sv_a.m3u8 | 9 + testvectors/demux/ad3/master.m3u8 | 10 + testvectors/demux/ad4/index_0_v.m3u8 | 9 + testvectors/demux/ad4/index_1_v.m3u8 | 9 + testvectors/demux/ad4/index_mono-en_a.m3u8 | 9 + testvectors/demux/ad4/index_mono-sv_a.m3u8 | 9 + testvectors/demux/ad4/index_stereo-en_a.m3u8 | 9 + testvectors/demux/ad4/index_stereo-sv_a.m3u8 | 9 + testvectors/demux/ad4/master.m3u8 | 9 + testvectors/demux/hls1/index_0_v.m3u8 | 28 + testvectors/demux/hls1/index_1_v.m3u8 | 28 + testvectors/demux/hls1/index_mono-en_a.m3u8 | 28 + testvectors/demux/hls1/index_mono-sv_a.m3u8 | 28 + testvectors/demux/hls1/index_stereo-en_a.m3u8 | 28 + testvectors/demux/hls1/index_stereo-sv_a.m3u8 | 28 + testvectors/demux/hls1/master.m3u8 | 9 + testvectors/demux/hls1b/index_0_v.m3u8 | 30 + testvectors/demux/hls1b/index_1_v.m3u8 | 30 + testvectors/demux/hls1b/index_mono-en_a.m3u8 | 30 + testvectors/demux/hls1b/index_mono-sv_a.m3u8 | 30 + .../demux/hls1b/index_stereo-en_a.m3u8 | 30 + .../demux/hls1b/index_stereo-sv_a.m3u8 | 30 + testvectors/demux/hls1b/master.m3u8 | 9 + testvectors/demux/hls2/index_0_v.m3u8 | 28 + testvectors/demux/hls2/index_1_v.m3u8 | 28 + testvectors/demux/hls2/index_audio-no_a.m3u8 | 28 + testvectors/demux/hls2/master.m3u8 | 6 + testvectors/demux_n_cmaf/ad1/index_0_v.m3u8 | 21 + testvectors/demux_n_cmaf/ad1/index_1_v.m3u8 | 20 + .../demux_n_cmaf/ad1/index_stereo-sv_a.m3u8 | 26 + testvectors/demux_n_cmaf/ad1/master.m3u8 | 6 + testvectors/demux_n_cmaf/ad3/index_0_v.m3u8 | 13 + testvectors/demux_n_cmaf/ad3/index_1_v.m3u8 | 12 + .../demux_n_cmaf/ad3/index_stereo-sv_a.m3u8 | 14 + testvectors/demux_n_cmaf/ad3/master.m3u8 | 6 + testvectors/demux_n_cmaf/hls1/index_0_v.m3u8 | 129 ++ testvectors/demux_n_cmaf/hls1/index_1_v.m3u8 | 129 ++ .../demux_n_cmaf/hls1/index_stereo-sv_a.m3u8 | 194 ++ testvectors/demux_n_cmaf/hls1/master.m3u8 | 6 + .../hls2_demux_cmaf/index_0_av.m3u8 | 28 + .../hls2_demux_cmaf/index_1_av.m3u8 | 28 + .../demux_n_cmaf/hls2_demux_cmaf/master.m3u8 | 13 + testvectors/hls1/index_0_av.m3u8 | 56 +- testvectors/hls1/index_1_av.m3u8 | 56 +- testvectors/hls1b/index_0_av.m3u8 | 56 +- testvectors/hls1b/index_1_av.m3u8 | 56 +- testvectors/hls1b/master.m3u8 | 10 +- 82 files changed, 3914 insertions(+), 902 deletions(-) create mode 100644 testvectors/demux/ad1/index_0_v.m3u8 create mode 100644 testvectors/demux/ad1/index_1_v.m3u8 create mode 100644 testvectors/demux/ad1/index_mono-en_a.m3u8 create mode 100644 testvectors/demux/ad1/index_mono-sv_a.m3u8 create mode 100644 testvectors/demux/ad1/index_stereo-en_a.m3u8 create mode 100644 testvectors/demux/ad1/index_stereo-sv_a.m3u8 create mode 100644 testvectors/demux/ad1/master.m3u8 create mode 100644 testvectors/demux/ad2/index_0_v.m3u8 create mode 100644 testvectors/demux/ad2/index_1_v.m3u8 create mode 100644 testvectors/demux/ad2/index_audio-no_a.m3u8 create mode 100644 testvectors/demux/ad2/master.m3u8 create mode 100644 testvectors/demux/ad3/index_0_v.m3u8 create mode 100644 testvectors/demux/ad3/index_1_v.m3u8 create mode 100644 testvectors/demux/ad3/index_mono-en_a.m3u8 create mode 100644 testvectors/demux/ad3/index_mono-sv_a.m3u8 create mode 100644 testvectors/demux/ad3/index_stereo-en_a.m3u8 create mode 100644 testvectors/demux/ad3/index_stereo-sv_a.m3u8 create mode 100644 testvectors/demux/ad3/master.m3u8 create mode 100644 testvectors/demux/ad4/index_0_v.m3u8 create mode 100644 testvectors/demux/ad4/index_1_v.m3u8 create mode 100644 testvectors/demux/ad4/index_mono-en_a.m3u8 create mode 100644 testvectors/demux/ad4/index_mono-sv_a.m3u8 create mode 100644 testvectors/demux/ad4/index_stereo-en_a.m3u8 create mode 100644 testvectors/demux/ad4/index_stereo-sv_a.m3u8 create mode 100644 testvectors/demux/ad4/master.m3u8 create mode 100644 testvectors/demux/hls1/index_0_v.m3u8 create mode 100644 testvectors/demux/hls1/index_1_v.m3u8 create mode 100644 testvectors/demux/hls1/index_mono-en_a.m3u8 create mode 100644 testvectors/demux/hls1/index_mono-sv_a.m3u8 create mode 100644 testvectors/demux/hls1/index_stereo-en_a.m3u8 create mode 100644 testvectors/demux/hls1/index_stereo-sv_a.m3u8 create mode 100644 testvectors/demux/hls1/master.m3u8 create mode 100644 testvectors/demux/hls1b/index_0_v.m3u8 create mode 100644 testvectors/demux/hls1b/index_1_v.m3u8 create mode 100644 testvectors/demux/hls1b/index_mono-en_a.m3u8 create mode 100644 testvectors/demux/hls1b/index_mono-sv_a.m3u8 create mode 100644 testvectors/demux/hls1b/index_stereo-en_a.m3u8 create mode 100644 testvectors/demux/hls1b/index_stereo-sv_a.m3u8 create mode 100644 testvectors/demux/hls1b/master.m3u8 create mode 100644 testvectors/demux/hls2/index_0_v.m3u8 create mode 100644 testvectors/demux/hls2/index_1_v.m3u8 create mode 100644 testvectors/demux/hls2/index_audio-no_a.m3u8 create mode 100644 testvectors/demux/hls2/master.m3u8 create mode 100644 testvectors/demux_n_cmaf/ad1/index_0_v.m3u8 create mode 100644 testvectors/demux_n_cmaf/ad1/index_1_v.m3u8 create mode 100644 testvectors/demux_n_cmaf/ad1/index_stereo-sv_a.m3u8 create mode 100644 testvectors/demux_n_cmaf/ad1/master.m3u8 create mode 100644 testvectors/demux_n_cmaf/ad3/index_0_v.m3u8 create mode 100644 testvectors/demux_n_cmaf/ad3/index_1_v.m3u8 create mode 100644 testvectors/demux_n_cmaf/ad3/index_stereo-sv_a.m3u8 create mode 100644 testvectors/demux_n_cmaf/ad3/master.m3u8 create mode 100644 testvectors/demux_n_cmaf/hls1/index_0_v.m3u8 create mode 100644 testvectors/demux_n_cmaf/hls1/index_1_v.m3u8 create mode 100644 testvectors/demux_n_cmaf/hls1/index_stereo-sv_a.m3u8 create mode 100644 testvectors/demux_n_cmaf/hls1/master.m3u8 create mode 100644 testvectors/demux_n_cmaf/hls2_demux_cmaf/index_0_av.m3u8 create mode 100644 testvectors/demux_n_cmaf/hls2_demux_cmaf/index_1_av.m3u8 create mode 100644 testvectors/demux_n_cmaf/hls2_demux_cmaf/master.m3u8 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index e00a27f..ac0c07d 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,20 +1,20 @@ -name: Publish - -on: - release: - types: [published] - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v1 - - uses: actions/setup-node@v1 - with: - node-version: 12 - registry-url: https://registry.npmjs.org/ - - run: npm install - - run: npm publish --access public - env: - NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} +name: Publish + +on: + release: + types: [published] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - uses: actions/setup-node@v1 + with: + node-version: 12 + registry-url: https://registry.npmjs.org/ + - run: npm install + - run: npm publish --access public + env: + NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} diff --git a/.gitignore b/.gitignore index c9106a7..deada91 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ -node_modules -.nyc_output +node_modules +.nyc_output diff --git a/index.js b/index.js index d8188c9..f51439e 100644 --- a/index.js +++ b/index.js @@ -1,7 +1,7 @@ -const m3u8 = require('@eyevinn/m3u8'); -const request = require('request'); -const url = require('url'); - +const m3u8 = require("@eyevinn/m3u8"); +const request = require("request"); +const url = require("url"); +const _log = (s, i = 0) => console.log(JSON.stringify(s, null, 2), 2002 + i); /* const findNearestBw = (bw, array) => { // TO BE IMPLEMENTED @@ -14,73 +14,124 @@ const findNearestBw = (bw, array) => { return sorted[sorted.length - 1]; }; */ +const findNearestGroupAndLang = (_group, _language, _playlist) => { + const groups = Object.keys(_playlist); + let group = groups[0]; // default + if (groups.includes(_group)) { + group = _group; + } + const langs = Object.keys(_playlist[group]); + let lang = langs[0]; // default + if (langs.includes(_language)) { + lang = _language; + } + return [group, lang]; +}; const findNearestBw = (bw, array) => { const sorted = array.sort((a, b) => b - a); return sorted.reduce((a, b) => { return Math.abs(b - bw) < Math.abs(a - bw) ? b : a; }); -} +}; class HLSSpliceVod { /** * Create an HLSSpliceVod instance - * @param {string} vodManifestUrl - * @param {Object} options + * @param {string} vodManifestUrl + * @param {Object} options */ constructor(vodManifestUri, options) { this.masterManifestUri = vodManifestUri; this.playlists = {}; + this.playlistsAudio = {}; this.baseUrl = null; this.targetDuration = 0; + this.targetDurationAudio = 0; this.mergeBreaks = false; // Merge ad breaks at the same position into one single break this.bumperDuration = null; if (options && options.baseUrl) { this.baseUrl = options.baseUrl; } if (options && options.absoluteUrls) { - const m = this.masterManifestUri.match('^(.*)/.*?'); + const m = this.masterManifestUri.match("^(.*)/.*?"); if (m) { - this.baseUrl = m[1] + '/'; + this.baseUrl = m[1] + "/"; } - } if (options && options.merge) { this.mergeBreaks = true; } + this.cmafMapUri = { video: {}, audio: {} }; } /** - * - * @param {ReadStream} _injectMasterManifest - * @param {ReadStream} _injectMediaManifest + * + * @param {ReadStream} _injectMasterManifest + * @param {ReadStream} _injectMediaManifest */ - load(_injectMasterManifest, _injectMediaManifest) { + load(_injectMasterManifest, _injectMediaManifest, _injectAudioManifest) { return new Promise((resolve, reject) => { const parser = m3u8.createStream(); - parser.on('m3u', m3u => { + parser.on("m3u", (m3u) => { this.m3u = m3u; let mediaManifestPromises = []; let baseUrl; const m = this.masterManifestUri.match(/^(.*)\/.*?$/); if (m) { - baseUrl = m[1] + '/'; + baseUrl = m[1] + "/"; } for (let i = 0; i < m3u.items.StreamItem.length; i++) { const streamItem = m3u.items.StreamItem[i]; - const mediaManifestUrl = url.resolve(baseUrl, streamItem.get('uri')); - mediaManifestPromises.push(this._loadMediaManifest(mediaManifestUrl, streamItem.get('bandwidth'), _injectMediaManifest)); + const mediaManifestUrl = url.resolve(baseUrl, streamItem.get("uri")); + mediaManifestPromises.push( + this._loadMediaManifest(mediaManifestUrl, streamItem.get("bandwidth"), _injectMediaManifest) + ); } - Promise.all(mediaManifestPromises) - .then(resolve) - .catch(reject); + let audioItems = m3u.items.MediaItem.filter((item) => { + return item.attributes.attributes.type === "AUDIO"; + }); + for (let i = 0; i < audioItems.length; i++) { + const audioItem = audioItems[i]; + // If no uri on mediaItem then it must exist on a streamItem + let audioItemUri; + if (!audioItem.get("uri")) { + const aItemGroup = audioItem.get("group-id"); + const audioStreamItem = m3u.items.StreamItem.filter((streamItem) => { + if (streamItem.get("resolution") === undefined) { + return true; + } + let streamGroupId = streamItem.attributes.attributes["audio"]; + if (streamGroupId === aItemGroup) { + return false; + } else { + return true; + } + })[0]; + if (audioStreamItem) { + audioItemUri = audioStreamItem.get("uri"); + } + } else { + audioItemUri = audioItem.get("uri"); + } + + const audioManifestUrl = url.resolve(baseUrl, audioItemUri); + mediaManifestPromises.push( + this._loadAudioManifest( + audioManifestUrl, + audioItem.get("group-id"), + audioItem.get("language"), + _injectAudioManifest + ) + ); + } + Promise.all(mediaManifestPromises).then(resolve).catch(reject); }); if (!_injectMasterManifest) { try { - request({ uri: this.masterManifestUri, gzip: true }) - .pipe(parser) + request({ uri: this.masterManifestUri, gzip: true }).pipe(parser); } catch (exc) { reject(exc); } @@ -90,62 +141,131 @@ class HLSSpliceVod { }); } - insertAdAt(offset, adMasterManifestUri, _injectAdMasterManifest, _injectAdMediaManifest) { + insertAdAt(offset, adMasterManifestUri, _injectAdMasterManifest, _injectAdMediaManifest, _injectAdAudioManifest) { return new Promise((resolve, reject) => { - this._parseAdMasterManifest(adMasterManifestUri, _injectAdMasterManifest, _injectAdMediaManifest) - .then(ad => { - const isPostRoll = (offset == -1); - const bandwidths = Object.keys(this.playlists); - - if (isPostRoll) { - let duration = 0; - this.playlists[bandwidths[0]].items.PlaylistItem.map(plItem => { - duration += (plItem.get('duration') * 1000); - }); - offset = duration; - } else { - if (this.bumperDuration) { - offset = this.bumperDuration + offset; - } - } + this._parseAdMasterManifest( + adMasterManifestUri, + _injectAdMasterManifest, + _injectAdMediaManifest, + _injectAdAudioManifest + ) + .then((ad) => { + const startOffset = offset; + const isPostRoll = offset == -1; + const bandwidths = Object.keys(this.playlists); - for (let b = 0; b < bandwidths.length; b++) { - const bw = bandwidths[b]; - - const adPlaylist = ad.playlist[findNearestBw(bw, Object.keys(ad.playlist))]; - let pos = 0; - let i = 0; - while(pos < offset && i < this.playlists[bw].items.PlaylistItem.length) { - const plItem = this.playlists[bw].items.PlaylistItem[i]; - pos += (plItem.get('duration') * 1000); - i++; - } - let insertCueIn = false; - if (this.playlists[bw].items.PlaylistItem[this.playlists[bw].items.PlaylistItem.length - 1].get('cuein')) { - insertCueIn = true; - } - const adLength = adPlaylist.items.PlaylistItem.length; - for (let j = 0; j < adLength; j++) { - this.playlists[bw].items.PlaylistItem.splice(i + j, 0, adPlaylist.items.PlaylistItem[j]); + if (isPostRoll) { + let duration = 0; + this.playlists[bandwidths[0]].items.PlaylistItem.map((plItem) => { + duration += plItem.get("duration") * 1000; + }); + offset = duration; + } else { + if (this.bumperDuration) { + offset = this.bumperDuration + offset; + } } - this.playlists[bw].items.PlaylistItem[i].set('discontinuity', true); - this.playlists[bw].items.PlaylistItem[i].set('cueout', ad.duration); - if (insertCueIn) { - this.playlists[bw].items.PlaylistItem[i].set('cuein', true); + + for (let b = 0; b < bandwidths.length; b++) { + const bw = bandwidths[b]; + + const adPlaylist = ad.playlist[findNearestBw(bw, Object.keys(ad.playlist))]; + let pos = 0; + let i = 0; + while (pos < offset && i < this.playlists[bw].items.PlaylistItem.length) { + const plItem = this.playlists[bw].items.PlaylistItem[i]; + pos += plItem.get("duration") * 1000; + i++; + } + let insertCueIn = false; + if (this.playlists[bw].items.PlaylistItem[this.playlists[bw].items.PlaylistItem.length - 1].get("cuein")) { + insertCueIn = true; + } + const adLength = adPlaylist.items.PlaylistItem.length; + for (let j = 0; j < adLength; j++) { + this.playlists[bw].items.PlaylistItem.splice(i + j, 0, adPlaylist.items.PlaylistItem[j]); + } + this.playlists[bw].items.PlaylistItem[i].set("discontinuity", true); + this.playlists[bw].items.PlaylistItem[i].set("cueout", ad.duration); + if (insertCueIn) { + this.playlists[bw].items.PlaylistItem[i].set("cuein", true); + } + if (this.playlists[bw].items.PlaylistItem[i + adLength]) { + this.playlists[bw].items.PlaylistItem[i + adLength].set("cuein", true); + if (!isPostRoll) { + this.playlists[bw].items.PlaylistItem[i + adLength].set("discontinuity", true); + } + if (this.cmafMapUri.video[bw]) { + this.playlists[bw].items.PlaylistItem[i + adLength].set("map-uri", this.cmafMapUri.video[bw]); + } + } else { + this.playlists[bw].addPlaylistItem({ cuein: true }); + } + this.playlists[bw].set("targetDuration", this.targetDuration); } - if (this.playlists[bw].items.PlaylistItem[i + adLength]) { - this.playlists[bw].items.PlaylistItem[i + adLength].set('cuein', true); - if (!isPostRoll) { - this.playlists[bw].items.PlaylistItem[i + adLength].set('discontinuity', true); + const groups = Object.keys(this.playlistsAudio); + if (groups.length > 0) { + if (isPostRoll) { + let duration = 0; + const langs = Object.keys(this.playlistsAudio[groups[0]]); + this.playlistsAudio[groups[0]][langs[0]].items.PlaylistItem.map((plItem) => { + duration += plItem.get("duration") * 1000; + }); + offset = duration; + } else { + if (this.bumperDuration) { + offset = this.bumperDuration + startOffset; + } + } + for (let i = 0; i < groups.length; i++) { + const g = groups[i]; + const langs = Object.keys(this.playlistsAudio[g]); + for (let ii = 0; ii < langs.length; ii++) { + const l = langs[ii]; + const playlist = this.playlistsAudio[g][l]; + const [nearestGroup, nearestLang] = findNearestGroupAndLang(g, l, ad.playlistAudio); + const adPlaylist = ad.playlistAudio[nearestGroup][nearestLang]; + let pos = 0; + let idx = 0; + while (pos < offset && idx < playlist.items.PlaylistItem.length) { + const plItem = playlist.items.PlaylistItem[idx]; + pos += plItem.get("duration") * 1000; + idx++; + } + let insertCueIn = false; + if (playlist.items.PlaylistItem[playlist.items.PlaylistItem.length - 1].get("cuein")) { + insertCueIn = true; + } + const adLength = adPlaylist.items.PlaylistItem.length; + for (let j = 0; j < adLength; j++) { + playlist.items.PlaylistItem.splice(idx + j, 0, adPlaylist.items.PlaylistItem[j]); + } + playlist.items.PlaylistItem[idx].set("discontinuity", true); + playlist.items.PlaylistItem[idx].set("cueout", ad.durationAudio); + if (insertCueIn) { + playlist.items.PlaylistItem[idx].set("cuein", true); + } + + if (playlist.items.PlaylistItem[idx + adLength]) { + playlist.items.PlaylistItem[idx + adLength].set("cuein", true); + if (!isPostRoll) { + playlist.items.PlaylistItem[idx + adLength].set("discontinuity", true); + } + if (this.cmafMapUri.audio[g][l]) { + playlist.items.PlaylistItem[idx + adLength].set("map-uri", this.cmafMapUri.audio[g][l]); + } + } else { + playlist.addPlaylistItem({ cuein: true }); + } + playlist.set("targetDuration", this.targetDurationAudio); + } } - } else { - this.playlists[bw].addPlaylistItem({ 'cuein': true }); } - this.playlists[bw].set('targetDuration', this.targetDuration); - } - //console.log(this.playlists[bandwidths[0]].toString()); - resolve(); - }).catch(reject); + + //console.log(this.playlists[bandwidths[0]].toString()); + resolve(); + }) + .catch(reject); }); } @@ -173,49 +293,117 @@ class HLSSpliceVod { const bw = bandwidths[b]; let pos = 0; let i = 0; - this.playlists[bw].items.PlaylistItem[0].set('date', new Date(1)); - while(pos < offset && i < this.playlists[bw].items.PlaylistItem.length) { + this.playlists[bw].items.PlaylistItem[0].set("date", new Date(1)); + while (pos < offset && i < this.playlists[bw].items.PlaylistItem.length) { const plItem = this.playlists[bw].items.PlaylistItem[i]; - pos += (plItem.get('duration') * 1000); + pos += plItem.get("duration") * 1000; i++; } - let startDate = (new Date(1 + offset)).toISOString(); + let startDate = new Date(1 + offset).toISOString(); let durationTag = ""; if (opts && opts.plannedDuration) { durationTag = `,DURATION=${opts.plannedDuration / 1000}`; } if (isAssetList) { - this.playlists[bw].items.PlaylistItem[i].set('daterange', - `ID=${id},CLASS="com.apple.hls.interstitial",START-DATE="${startDate}"${durationTag},X-ASSET-LIST="${uri}"${extraAttrs}`); + this.playlists[bw].items.PlaylistItem[i].set( + "daterange", + `ID=${id},CLASS="com.apple.hls.interstitial",START-DATE="${startDate}"${durationTag},X-ASSET-LIST="${uri}"${extraAttrs}` + ); } else { - this.playlists[bw].items.PlaylistItem[i].set('daterange', - `ID=${id},CLASS="com.apple.hls.interstitial",START-DATE="${startDate}"${durationTag},X-ASSET-URI="${uri}"${extraAttrs}`); + this.playlists[bw].items.PlaylistItem[i].set( + "daterange", + `ID=${id},CLASS="com.apple.hls.interstitial",START-DATE="${startDate}"${durationTag},X-ASSET-URI="${uri}"${extraAttrs}` + ); + } + } + const groups = Object.keys(this.playlistsAudio); + for (let i = 0; i < groups.length; i++) { + const group = groups[i]; + const langs = Object.keys(this.playlistsAudio[group]); + for (let j = 0; j < langs.length; j++) { + const lang = langs[j]; + let pos = 0; + let idx = 0; + // todo + this.playlistsAudio[group][lang].items.PlaylistItem[0].set("date", new Date(1)); + while (pos < offset && idx < this.playlistsAudio[group][lang].items.PlaylistItem.length) { + const plItem = this.playlistsAudio[group][lang].items.PlaylistItem[idx]; + pos += plItem.get("duration") * 1000; + idx++; + } + let startDate = new Date(1 + offset).toISOString(); + let durationTag = ""; + if (opts && opts.plannedDuration) { + durationTag = `,DURATION=${opts.plannedDuration / 1000}`; + } + if (isAssetList) { + this.playlistsAudio[group][lang].items.PlaylistItem[idx].set( + "daterange", + `ID=${id},CLASS="com.apple.hls.interstitial",START-DATE="${startDate}"${durationTag},X-ASSET-LIST="${uri}"${extraAttrs}` + ); + } else { + this.playlistsAudio[group][lang].items.PlaylistItem[idx].set( + "daterange", + `ID=${id},CLASS="com.apple.hls.interstitial",START-DATE="${startDate}"${durationTag},X-ASSET-URI="${uri}"${extraAttrs}` + ); + } } } resolve(); }); } - insertBumper(bumperMasterManifestUri, _injectBumperMasterManifest, _injectBumperMediaManifest) { + insertBumper( + bumperMasterManifestUri, + _injectBumperMasterManifest, + _injectBumperMediaManifest, + _injectBumperAudioManifest + ) { return new Promise((resolve, reject) => { - this._parseAdMasterManifest(bumperMasterManifestUri, _injectBumperMasterManifest, _injectBumperMediaManifest) - .then(bumper => { - const bandwidths = Object.keys(this.playlists); - for (let b = 0; b < bandwidths.length; b++) { - const bw = bandwidths[b]; - - const bumperPlaylist = bumper.playlist[findNearestBw(bw, Object.keys(bumper.playlist))]; - const bumperLength = bumperPlaylist.items.PlaylistItem.length; - this.bumperDuration = 0; - for (let j = 0; j < bumperLength; j++) { - this.playlists[bw].items.PlaylistItem.splice(j, 0, bumperPlaylist.items.PlaylistItem[j]); - this.bumperDuration += (bumperPlaylist.items.PlaylistItem[j].get('duration') * 1000); + this._parseAdMasterManifest( + bumperMasterManifestUri, + _injectBumperMasterManifest, + _injectBumperMediaManifest, + _injectBumperAudioManifest + ) + .then((bumper) => { + const bandwidths = Object.keys(this.playlists); + for (let b = 0; b < bandwidths.length; b++) { + const bw = bandwidths[b]; + + const bumperPlaylist = bumper.playlist[findNearestBw(bw, Object.keys(bumper.playlist))]; + const bumperLength = bumperPlaylist.items.PlaylistItem.length; + this.bumperDuration = 0; + for (let j = 0; j < bumperLength; j++) { + this.playlists[bw].items.PlaylistItem.splice(j, 0, bumperPlaylist.items.PlaylistItem[j]); + this.bumperDuration += bumperPlaylist.items.PlaylistItem[j].get("duration") * 1000; + } + this.playlists[bw].items.PlaylistItem[bumperLength].set("discontinuity", true); + this.playlists[bw].set("targetDuration", this.targetDuration); } - this.playlists[bw].items.PlaylistItem[bumperLength].set('discontinuity', true); - this.playlists[bw].set('targetDuration', this.targetDuration); - } - resolve(); - }).catch(reject); + // for audio + const groups = Object.keys(this.playlistsAudio); + for (let i = 0; i < groups.length; i++) { + const g = groups[i]; + const langs = Object.keys(this.playlistsAudio[g]); + for (let ii = 0; ii < langs.length; ii++) { + const l = langs[ii]; + + const [nearestGroup, nearestLang] = findNearestGroupAndLang(g, l, bumper.playlistAudio); + const bumperPlaylist = bumper.playlistAudio[nearestGroup][nearestLang]; + const bumperLength = bumperPlaylist.items.PlaylistItem.length; + this.bumperDuration = 0; + for (let j = 0; j < bumperLength; j++) { + this.playlistsAudio[g][l].items.PlaylistItem.splice(j, 0, bumperPlaylist.items.PlaylistItem[j]); + this.bumperDuration += bumperPlaylist.items.PlaylistItem[j].get("duration") * 1000; + } + this.playlistsAudio[g][l].items.PlaylistItem[bumperLength].set("discontinuity", true); + this.playlistsAudio[g][l].set("targetDuration", this.targetDurationAudio); + } + } + resolve(); + }) + .catch(reject); }); } @@ -224,32 +412,80 @@ class HLSSpliceVod { } getMediaManifest(bw) { - if (this.mergeBreaks) { - let adBreakDuration = 0; - let itemToUpdate = null; - for (let i = 0; i < this.playlists[bw].items.PlaylistItem.length; i++) { - if (this.playlists[bw].items.PlaylistItem[i].get('cueout') && this.playlists[bw].items.PlaylistItem[i].get('cuein')) { - adBreakDuration += this.playlists[bw].items.PlaylistItem[i].get('cueout'); - this.playlists[bw].items.PlaylistItem[i].set('cueout', null); - this.playlists[bw].items.PlaylistItem[i].set('cuein', false); - } else if (this.playlists[bw].items.PlaylistItem[i].get('cueout') && !this.playlists[bw].items.PlaylistItem[i].get('cuein')) { - adBreakDuration = 0; - itemToUpdate = this.playlists[bw].items.PlaylistItem[i]; - } else if (!this.playlists[bw].items.PlaylistItem[i].get('cueout') && this.playlists[bw].items.PlaylistItem[i].get('cuein')) { - const cueOut = itemToUpdate.get('cueout'); - itemToUpdate.set('cueout', cueOut + adBreakDuration); + try { + if (this.mergeBreaks) { + let adBreakDuration = 0; + let itemToUpdate = null; + for (let i = 0; i < this.playlists[bw].items.PlaylistItem.length; i++) { + if ( + this.playlists[bw].items.PlaylistItem[i].get("cueout") && + this.playlists[bw].items.PlaylistItem[i].get("cuein") + ) { + adBreakDuration += this.playlists[bw].items.PlaylistItem[i].get("cueout"); + this.playlists[bw].items.PlaylistItem[i].set("cueout", null); + this.playlists[bw].items.PlaylistItem[i].set("cuein", false); + } else if ( + this.playlists[bw].items.PlaylistItem[i].get("cueout") && + !this.playlists[bw].items.PlaylistItem[i].get("cuein") + ) { + adBreakDuration = 0; + itemToUpdate = this.playlists[bw].items.PlaylistItem[i]; + } else if ( + !this.playlists[bw].items.PlaylistItem[i].get("cueout") && + this.playlists[bw].items.PlaylistItem[i].get("cuein") + ) { + const cueOut = itemToUpdate.get("cueout"); + itemToUpdate.set("cueout", cueOut + adBreakDuration); + } + } + } + this.playlists[bw].set("playlistType", "VOD"); // Ensure playlist type is VOD + return this.playlists[bw].toString().replace(/^\s*\n/gm, ""); + } catch (err) { + return new Error("Failed to get manifest. " + err); + } + } + + getAudioManifest(g, l) { + try { + if (this.mergeBreaks) { + let adBreakDuration = 0; + let itemToUpdate = null; + for (let i = 0; i < this.playlistsAudio[g][l].items.PlaylistItem.length; i++) { + if ( + this.playlistsAudio[g][l].items.PlaylistItem[i].get("cueout") && + this.playlistsAudio[g][l].items.PlaylistItem[i].get("cuein") + ) { + adBreakDuration += this.playlistsAudio[g][l].items.PlaylistItem[i].get("cueout"); + this.playlistsAudio[g][l].items.PlaylistItem[i].set("cueout", null); + this.playlistsAudio[g][l].items.PlaylistItem[i].set("cuein", false); + } else if ( + this.playlistsAudio[g][l].items.PlaylistItem[i].get("cueout") && + !this.playlistsAudio[g][l].items.PlaylistItem[i].get("cuein") + ) { + adBreakDuration = 0; + itemToUpdate = this.playlistsAudio[g][l].items.PlaylistItem[i]; + } else if ( + !this.playlistsAudio[g][l].items.PlaylistItem[i].get("cueout") && + this.playlistsAudio[g][l].items.PlaylistItem[i].get("cuein") + ) { + const cueOut = itemToUpdate.get("cueout"); + itemToUpdate.set("cueout", cueOut + adBreakDuration); + } } } + this.playlistsAudio[g][l].set("playlistType", "VOD"); // Ensure playlist type is VOD + return this.playlistsAudio[g][l].toString().replace(/^\s*\n/gm, ""); + } catch (err) { + return new Error("Failed to get manifest. " + err); } - this.playlists[bw].set('playlistType', "VOD"); // Ensure playlist type is VOD - return this.playlists[bw].toString().replace(/^\s*\n/gm, ''); } _loadMediaManifest(mediaManifestUri, bandwidth, _injectMediaManifest) { return new Promise((resolve, reject) => { const parser = m3u8.createStream(); - parser.on('m3u', m3u => { + parser.on("m3u", (m3u) => { this.duration = 0; if (!this.playlists[bandwidth]) { this.playlists[bandwidth] = m3u; @@ -257,22 +493,27 @@ class HLSSpliceVod { if (this.baseUrl) { for (let i = 0; i < this.playlists[bandwidth].items.PlaylistItem.length; i++) { let plItem = this.playlists[bandwidth].items.PlaylistItem[i]; - let uri = plItem.get('uri'); - plItem.set('uri', this.baseUrl + uri); + let uri = plItem.get("uri"); + plItem.set("uri", this.baseUrl + uri); } } - const targetDuration = this.playlists[bandwidth].get('targetDuration'); + const targetDuration = this.playlists[bandwidth].get("targetDuration"); if (targetDuration > this.targetDuration) { this.targetDuration = targetDuration; } - this.playlists[bandwidth].set('targetDuration', this.targetDuration); + this.playlists[bandwidth].set("targetDuration", this.targetDuration); + const initSegUri = this._getCmafMapUri(m3u, mediaManifestUri); + if (initSegUri) { + if (!this.cmafMapUri.video[bandwidth]) { + this.cmafMapUri.video[bandwidth] = initSegUri; + } + } resolve(); }); if (!_injectMediaManifest) { try { - request({uri: mediaManifestUri, gzip: true }) - .pipe(parser) + request({ uri: mediaManifestUri, gzip: true }).pipe(parser); } catch (exc) { reject(exc); } @@ -282,86 +523,188 @@ class HLSSpliceVod { }); } - _parseAdMasterManifest(manifestUri, _injectAdMasterManifest, _injectAdMediaManifest) { + _loadAudioManifest(audioManifestUri, group, lang, _injectAudioManifest) { return new Promise((resolve, reject) => { - let ad = {}; const parser = m3u8.createStream(); - parser.on('m3u', m3u => { + parser.on("m3u", (m3u) => { + this.duration = 0; + if (!this.playlistsAudio[group]) { + this.playlistsAudio[group] = {}; + } + if (!this.playlistsAudio[group][lang]) { + this.playlistsAudio[group][lang] = m3u; + } + + if (this.baseUrl) { + for (let i = 0; i < this.playlistsAudio[group][lang].items.PlaylistItem.length; i++) { + let plItem = this.playlistsAudio[group][lang].items.PlaylistItem[i]; + let uri = plItem.get("uri"); + plItem.set("uri", this.baseUrl + uri); + } + } + const targetDuration = this.playlistsAudio[group][lang].get("targetDuration"); + if (targetDuration > this.targetDurationAudio) { + this.targetDurationAudio = targetDuration; + } + this.playlistsAudio[group][lang].set("targetDuration", this.targetDurationAudio); + const initSegUri = this._getCmafMapUri(m3u, audioManifestUri); + if (initSegUri) { + if (!this.cmafMapUri.audio[group]) { + this.cmafMapUri.audio[group] = {}; + } + this.cmafMapUri.audio[group][lang] = initSegUri; + } + resolve(); + }); + + if (!_injectAudioManifest) { + try { + request({ uri: audioManifestUri, gzip: true }).pipe(parser); + } catch (exc) { + reject(exc); + } + } else { + _injectAudioManifest(group, lang).pipe(parser); + } + }); + } + + _parseAdMasterManifest(manifestUri, _injectAdMasterManifest, _injectAdMediaManifest, _injectAdAudioManifest) { + return new Promise((resolve, reject) => { + let ad = {}; + const parser = m3u8.createStream(); + parser.on("m3u", (m3u) => { let mediaManifestPromises = []; ad.master = m3u; ad.playlist = {}; + ad.playlistAudio = {}; ad.baseUrl = null; - const m = manifestUri.match(/^(.*)\/.*?$/); if (m) { - ad.baseUrl = m[1] + '/'; + ad.baseUrl = m[1] + "/"; } for (let i = 0; i < m3u.items.StreamItem.length; i++) { const streamItem = m3u.items.StreamItem[i]; - const mediaManifestUrl = url.resolve(ad.baseUrl, streamItem.get('uri')); + const mediaManifestUrl = url.resolve(ad.baseUrl, streamItem.get("uri")); const p = new Promise((res, rej) => { const mediaManifestParser = m3u8.createStream(); - mediaManifestParser.on('m3u', m3u => { - if (m3u.get('targetDuration') > this.targetDuration) { - this.targetDuration = m3u.get('targetDuration'); + mediaManifestParser.on("m3u", (m3u) => { + if (m3u.get("targetDuration") > this.targetDuration) { + this.targetDuration = m3u.get("targetDuration"); } ad.duration = 0; let baseUrl; - const n = mediaManifestUrl.match('^(.*)/.*?'); + const n = mediaManifestUrl.match("^(.*)/.*?"); if (n) { - baseUrl = n[1] + '/'; + baseUrl = n[1] + "/"; } for (let j = 0; j < m3u.items.PlaylistItem.length; j++) { let plItem = m3u.items.PlaylistItem[j]; const plUri = plItem.get("uri"); - if (!plUri.match('^http')) { + if (!plUri.match("^http")) { plItem.set("uri", url.resolve(baseUrl, plUri)); } ad.duration += plItem.get("duration"); } - ad.playlist[streamItem.get('bandwidth')] = m3u; + ad.playlist[streamItem.get("bandwidth")] = m3u; res(); }); - mediaManifestParser.on('error', err => { + mediaManifestParser.on("error", (err) => { rej(err); }); if (!_injectAdMediaManifest) { try { - request({ uri: mediaManifestUrl, gzip: true }) - .on('error', err => { - rej(err); - }) - .pipe(mediaManifestParser) + request({ uri: mediaManifestUrl, gzip: true }) + .on("error", (err) => { + rej(err); + }) + .pipe(mediaManifestParser); } catch (err) { rej(err); } } else { - _injectAdMediaManifest(streamItem.get('bandwidth')).pipe(mediaManifestParser); + _injectAdMediaManifest(streamItem.get("bandwidth")).pipe(mediaManifestParser); } }); mediaManifestPromises.push(p); } + let audioItems = m3u.items.MediaItem.filter((item) => { + return item.attributes.attributes.type === "AUDIO"; + }); + for (let i = 0; i < audioItems.length; i++) { + const audioItem = audioItems[i]; + const g = audioItem.get("group-id"); + const l = audioItem.attributes.attributes["language"]; + const audioManifestUrl = url.resolve(ad.baseUrl, audioItem.get("uri")); + const p = new Promise((res, rej) => { + const audioManifestParser = m3u8.createStream(); - Promise.all(mediaManifestPromises) - .then(() => { + audioManifestParser.on("m3u", (m3u) => { + if (m3u.get("targetDuration") > this.targetDurationAudio) { + this.targetDurationAudio = m3u.get("targetDuration"); + } + ad.durationAudio = 0; + let baseUrl; + const n = audioManifestUrl.match("^(.*)/.*?"); + if (n) { + baseUrl = n[1] + "/"; + } + for (let j = 0; j < m3u.items.PlaylistItem.length; j++) { + let plItem = m3u.items.PlaylistItem[j]; + const plUri = plItem.get("uri"); + if (!plUri.match("^http")) { + plItem.set("uri", url.resolve(baseUrl, plUri)); + } + ad.durationAudio += plItem.get("duration"); + } + + if (!ad.playlistAudio[g]) { + ad.playlistAudio[g] = {}; + } + if (!ad.playlistAudio[g][l]) { + ad.playlistAudio[g][l] = m3u; + } + res(); + }); + audioManifestParser.on("error", (err) => { + rej(err); + }); + if (!_injectAdAudioManifest) { + try { + request({ uri: audioManifestUrl, gzip: true }) + .on("error", (err) => { + rej(err); + }) + .pipe(audioManifestParser); + } catch (err) { + rej(err); + } + } else { + _injectAdAudioManifest(g, l).pipe(audioManifestParser); + } + }); + mediaManifestPromises.push(p); + } + + Promise.all(mediaManifestPromises).then(() => { resolve(ad); }); }); - parser.on('error', err => { + parser.on("error", (err) => { reject(err); }); if (!_injectAdMasterManifest) { try { request({ uri: manifestUri, gzip: true }) - .on('error', err => { - reject(err); - }) - .pipe(parser) + .on("error", (err) => { + reject(err); + }) + .pipe(parser); } catch (exc) { reject(exc); } @@ -370,6 +713,20 @@ class HLSSpliceVod { } }); } -}; + + _getCmafMapUri(m3u, manifestUri) { + let initSegment = undefined; + if (m3u.items.PlaylistItem[0].attributes.attributes["map-uri"]) { + initSegment = m3u.items.PlaylistItem[0].attributes.attributes["map-uri"]; + if (!initSegment.match("^http")) { + const n = manifestUri.match("^(.*)/.*?$"); + if (n) { + initSegment = url.resolve(n[1] + "/", initSegment); + } + } + } + return initSegment; + } +} module.exports = HLSSpliceVod; diff --git a/package-lock.json b/package-lock.json index 1242f08..b93287a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.3.5", "license": "MIT", "dependencies": { - "@eyevinn/m3u8": ">=0.4.3", + "@eyevinn/m3u8": ">=0.5.2", "request": "^2.88.2" }, "devDependencies": { @@ -248,9 +248,9 @@ } }, "node_modules/@eyevinn/m3u8": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@eyevinn/m3u8/-/m3u8-0.4.3.tgz", - "integrity": "sha512-bKqYKgKksoY+sAoBB2+4TYNkfjrZOoBVIkX5OrmLE666JFmXD8Z5QkoMXO4yp+pVHQqtT5qvyXcXI/3wyAG55A==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@eyevinn/m3u8/-/m3u8-0.5.2.tgz", + "integrity": "sha512-xIcBKo36ZhMG3JEM4r4+T/Bpz1QRG5wlbizo7d1onop6x76NUi+ACkXXxDlna7j+ELlljVhbkGhu0zTfCpJbHQ==", "dependencies": { "chunked-stream": "~0.0.2" } @@ -299,24 +299,20 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", + "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" } }, "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", "dev": true, "engines": { "node": ">=8" @@ -1150,9 +1146,9 @@ } }, "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" }, "node_modules/json-schema-traverse": { "version": "0.4.1", @@ -1165,10 +1161,13 @@ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, "bin": { "json5": "lib/cli.js" }, @@ -1177,17 +1176,17 @@ } }, "node_modules/jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "engines": [ + "node >=0.6.0" + ], "dependencies": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", - "json-schema": "0.4.0", + "json-schema": "0.2.3", "verror": "1.10.0" - }, - "engines": { - "node": ">=0.6.0" } }, "node_modules/lcov-parse": { @@ -1212,9 +1211,9 @@ } }, "node_modules/lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, "node_modules/lodash.flattendeep": { @@ -1276,9 +1275,9 @@ } }, "node_modules/minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true }, "node_modules/ms": { @@ -1444,9 +1443,9 @@ } }, "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", "dev": true }, "node_modules/performance-now": { @@ -1492,9 +1491,9 @@ } }, "node_modules/qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", "engines": { "node": ">=0.6" } @@ -1919,9 +1918,9 @@ } }, "node_modules/y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", "dev": true }, "node_modules/yargs": { @@ -2181,9 +2180,9 @@ } }, "@eyevinn/m3u8": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@eyevinn/m3u8/-/m3u8-0.4.3.tgz", - "integrity": "sha512-bKqYKgKksoY+sAoBB2+4TYNkfjrZOoBVIkX5OrmLE666JFmXD8Z5QkoMXO4yp+pVHQqtT5qvyXcXI/3wyAG55A==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@eyevinn/m3u8/-/m3u8-0.5.2.tgz", + "integrity": "sha512-xIcBKo36ZhMG3JEM4r4+T/Bpz1QRG5wlbizo7d1onop6x76NUi+ACkXXxDlna7j+ELlljVhbkGhu0zTfCpJbHQ==", "requires": { "chunked-stream": "~0.0.2" } @@ -2223,9 +2222,9 @@ } }, "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", + "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -2234,9 +2233,9 @@ } }, "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", "dev": true }, "ansi-styles": { @@ -2909,9 +2908,9 @@ "dev": true }, "json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" }, "json-schema-traverse": { "version": "0.4.1", @@ -2924,19 +2923,22 @@ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, "json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } }, "jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", "requires": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", - "json-schema": "0.4.0", + "json-schema": "0.2.3", "verror": "1.10.0" } }, @@ -2956,9 +2958,9 @@ } }, "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, "lodash.flattendeep": { @@ -3005,9 +3007,9 @@ } }, "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true }, "ms": { @@ -3137,9 +3139,9 @@ "dev": true }, "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", "dev": true }, "performance-now": { @@ -3176,9 +3178,9 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==" + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, "release-zalgo": { "version": "1.0.0", @@ -3521,9 +3523,9 @@ } }, "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", "dev": true }, "yargs": { diff --git a/package.json b/package.json index 85cacd8..348a5ca 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "nyc": "^15.0.1" }, "dependencies": { - "@eyevinn/m3u8": ">=0.4.3", + "@eyevinn/m3u8": ">=0.5.2", "request": "^2.88.2" } } diff --git a/spec/hls_splice_spec.js b/spec/hls_splice_spec.js index 560fe32..a799fcf 100644 --- a/spec/hls_splice_spec.js +++ b/spec/hls_splice_spec.js @@ -1,5 +1,5 @@ -const HLSSpliceVod = require('../index.js'); -const fs = require('fs'); +const HLSSpliceVod = require("../index.js"); +const fs = require("fs"); describe("HLSSpliceVod", () => { let mockMasterManifest; @@ -11,517 +11,1774 @@ describe("HLSSpliceVod", () => { beforeEach(() => { mockMasterManifest = () => { - return fs.createReadStream('testvectors/hls1/master.m3u8') + return fs.createReadStream("testvectors/hls1/master.m3u8"); }; mockMediaManifest = (bw) => { const bwmap = { 4497000: "0", - 2497000: "1" - } + 2497000: "1", + }; return fs.createReadStream(`testvectors/hls1/index_${bwmap[bw]}_av.m3u8`); }; mockMasterManifest1b = () => { - return fs.createReadStream('testvectors/hls1b/master.m3u8') + return fs.createReadStream("testvectors/hls1b/master.m3u8"); }; mockMediaManifest1b = (bw) => { const bwmap = { 4497000: "0", - 2497000: "1" - } + 2497000: "1", + }; return fs.createReadStream(`testvectors/hls1b/index_${bwmap[bw]}_av.m3u8`); }; mockAdMasterManifest = () => { - return fs.createReadStream('testvectors/ad1/master.m3u8') + return fs.createReadStream("testvectors/ad1/master.m3u8"); }; mockAdMediaManifest = (bw) => { const bwmap = { 4497000: "0", - 2497000: "1" - } + 2497000: "1", + }; return fs.createReadStream(`testvectors/ad1/index_${bwmap[bw]}_av.m3u8`); }; mockAdMasterManifest2 = () => { - return fs.createReadStream('testvectors/ad2/master.m3u8') + return fs.createReadStream("testvectors/ad2/master.m3u8"); }; mockAdMediaManifest2 = (bw) => { const bwmap = { 4397000: "0", - 2597000: "1" - } + 2597000: "1", + }; return fs.createReadStream(`testvectors/ad2/index_${bwmap[bw]}_av.m3u8`); }; mockAdMasterManifest3 = () => { - return fs.createReadStream('testvectors/ad3/master.m3u8') + return fs.createReadStream("testvectors/ad3/master.m3u8"); }; mockAdMediaManifest3 = (bw) => { const bwmap = { 4397000: "0", - 2597000: "1" - } + 2597000: "1", + }; return fs.createReadStream(`testvectors/ad3/index_${bwmap[bw]}_av.m3u8`); }; mockAdMasterManifest4 = () => { - return fs.createReadStream('testvectors/ad4/master.m3u8') + return fs.createReadStream("testvectors/ad4/master.m3u8"); }; mockAdMediaManifest4 = (bw) => { const bwmap = { 4397000: "0", - 2597000: "1" - } + 2597000: "1", + }; return fs.createReadStream(`testvectors/ad4/index_${bwmap[bw]}_av.m3u8`); }; mockBumperMasterManifest = () => { - return fs.createReadStream('testvectors/ad1/master.m3u8') + return fs.createReadStream("testvectors/ad1/master.m3u8"); }; mockBumperMediaManifest = (bw) => { const bwmap = { 4497000: "0", - 2497000: "1" - } + 2497000: "1", + }; return fs.createReadStream(`testvectors/ad1/index_${bwmap[bw]}_av.m3u8`); }; }); - it("can prepend a baseurl on each segment", done => { - const mockVod = new HLSSpliceVod('http://mock.com/mock.m3u8', { baseUrl: 'https://baseurl.com/'}); - mockVod.load(mockMasterManifest, mockMediaManifest) - .then(() => { + it("can prepend a baseurl on each segment", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8", { baseUrl: "https://baseurl.com/" }); + mockVod.load(mockMasterManifest, mockMediaManifest).then(() => { const m3u8 = mockVod.getMediaManifest(4497000); - const m = m3u8.match('https://baseurl.com/segment3_0_av.ts'); + const m = m3u8.match("https://baseurl.com/segment3_0_av.ts"); expect(m).not.toBe(null); done(); }); }); - it("can provide absolute urls on each segment", done => { - const mockVod = new HLSSpliceVod('http://mock.com/mock.m3u8', { absoluteUrls: true }); - mockVod.load(mockMasterManifest, mockMediaManifest) - .then(() => { + it("can provide absolute urls on each segment", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8", { absoluteUrls: true }); + mockVod.load(mockMasterManifest, mockMediaManifest).then(() => { const m3u8 = mockVod.getMediaManifest(4497000); - const m = m3u8.match('http://mock.com/segment3_0_av.ts'); + const m = m3u8.match("http://mock.com/segment3_0_av.ts"); expect(m).not.toBe(null); done(); }); }); + it("contains a 15 second splice at 9 seconds from start", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8"); + mockVod + .load(mockMasterManifest, mockMediaManifest) + .then(() => { + return mockVod.insertAdAt(9000, "http://mock.com/ad/mockad.m3u8", mockAdMasterManifest, mockAdMediaManifest); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + const m = m3u8.match( + /#EXTINF:9.0000,\s+segment1_0_av.ts\s+#EXT-X-DISCONTINUITY\s+#EXT-X-CUE-OUT:DURATION=15\s+#EXTINF:3.0000,\s+http:\/\/mock.com\/ad\/ad1_0_av.ts/ + ); + expect(m).not.toBe(null); + done(); + }); + }); - it("contains a 15 second splice at 9 seconds from start", done => { - const mockVod = new HLSSpliceVod('http://mock.com/mock.m3u8'); - mockVod.load(mockMasterManifest, mockMediaManifest) - .then(() => { - return mockVod.insertAdAt(9000, 'http://mock.com/ad/mockad.m3u8', mockAdMasterManifest, mockAdMediaManifest); - }) - .then(() => { - const m3u8 = mockVod.getMediaManifest(4497000); - const m = m3u8.match(/#EXTINF:9.0000,\s+segment1_0_av.ts\s+#EXT-X-DISCONTINUITY\s+#EXT-X-CUE-OUT:DURATION=15\s+#EXTINF:3.0000,\s+http:\/\/mock.com\/ad\/ad1_0_av.ts/); - expect(m).not.toBe(null); - done(); - }); + it("can handle ad with non matching profile", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8"); + mockVod + .load(mockMasterManifest, mockMediaManifest) + .then(() => { + return mockVod.insertAdAt(9000, "http://mock.com/ad2/mockad.m3u8", mockAdMasterManifest2, mockAdMediaManifest2); + }) + .then(() => { + let m3u8 = mockVod.getMediaManifest(4497000); + let m = m3u8.match( + /#EXTINF:9.0000,\s+segment1_0_av.ts\s+#EXT-X-DISCONTINUITY\s+#EXT-X-CUE-OUT:DURATION=15\s+#EXTINF:3.0000,\s+http:\/\/mock.com\/ad2\/ad1_0_av.ts/ + ); + expect(m).not.toBe(null); + m3u8 = mockVod.getMediaManifest(2497000); + m = m3u8.match( + /#EXTINF:9.0000,\s+segment1_0_av.ts\s+#EXT-X-DISCONTINUITY\s+#EXT-X-CUE-OUT:DURATION=15\s+#EXTINF:3.0000,\s+http:\/\/mock.com\/ad2\/ad1_0_av.ts/ + ); + expect(m).toBe(null); + done(); + }); }); - it("can handle ad with non matching profile", done => { - const mockVod = new HLSSpliceVod('http://mock.com/mock.m3u8'); - mockVod.load(mockMasterManifest, mockMediaManifest) - .then(() => { - return mockVod.insertAdAt(9000, 'http://mock.com/ad2/mockad.m3u8', mockAdMasterManifest2, mockAdMediaManifest2); - }) - .then(() => { - let m3u8 = mockVod.getMediaManifest(4497000); - let m = m3u8.match(/#EXTINF:9.0000,\s+segment1_0_av.ts\s+#EXT-X-DISCONTINUITY\s+#EXT-X-CUE-OUT:DURATION=15\s+#EXTINF:3.0000,\s+http:\/\/mock.com\/ad2\/ad1_0_av.ts/); - expect(m).not.toBe(null); - m3u8 = mockVod.getMediaManifest(2497000); - m = m3u8.match(/#EXTINF:9.0000,\s+segment1_0_av.ts\s+#EXT-X-DISCONTINUITY\s+#EXT-X-CUE-OUT:DURATION=15\s+#EXTINF:3.0000,\s+http:\/\/mock.com\/ad2\/ad1_0_av.ts/); - expect(m).toBe(null); + it("contains a 15 sec splice at 9 sec and at 30 sec from start", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8"); + mockVod + .load(mockMasterManifest, mockMediaManifest) + .then(() => { + return mockVod.insertAdAt(9000, "http://mock.com/ad/mockad.m3u8", mockAdMasterManifest, mockAdMediaManifest); + }) + .then(() => { + return mockVod.insertAdAt(30000, "http://mock.com/ad/mockad.m3u8", mockAdMasterManifest, mockAdMediaManifest); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + const m = m3u8.match( + /#EXTINF:9.0000,\s+segment1_0_av.ts\s+#EXT-X-DISCONTINUITY\s+#EXT-X-CUE-OUT:DURATION=15\s+#EXTINF:3.0000,\s+http:\/\/mock.com\/ad\/ad1_0_av.ts/ + ); + expect(m).not.toBe(null); + const n = m3u8.match( + /#EXTINF:9.0000,\s+segment2_0_av.ts\s+#EXT-X-DISCONTINUITY\s+#EXT-X-CUE-OUT:DURATION=15\s+#EXTINF:3.0000,\s+http:\/\/mock.com\/ad\/ad1_0_av.ts/ + ); + expect(n).not.toBe(null); + done(); + }); + }); + + it("handles two ads in a row", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8"); + mockVod + .load(mockMasterManifest, mockMediaManifest) + .then(() => { + return mockVod.insertAdAt(0, "http://mock.com/ad/mockad.m3u8", mockAdMasterManifest, mockAdMediaManifest); + }) + .then(() => { + // This one will go first + return mockVod.insertAdAt(0, "http://mock.com/ad/mockad.m3u8", mockAdMasterManifest3, mockAdMediaManifest3); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + const lines = m3u8.split("\n"); + expect(lines[8]).toEqual("#EXT-X-CUE-OUT:DURATION=3"); + expect(lines[12]).toEqual("#EXT-X-CUE-OUT:DURATION=15"); + expect(lines[lines.length - 2]).toEqual("#EXT-X-ENDLIST"); + done(); + }); + }); + + it("handles two ads in a row merged into one break", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8", { merge: true }); + mockVod + .load(mockMasterManifest, mockMediaManifest) + .then(() => { + return mockVod.insertAdAt(0, "http://mock.com/ad/mockad.m3u8", mockAdMasterManifest, mockAdMediaManifest); + }) + .then(() => { + // This one will go first + return mockVod.insertAdAt(0, "http://mock.com/ad/mockad.m3u8", mockAdMasterManifest3, mockAdMediaManifest3); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + const lines = m3u8.split("\n"); + expect(lines[8]).toEqual("#EXT-X-CUE-OUT:DURATION=18"); + expect(lines[lines.length - 2]).toEqual("#EXT-X-ENDLIST"); + done(); + }); + }); + + it("handles two ads that should not be merged into one break", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8", { merge: true }); + mockVod + .load(mockMasterManifest, mockMediaManifest) + .then(() => { + return mockVod.insertAdAt(9, "http://mock.com/ad/mockad.m3u8", mockAdMasterManifest, mockAdMediaManifest); + }) + .then(() => { + // This one will go first + return mockVod.insertAdAt(0, "http://mock.com/ad/mockad.m3u8", mockAdMasterManifest3, mockAdMediaManifest3); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + const lines = m3u8.split("\n"); + expect(lines[8]).toEqual("#EXT-X-CUE-OUT:DURATION=3"); + expect(lines[16]).toEqual("#EXT-X-CUE-OUT:DURATION=15"); + expect(lines[lines.length - 2]).toEqual("#EXT-X-ENDLIST"); + done(); + }); + }); + + it("handles post-roll ads", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8"); + mockVod + .load(mockMasterManifest, mockMediaManifest) + .then(() => { + return mockVod.insertAdAt(-1, "http://mock.com/ad/mockad.m3u8", mockAdMasterManifest, mockAdMediaManifest); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + const lines = m3u8.split("\n"); + expect(lines[28]).toEqual("#EXT-X-CUE-OUT:DURATION=15"); + expect(lines[39]).toEqual("#EXT-X-CUE-IN"); + expect(lines[lines.length - 2]).toEqual("#EXT-X-ENDLIST"); + done(); + }); + }); + + it("handles two post-roll ads", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8"); + mockVod + .load(mockMasterManifest, mockMediaManifest) + .then(() => { + return mockVod.insertAdAt(-1, "http://mock.com/ad/mockad.m3u8", mockAdMasterManifest, mockAdMediaManifest); + }) + .then(() => { + return mockVod.insertAdAt(-1, "http://mock.com/ad/mockad.m3u8", mockAdMasterManifest, mockAdMediaManifest); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + const lines = m3u8.split("\n"); + expect(lines[28]).toEqual("#EXT-X-CUE-OUT:DURATION=15"); + expect(lines[41]).toEqual("#EXT-X-CUE-IN"); + expect(lines[52]).toEqual("#EXT-X-CUE-IN"); + expect(lines[lines.length - 2]).toEqual("#EXT-X-ENDLIST"); + done(); + }); + }); + + it("handles one pre-roll and one post-roll", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8"); + mockVod + .load(mockMasterManifest, mockMediaManifest) + .then(() => { + return mockVod.insertAdAt(0, "http://mock.com/ad/mockad.m3u8", mockAdMasterManifest, mockAdMediaManifest); + }) + .then(() => { + return mockVod.insertAdAt(-1, "http://mock.com/ad/mockad.m3u8", mockAdMasterManifest, mockAdMediaManifest); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + const lines = m3u8.split("\n"); + expect(lines[lines.length - 3]).toEqual("#EXT-X-CUE-IN"); + expect(lines[lines.length - 2]).toEqual("#EXT-X-ENDLIST"); + done(); + }); + }); + + it("handles video bumper without any ads", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8"); + mockVod + .load(mockMasterManifest, mockMediaManifest) + .then(() => { + return mockVod.insertBumper( + "http://mock.com/ad/mockbumper.m3u8", + mockBumperMasterManifest, + mockBumperMediaManifest + ); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + const lines = m3u8.split("\n"); + expect(lines[8]).toEqual("http://mock.com/ad/ad1_0_av.ts"); + expect(lines[17]).toEqual("#EXT-X-DISCONTINUITY"); + expect(lines[18]).not.toEqual("#EXT-X-CUE-IN"); + done(); + }); + }); + + it("handles video bumper with one ad", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8"); + mockVod + .load(mockMasterManifest, mockMediaManifest) + .then(() => { + return mockVod.insertAdAt(0, "http://mock.com/ad/mockad.m3u8", mockAdMasterManifest, mockAdMediaManifest); + }) + .then(() => { + return mockVod.insertBumper( + "http://mock.com/ad/mockbumper.m3u8", + mockBumperMasterManifest, + mockBumperMediaManifest + ); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + const lines = m3u8.split("\n"); + expect(lines[8]).toEqual("http://mock.com/ad/ad1_0_av.ts"); + expect(lines[17]).toEqual("#EXT-X-DISCONTINUITY"); + expect(lines[18]).not.toEqual("#EXT-X-CUE-IN"); + expect(lines[18]).toEqual("#EXT-X-CUE-OUT:DURATION=15"); + done(); + }); + }); + + it("handles video bumper and two ads in a row merged into one break", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8", { merge: true }); + mockVod + .load(mockMasterManifest, mockMediaManifest) + .then(() => { + return mockVod.insertAdAt(0, "http://mock.com/ad/mockad.m3u8", mockAdMasterManifest, mockAdMediaManifest); + }) + .then(() => { + // This one will go first + return mockVod.insertAdAt(0, "http://mock.com/ad/mockad.m3u8", mockAdMasterManifest3, mockAdMediaManifest3); + }) + .then(() => { + return mockVod.insertBumper( + "http://mock.com/ad/mockbumper.m3u8", + mockBumperMasterManifest, + mockBumperMediaManifest + ); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + const lines = m3u8.split("\n"); + expect(lines[10 + 8]).toEqual("#EXT-X-CUE-OUT:DURATION=18"); + expect(lines[lines.length - 2]).toEqual("#EXT-X-ENDLIST"); + done(); + }); + }); + + it("handles target duration for video bumper and two ads in a row merged into one break", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8", { merge: true }); + mockVod + .load(mockMasterManifest1b, mockMediaManifest1b) + .then(() => { + return mockVod.insertAdAt(0, "http://mock.com/ad/mockad.m3u8", mockAdMasterManifest, mockAdMediaManifest); + }) + .then(() => { + return mockVod.insertAdAt(0, "http://mock.com/ad/mockad.m3u8", mockAdMasterManifest3, mockAdMediaManifest3); + }) + .then(() => { + // This one will go first + return mockVod.insertAdAt(0, "http://mock.com/ad/mockad.m3u8", mockAdMasterManifest4, mockAdMediaManifest4); + }) + .then(() => { + return mockVod.insertBumper( + "http://mock.com/ad/mockbumper.m3u8", + mockBumperMasterManifest, + mockBumperMediaManifest + ); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + const lines = m3u8.split("\n"); + expect(lines[1]).toEqual("#EXT-X-TARGETDURATION:5"); + expect(lines[10 + 8]).toEqual("#EXT-X-CUE-OUT:DURATION=23"); + expect(lines[lines.length - 2]).toEqual("#EXT-X-ENDLIST"); + done(); + }); + }); + + it("handles target duration with video bumper and no ads", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8", { merge: true }); + mockVod + .load(mockMasterManifest1b, mockMediaManifest1b) + .then(() => { + return mockVod.insertBumper("http://mock.com/ad/mockbumper.m3u8", mockAdMasterManifest4, mockAdMediaManifest4); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + const lines = m3u8.split("\n"); + expect(lines[1]).toEqual("#EXT-X-TARGETDURATION:5"); + done(); + }); + }); + + it("ensures that video bumper is always first", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8"); + mockVod + .load(mockMasterManifest, mockMediaManifest) + .then(() => { + return mockVod.insertBumper( + "http://mock.com/ad/mockbumper.m3u8", + mockBumperMasterManifest, + mockBumperMediaManifest + ); + }) + .then(() => { + return mockVod.insertAdAt(0, "http://mock.com/ad/mockad.m3u8", mockAdMasterManifest, mockAdMediaManifest); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + const lines = m3u8.split("\n"); + expect(lines[8]).toEqual("http://mock.com/ad/ad1_0_av.ts"); + expect(lines[17]).toEqual("#EXT-X-DISCONTINUITY"); + expect(lines[18]).not.toEqual("#EXT-X-CUE-IN"); + expect(lines[18]).toEqual("#EXT-X-CUE-OUT:DURATION=15"); + done(); + }); + }); + + it("can insert interstitial with an assetlist", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8"); + mockVod + .load(mockMasterManifest, mockMediaManifest) + .then(() => { + return mockVod.insertInterstitialAt(16000, "001", "http://mock.com/assetlist", true); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + const lines = m3u8.split("\n"); + expect(lines[12]).toEqual( + '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:16.001Z",X-ASSET-LIST="http://mock.com/assetlist"' + ); + done(); + }); + }); + + it("can insert interstitial with an relative assetlist URL", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8"); + mockVod + .load(mockMasterManifest, mockMediaManifest) + .then(() => { + return mockVod.insertInterstitialAt(16000, "001", "/assetlist/sdfsdfjlsdfsdf", true); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + const lines = m3u8.split("\n"); + expect(lines[12]).toEqual( + '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:16.001Z",X-ASSET-LIST="/assetlist/sdfsdfjlsdfsdf"' + ); + done(); + }); + }); + + it("can insert interstitial with an asset uri", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8"); + mockVod + .load(mockMasterManifest, mockMediaManifest) + .then(() => { + return mockVod.insertInterstitialAt(18000, "001", "http://mock.com/asseturi", false); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + const lines = m3u8.split("\n"); + expect(lines[12]).toEqual( + '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",X-ASSET-URI="http://mock.com/asseturi"' + ); + done(); + }); + }); + + it("can insert interstitial with an asset uri and a resume offset", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8"); + mockVod + .load(mockMasterManifest, mockMediaManifest) + .then(() => { + return mockVod.insertInterstitialAt(18000, "001", "http://mock.com/asseturi", false, { + resumeOffset: 10500, + }); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + const lines = m3u8.split("\n"); + expect(lines[12]).toEqual( + '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",X-ASSET-URI="http://mock.com/asseturi",X-RESUME-OFFSET="10.5"' + ); + done(); + }); + }); + + it("can insert interstitial with an asset uri and a resume offset that is 0", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8"); + mockVod + .load(mockMasterManifest, mockMediaManifest) + .then(() => { + return mockVod.insertInterstitialAt(18000, "001", "http://mock.com/asseturi", false, { + resumeOffset: 0, + }); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + const lines = m3u8.split("\n"); + expect(lines[12]).toEqual( + '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",X-ASSET-URI="http://mock.com/asseturi",X-RESUME-OFFSET="0"' + ); + done(); + }); + }); + + it("can insert interstitial with an asset uri and a playout limit", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8"); + mockVod + .load(mockMasterManifest, mockMediaManifest) + .then(() => { + return mockVod.insertInterstitialAt(18000, "001", "http://mock.com/asseturi", false, { + playoutLimit: 12500, + }); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + const lines = m3u8.split("\n"); + expect(lines[12]).toEqual( + '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",X-ASSET-URI="http://mock.com/asseturi",X-PLAYOUT-LIMIT="12.5"' + ); + done(); + }); + }); + + it("can insert interstitial with an asset uri and a snap IN", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8"); + mockVod + .load(mockMasterManifest, mockMediaManifest) + .then(() => { + return mockVod.insertInterstitialAt(18000, "001", "http://mock.com/asseturi", false, { + snap: "IN", + }); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + const lines = m3u8.split("\n"); + expect(lines[12]).toEqual( + '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",X-ASSET-URI="http://mock.com/asseturi",X-SNAP="IN"' + ); + done(); + }); + }); + + it("can insert interstitial with an asset uri and a snap OUT", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8"); + mockVod + .load(mockMasterManifest, mockMediaManifest) + .then(() => { + return mockVod.insertInterstitialAt(18000, "001", "http://mock.com/asseturi", false, { + snap: "OUT", + }); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + const lines = m3u8.split("\n"); + expect(lines[12]).toEqual( + '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",X-ASSET-URI="http://mock.com/asseturi",X-SNAP="OUT"' + ); + done(); + }); + }); + + it("can insert interstitial with an assetlist uri and a planned duration", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8"); + mockVod + .load(mockMasterManifest, mockMediaManifest) + .then(() => { + return mockVod.insertInterstitialAt(18000, "001", "http://mock.com/asseturi", true, { + plannedDuration: 30000, + }); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + const lines = m3u8.split("\n"); + expect(lines[12]).toEqual( + '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",DURATION="30",X-ASSET-LIST="http://mock.com/asseturi"' + ); + done(); + }); + }); +}); + +describe("HLSSpliceVod with Demuxed Audio Tracks,", () => { + let mockMasterManifest; + let mockMediaManifest; + let mockAudioManifest; + let mockAdMasterManifest; + let mockAdMediaManifest; + let mockAdAudioManifest; + let mockAdMasterManifest2; + let mockAdMediaManifest2; + let mockAdAudioManifest2; + const _log = (s) => console.log(JSON.stringify(s, null, 2)); + beforeEach(() => { + // MOCK VOD #1 + mockMasterManifest = () => { + return fs.createReadStream("testvectors/demux/hls1/master.m3u8"); + }; + mockMediaManifest = (bw) => { + const bwmap = { + 4497000: "0", + 2497000: "1", + }; + return fs.createReadStream(`testvectors/demux/hls1/index_${bwmap[bw]}_v.m3u8`); + }; + mockAudioManifest = (g, l) => { + return fs.createReadStream(`testvectors/demux/hls1/index_${g}-${l}_a.m3u8`); + }; + // MOCK VOD #2 + mockMasterManifest1b = () => { + return fs.createReadStream("testvectors/demux/hls1b/master.m3u8"); + }; + mockMediaManifest1b = (bw) => { + const bwmap = { + 4497000: "0", + 2497000: "1", + }; + return fs.createReadStream(`testvectors/demux/hls1b/index_${bwmap[bw]}_v.m3u8`); + }; + mockAudioManifest1b = (g, l) => { + return fs.createReadStream(`testvectors/demux/hls1b/index_${g}-${l}_a.m3u8`); + }; + // MOCK VOD #3 + mockAdMasterManifest = () => { + return fs.createReadStream("testvectors/demux/ad1/master.m3u8"); + }; + mockAdMediaManifest = (bw) => { + const bwmap = { + 4497000: "0", + 2497000: "1", + }; + return fs.createReadStream(`testvectors/demux/ad1/index_${bwmap[bw]}_v.m3u8`); + }; + mockAdAudioManifest = (g, l) => { + return fs.createReadStream(`testvectors/demux/ad1/index_${g}-${l}_a.m3u8`); + }; + // MOCK VOD #4 + mockAdMasterManifest2 = () => { + return fs.createReadStream("testvectors/demux/ad2/master.m3u8"); + }; + mockAdMediaManifest2 = (bw) => { + const bwmap = { + 4397000: "0", + 2597000: "1", + }; + return fs.createReadStream(`testvectors/demux/ad2/index_${bwmap[bw]}_v.m3u8`); + }; + mockAdAudioManifest2 = (g, l) => { + return fs.createReadStream(`testvectors/demux/ad2/index_${g}-${l}_a.m3u8`); + }; + // MOCK VOD #5 + mockAdMasterManifest3 = () => { + return fs.createReadStream("testvectors/demux/ad3/master.m3u8"); + }; + mockAdMediaManifest3 = (bw) => { + const bwmap = { + 4497000: "0", + 2497000: "1", + }; + return fs.createReadStream(`testvectors/demux/ad3/index_${bwmap[bw]}_v.m3u8`); + }; + mockAdAudioManifest3 = (g, l) => { + return fs.createReadStream(`testvectors/demux/ad3/index_${g}-${l}_a.m3u8`); + }; + // MOCK VOD #6 + mockAdMasterManifest4 = () => { + return fs.createReadStream("testvectors/demux/ad4/master.m3u8"); + }; + mockAdMediaManifest4 = (bw) => { + const bwmap = { + 4497000: "0", + 2497000: "1", + }; + return fs.createReadStream(`testvectors/demux/ad4/index_${bwmap[bw]}_v.m3u8`); + }; + mockAdAudioManifest4 = (g, l) => { + return fs.createReadStream(`testvectors/demux/ad4/index_${g}-${l}_a.m3u8`); + }; + // MOCK VOD #7 + mockBumperMasterManifest = () => { + return fs.createReadStream("testvectors/demux/ad1/master.m3u8"); + }; + mockBumperMediaManifest = (bw) => { + const bwmap = { + 4497000: "0", + 2497000: "1", + }; + return fs.createReadStream(`testvectors/demux/ad1/index_${bwmap[bw]}_v.m3u8`); + }; + mockBumperAudioManifest = (g, l) => { + return fs.createReadStream(`testvectors/demux/ad1/index_${g}-${l}_a.m3u8`); + }; + }); + + it("can prepend a baseurl on each segment", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8", { baseUrl: "https://baseurl.com/" }); + mockVod.load(mockMasterManifest, mockMediaManifest, mockAudioManifest).then(() => { + const m3u8v = mockVod.getMediaManifest(4497000); + const m3u8a = mockVod.getAudioManifest("mono", "en"); + const m1 = m3u8v.match("https://baseurl.com/segment3_0_av.ts"); + const m2 = m3u8a.match("https://baseurl.com/segment3_men_a.ts"); + expect(m1).not.toBe(null); + expect(m2).not.toBe(null); done(); }); }); - it("contains a 15 sec splice at 9 sec and at 30 sec from start", done => { - const mockVod = new HLSSpliceVod('http://mock.com/mock.m3u8'); - mockVod.load(mockMasterManifest, mockMediaManifest) - .then(() => { - return mockVod.insertAdAt(9000, 'http://mock.com/ad/mockad.m3u8', mockAdMasterManifest, mockAdMediaManifest); - }) - .then(() => { - return mockVod.insertAdAt(30000, 'http://mock.com/ad/mockad.m3u8', mockAdMasterManifest, mockAdMediaManifest); - }) - .then(() => { + it("can provide absolute urls on each segment", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8", { absoluteUrls: true }); + mockVod.load(mockMasterManifest, mockMediaManifest, mockAudioManifest).then(() => { const m3u8 = mockVod.getMediaManifest(4497000); - const m = m3u8.match(/#EXTINF:9.0000,\s+segment1_0_av.ts\s+#EXT-X-DISCONTINUITY\s+#EXT-X-CUE-OUT:DURATION=15\s+#EXTINF:3.0000,\s+http:\/\/mock.com\/ad\/ad1_0_av.ts/); + const m3u8a = mockVod.getAudioManifest("stereo", "sv"); + const m = m3u8.match("http://mock.com/segment3_0_av.ts"); + const m2 = m3u8a.match("http://mock.com/segment6_ssv_a.ts"); expect(m).not.toBe(null); - const n = m3u8.match(/#EXTINF:9.0000,\s+segment2_0_av.ts\s+#EXT-X-DISCONTINUITY\s+#EXT-X-CUE-OUT:DURATION=15\s+#EXTINF:3.0000,\s+http:\/\/mock.com\/ad\/ad1_0_av.ts/); - expect(n).not.toBe(null); + expect(m2).not.toBe(null); done(); }); }); - it("handles two ads in a row", done => { - const mockVod = new HLSSpliceVod('http://mock.com/mock.m3u8'); - mockVod.load(mockMasterManifest, mockMediaManifest) - .then(() => { - return mockVod.insertAdAt(0, 'http://mock.com/ad/mockad.m3u8', mockAdMasterManifest, mockAdMediaManifest); - }) - .then(() => { - // This one will go first - return mockVod.insertAdAt(0, 'http://mock.com/ad/mockad.m3u8', mockAdMasterManifest3, mockAdMediaManifest3); - }) - .then(() => { - const m3u8 = mockVod.getMediaManifest(4497000); - const lines = m3u8.split('\n'); - expect(lines[8]).toEqual("#EXT-X-CUE-OUT:DURATION=3"); - expect(lines[12]).toEqual("#EXT-X-CUE-OUT:DURATION=15"); - expect(lines[lines.length - 2]).toEqual("#EXT-X-ENDLIST"); - done(); - }); + it("contains a 15 second splice at 9 seconds from start", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8"); + mockVod + .load(mockMasterManifest, mockMediaManifest, mockAudioManifest) + .then(() => { + return mockVod.insertAdAt( + 9000, + "http://mock.com/ad/mockad.m3u8", + mockAdMasterManifest, + mockAdMediaManifest, + mockAdAudioManifest + ); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + const m3u8Audio = mockVod.getAudioManifest("stereo", "en"); + const m = m3u8.match( + /#EXTINF:9.0000,\s+segment1_0_av.ts\s+#EXT-X-DISCONTINUITY\s+#EXT-X-CUE-OUT:DURATION=15\s+#EXTINF:3.0000,\s+http:\/\/mock.com\/ad\/ad1_0_av.ts/ + ); + const m2 = m3u8Audio.match( + /#EXTINF:9.0000,\s+segment1_sen_a.ts\s+#EXT-X-DISCONTINUITY\s+#EXT-X-CUE-OUT:DURATION=15\s+#EXTINF:3.0000,\s+http:\/\/mock.com\/ad\/ad1_sen_a.ts/ + ); + expect(m).not.toBe(null); + expect(m2).not.toBe(null); + done(); + }); }); - it("handles two ads in a row merged into one break", done => { - const mockVod = new HLSSpliceVod('http://mock.com/mock.m3u8', { merge: true }); - mockVod.load(mockMasterManifest, mockMediaManifest) - .then(() => { - return mockVod.insertAdAt(0, 'http://mock.com/ad/mockad.m3u8', mockAdMasterManifest, mockAdMediaManifest); - }) - .then(() => { - // This one will go first - return mockVod.insertAdAt(0, 'http://mock.com/ad/mockad.m3u8', mockAdMasterManifest3, mockAdMediaManifest3); - }) - .then(() => { - const m3u8 = mockVod.getMediaManifest(4497000); - const lines = m3u8.split('\n'); - expect(lines[8]).toEqual("#EXT-X-CUE-OUT:DURATION=18"); - expect(lines[lines.length - 2]).toEqual("#EXT-X-ENDLIST"); - done(); - }); + it("can handle ad with non matching profile", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8"); + mockVod + .load(mockMasterManifest, mockMediaManifest, mockAudioManifest) + .then(() => { + return mockVod.insertAdAt( + 9000, + "http://mock.com/ad2/mockad.m3u8", + mockAdMasterManifest2, + mockAdMediaManifest2, + mockAdAudioManifest2 + ); + }) + .then(() => { + let m3u8 = mockVod.getMediaManifest(4497000); + let m3u8Audio = mockVod.getAudioManifest("stereo", "en"); + let m = m3u8.match( + /#EXTINF:9.0000,\s+segment1_0_av.ts\s+#EXT-X-DISCONTINUITY\s+#EXT-X-CUE-OUT:DURATION=15\s+#EXTINF:3.0000,\s+http:\/\/mock.com\/ad2\/ad1_0_av.ts/ + ); + m2 = m3u8.match( + /#EXTINF:9.0000,\s+segment1_0_av.ts\s+#EXT-X-DISCONTINUITY\s+#EXT-X-CUE-OUT:DURATION=15\s+#EXTINF:3.0000,\s+http:\/\/mock.com\/ad2\/ad1_ano_a.ts/ + ); + expect(m).not.toBe(null); + expect(m2).toBe(null); + m3u8 = mockVod.getMediaManifest(2497000); + m3u8Audio = mockVod.getAudioManifest("mono", "sv"); + m = m3u8.match( + /#EXTINF:9.0000,\s+segment1_0_av.ts\s+#EXT-X-DISCONTINUITY\s+#EXT-X-CUE-OUT:DURATION=15\s+#EXTINF:3.0000,\s+http:\/\/mock.com\/ad2\/ad1_0_av.ts/ + ); + m2 = m3u8Audio.match( + /#EXTINF:9.0000,\s+segment1_0_av.ts\s+#EXT-X-DISCONTINUITY\s+#EXT-X-CUE-OUT:DURATION=15\s+#EXTINF:3.0000,\s+http:\/\/mock.com\/ad2\/ad1_ano_a.ts/ + ); + expect(m).toBe(null); + expect(m2).toBe(null); + done(); + }); }); - it("handles two ads that should not be merged into one break", done => { - const mockVod = new HLSSpliceVod('http://mock.com/mock.m3u8', { merge: true }); - mockVod.load(mockMasterManifest, mockMediaManifest) - .then(() => { - return mockVod.insertAdAt(9, 'http://mock.com/ad/mockad.m3u8', mockAdMasterManifest, mockAdMediaManifest); - }) - .then(() => { - // This one will go first - return mockVod.insertAdAt(0, 'http://mock.com/ad/mockad.m3u8', mockAdMasterManifest3, mockAdMediaManifest3); - }) - .then(() => { - const m3u8 = mockVod.getMediaManifest(4497000); - const lines = m3u8.split('\n'); - expect(lines[8]).toEqual("#EXT-X-CUE-OUT:DURATION=3"); - expect(lines[16]).toEqual("#EXT-X-CUE-OUT:DURATION=15"); - expect(lines[lines.length - 2]).toEqual("#EXT-X-ENDLIST"); - done(); - }); + it("contains a 15 sec splice at 9 sec and at 30 sec from start", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8"); + mockVod + .load(mockMasterManifest, mockMediaManifest, mockAudioManifest) + .then(() => { + return mockVod.insertAdAt( + 9000, + "http://mock.com/ad/mockad.m3u8", + mockAdMasterManifest, + mockAdMediaManifest, + mockAdAudioManifest + ); + }) + .then(() => { + return mockVod.insertAdAt( + 30000, + "http://mock.com/ad/mockad.m3u8", + mockAdMasterManifest, + mockAdMediaManifest, + mockAdAudioManifest + ); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + const m3u8Audio = mockVod.getAudioManifest("stereo", "en"); + const m = m3u8.match( + /#EXTINF:9.0000,\s+segment1_0_av.ts\s+#EXT-X-DISCONTINUITY\s+#EXT-X-CUE-OUT:DURATION=15\s+#EXTINF:3.0000,\s+http:\/\/mock.com\/ad\/ad1_0_av.ts/ + ); + const m2 = m3u8Audio.match( + /#EXTINF:9.0000,\s+segment1_sen_a.ts\s+#EXT-X-DISCONTINUITY\s+#EXT-X-CUE-OUT:DURATION=15\s+#EXTINF:3.0000,\s+http:\/\/mock.com\/ad\/ad1_sen_a.ts/ + ); + expect(m).not.toBe(null); + expect(m2).not.toBe(null); + const n = m3u8.match( + /#EXTINF:9.0000,\s+segment2_0_av.ts\s+#EXT-X-DISCONTINUITY\s+#EXT-X-CUE-OUT:DURATION=15\s+#EXTINF:3.0000,\s+http:\/\/mock.com\/ad\/ad1_0_av.ts/ + ); + const n2 = m3u8Audio.match( + /#EXTINF:9.0000,\s+segment2_sen_a.ts\s+#EXT-X-DISCONTINUITY\s+#EXT-X-CUE-OUT:DURATION=15\s+#EXTINF:3.0000,\s+http:\/\/mock.com\/ad\/ad1_sen_a.ts/ + ); + expect(n).not.toBe(null); + expect(n2).not.toBe(null); + done(); + }); }); + it("handles two ads in a row", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8"); + mockVod + .load(mockMasterManifest, mockMediaManifest, mockAudioManifest) + .then(() => { + return mockVod.insertAdAt( + 0, + "http://mock.com/ad/mockad.m3u8", + mockAdMasterManifest, + mockAdMediaManifest, + mockAdAudioManifest + ); + }) + .then(() => { + return mockVod.insertAdAt( + 0, + "http://mock.com/ad3/mockad.m3u8", + mockAdMasterManifest3, + mockAdMediaManifest3, + mockAdAudioManifest3 + ); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + const lines = m3u8.split("\n"); + expect(lines[8]).toEqual("#EXT-X-CUE-OUT:DURATION=3"); + expect(lines[12]).toEqual("#EXT-X-CUE-OUT:DURATION=15"); + expect(lines[lines.length - 2]).toEqual("#EXT-X-ENDLIST"); + const m3u8Audio = mockVod.getAudioManifest("stereo", "sv"); + const linesAudio = m3u8Audio.split("\n"); + expect(linesAudio[8]).toEqual("#EXT-X-CUE-OUT:DURATION=3"); + expect(linesAudio[12]).toEqual("#EXT-X-CUE-OUT:DURATION=15"); + expect(linesAudio[linesAudio.length - 2]).toEqual("#EXT-X-ENDLIST"); + done(); + }); + }); - it("handles post-roll ads", done => { - const mockVod = new HLSSpliceVod('http://mock.com/mock.m3u8'); - mockVod.load(mockMasterManifest, mockMediaManifest) - .then(() => { - return mockVod.insertAdAt(-1, 'http://mock.com/ad/mockad.m3u8', mockAdMasterManifest, mockAdMediaManifest); - }) - .then(() => { - const m3u8 = mockVod.getMediaManifest(4497000); - const lines = m3u8.split('\n'); - expect(lines[28]).toEqual("#EXT-X-CUE-OUT:DURATION=15"); - expect(lines[39]).toEqual("#EXT-X-CUE-IN"); - expect(lines[lines.length - 2]).toEqual("#EXT-X-ENDLIST"); - done(); - }) - }); - - it("handles two post-roll ads", done => { - const mockVod = new HLSSpliceVod('http://mock.com/mock.m3u8'); - mockVod.load(mockMasterManifest, mockMediaManifest) - .then(() => { - return mockVod.insertAdAt(-1, 'http://mock.com/ad/mockad.m3u8', mockAdMasterManifest, mockAdMediaManifest); - }) - .then(() => { - return mockVod.insertAdAt(-1, 'http://mock.com/ad/mockad.m3u8', mockAdMasterManifest, mockAdMediaManifest); - }) - .then(() => { - const m3u8 = mockVod.getMediaManifest(4497000); - const lines = m3u8.split('\n'); - expect(lines[28]).toEqual("#EXT-X-CUE-OUT:DURATION=15"); - expect(lines[41]).toEqual("#EXT-X-CUE-IN"); - expect(lines[52]).toEqual("#EXT-X-CUE-IN"); - expect(lines[lines.length - 2]).toEqual("#EXT-X-ENDLIST"); - done(); - }) - }); - - it("handles one pre-roll and one post-roll", done => { - const mockVod = new HLSSpliceVod('http://mock.com/mock.m3u8'); - mockVod.load(mockMasterManifest, mockMediaManifest) - .then(() => { - return mockVod.insertAdAt(0, 'http://mock.com/ad/mockad.m3u8', mockAdMasterManifest, mockAdMediaManifest); - }) - .then(() => { - return mockVod.insertAdAt(-1, 'http://mock.com/ad/mockad.m3u8', mockAdMasterManifest, mockAdMediaManifest); - }) - .then(() => { - const m3u8 = mockVod.getMediaManifest(4497000); - const lines = m3u8.split('\n'); - expect(lines[lines.length - 3]).toEqual("#EXT-X-CUE-IN"); - expect(lines[lines.length - 2]).toEqual("#EXT-X-ENDLIST"); - done(); - }); + it("handles two ads in a row merged into one break", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8", { merge: true }); + mockVod + .load(mockMasterManifest, mockMediaManifest, mockAudioManifest) + .then(() => { + return mockVod.insertAdAt( + 0, + "http://mock.com/ad/mockad.m3u8", + mockAdMasterManifest, + mockAdMediaManifest, + mockAdAudioManifest + ); + }) + .then(() => { + // This one will go first + return mockVod.insertAdAt( + 0, + "http://mock.com/ad/mockad.m3u8", + mockAdMasterManifest3, + mockAdMediaManifest3, + mockAdAudioManifest3 + ); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + const m3u8Audio = mockVod.getAudioManifest("stereo", "en"); + const lines = m3u8.split("\n"); + expect(lines[8]).toEqual("#EXT-X-CUE-OUT:DURATION=18"); + expect(lines[lines.length - 2]).toEqual("#EXT-X-ENDLIST"); + const linesAudio = m3u8Audio.split("\n"); + expect(linesAudio[8]).toEqual("#EXT-X-CUE-OUT:DURATION=18"); + expect(linesAudio[linesAudio.length - 2]).toEqual("#EXT-X-ENDLIST"); + done(); + }); }); - it("handles video bumper without any ads", done => { - const mockVod = new HLSSpliceVod('http://mock.com/mock.m3u8'); - mockVod.load(mockMasterManifest, mockMediaManifest) - .then(() => { - return mockVod.insertBumper('http://mock.com/ad/mockbumper.m3u8', mockBumperMasterManifest, mockBumperMediaManifest); - }) - .then(() => { - const m3u8 = mockVod.getMediaManifest(4497000); - const lines = m3u8.split('\n'); - expect(lines[8]).toEqual("http://mock.com/ad/ad1_0_av.ts"); - expect(lines[17]).toEqual("#EXT-X-DISCONTINUITY"); - expect(lines[18]).not.toEqual("#EXT-X-CUE-IN"); - done(); - }) - }); - - it("handles video bumper with one ad", done => { - const mockVod = new HLSSpliceVod('http://mock.com/mock.m3u8'); - mockVod.load(mockMasterManifest, mockMediaManifest) - .then(() => { - return mockVod.insertAdAt(0, 'http://mock.com/ad/mockad.m3u8', mockAdMasterManifest, mockAdMediaManifest); - }) - .then(() => { - return mockVod.insertBumper('http://mock.com/ad/mockbumper.m3u8', mockBumperMasterManifest, mockBumperMediaManifest); - }) - .then(() => { - const m3u8 = mockVod.getMediaManifest(4497000); - const lines = m3u8.split('\n'); - expect(lines[8]).toEqual("http://mock.com/ad/ad1_0_av.ts"); - expect(lines[17]).toEqual("#EXT-X-DISCONTINUITY"); - expect(lines[18]).not.toEqual("#EXT-X-CUE-IN"); - expect(lines[18]).toEqual("#EXT-X-CUE-OUT:DURATION=15"); - done(); - }) - }); - - it("handles video bumper and two ads in a row merged into one break", done => { - const mockVod = new HLSSpliceVod('http://mock.com/mock.m3u8', { merge: true }); - mockVod.load(mockMasterManifest, mockMediaManifest) - .then(() => { - return mockVod.insertAdAt(0, 'http://mock.com/ad/mockad.m3u8', mockAdMasterManifest, mockAdMediaManifest); - }) - .then(() => { - // This one will go first - return mockVod.insertAdAt(0, 'http://mock.com/ad/mockad.m3u8', mockAdMasterManifest3, mockAdMediaManifest3); - }) - .then(() => { - return mockVod.insertBumper('http://mock.com/ad/mockbumper.m3u8', mockBumperMasterManifest, mockBumperMediaManifest); - }) - .then(() => { - const m3u8 = mockVod.getMediaManifest(4497000); - const lines = m3u8.split('\n'); - expect(lines[10+8]).toEqual("#EXT-X-CUE-OUT:DURATION=18"); - expect(lines[lines.length - 2]).toEqual("#EXT-X-ENDLIST"); - done(); - }); + it("handles two ads that should not be merged into one break", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8", { merge: true }); + mockVod + .load(mockMasterManifest, mockMediaManifest, mockAudioManifest) + .then(() => { + return mockVod.insertAdAt( + 9, + "http://mock.com/ad/mockad.m3u8", + mockAdMasterManifest, + mockAdMediaManifest, + mockAdAudioManifest + ); + }) + .then(() => { + // This one will go first + return mockVod.insertAdAt( + 0, + "http://mock.com/ad/mockad.m3u8", + mockAdMasterManifest3, + mockAdMediaManifest3, + mockAdAudioManifest3 + ); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + let lines = m3u8.split("\n"); + expect(lines[8]).toEqual("#EXT-X-CUE-OUT:DURATION=3"); + expect(lines[16]).toEqual("#EXT-X-CUE-OUT:DURATION=15"); + expect(lines[lines.length - 2]).toEqual("#EXT-X-ENDLIST"); + const m3u8Audio = mockVod.getAudioManifest("mono", "en"); + lines = m3u8Audio.split("\n"); + expect(lines[8]).toEqual("#EXT-X-CUE-OUT:DURATION=3"); + expect(lines[16]).toEqual("#EXT-X-CUE-OUT:DURATION=15"); + expect(lines[lines.length - 2]).toEqual("#EXT-X-ENDLIST"); + done(); + }); }); - it("handles target duration for video bumper and two ads in a row merged into one break", done => { - const mockVod = new HLSSpliceVod('http://mock.com/mock.m3u8', { merge: true }); - mockVod.load(mockMasterManifest1b, mockMediaManifest1b) - .then(() => { - return mockVod.insertAdAt(0, 'http://mock.com/ad/mockad.m3u8', mockAdMasterManifest, mockAdMediaManifest); - }) - .then(() => { - return mockVod.insertAdAt(0, 'http://mock.com/ad/mockad.m3u8', mockAdMasterManifest3, mockAdMediaManifest3); - }) - .then(() => { - // This one will go first - return mockVod.insertAdAt(0, 'http://mock.com/ad/mockad.m3u8', mockAdMasterManifest4, mockAdMediaManifest4); - }) - .then(() => { - return mockVod.insertBumper('http://mock.com/ad/mockbumper.m3u8', mockBumperMasterManifest, mockBumperMediaManifest); - }) - .then(() => { - const m3u8 = mockVod.getMediaManifest(4497000); - const lines = m3u8.split('\n'); - expect(lines[1]).toEqual("#EXT-X-TARGETDURATION:5"); - expect(lines[10+8]).toEqual("#EXT-X-CUE-OUT:DURATION=23"); - expect(lines[lines.length - 2]).toEqual("#EXT-X-ENDLIST"); - done(); - }); + it("handles post-roll ads", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8"); + mockVod + .load(mockMasterManifest, mockMediaManifest, mockAudioManifest) + .then(() => { + return mockVod.insertAdAt( + -1, + "http://mock.com/ad/mockad.m3u8", + mockAdMasterManifest, + mockAdMediaManifest, + mockAdAudioManifest + ); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + let lines = m3u8.split("\n"); + expect(lines[28]).toEqual("#EXT-X-CUE-OUT:DURATION=15"); + expect(lines[39]).toEqual("#EXT-X-CUE-IN"); + expect(lines[lines.length - 2]).toEqual("#EXT-X-ENDLIST"); + const m3u8Audio = mockVod.getAudioManifest("mono", "en"); + lines = m3u8Audio.split("\n"); + expect(lines[28]).toEqual("#EXT-X-CUE-OUT:DURATION=15"); + expect(lines[39]).toEqual("#EXT-X-CUE-IN"); + expect(lines[lines.length - 2]).toEqual("#EXT-X-ENDLIST"); + done(); + }); }); - it("handles target duration with video bumper and no ads", done => { - const mockVod = new HLSSpliceVod('http://mock.com/mock.m3u8', { merge: true }); - mockVod.load(mockMasterManifest1b, mockMediaManifest1b) - .then(() => { - return mockVod.insertBumper('http://mock.com/ad/mockbumper.m3u8', mockAdMasterManifest4, mockAdMediaManifest4); - }) - .then(() => { - const m3u8 = mockVod.getMediaManifest(4497000); - const lines = m3u8.split('\n'); - expect(lines[1]).toEqual("#EXT-X-TARGETDURATION:5"); - done(); - }); + it("handles two post-roll ads", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8"); + mockVod + .load(mockMasterManifest, mockMediaManifest, mockAudioManifest) + .then(() => { + return mockVod.insertAdAt( + -1, + "http://mock.com/ad/mockad.m3u8", + mockAdMasterManifest, + mockAdMediaManifest, + mockAdAudioManifest + ); + }) + .then(() => { + return mockVod.insertAdAt( + -1, + "http://mock.com/ad/mockad.m3u8", + mockAdMasterManifest, + mockAdMediaManifest, + mockAdAudioManifest + ); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + let lines = m3u8.split("\n"); + expect(lines[28]).toEqual("#EXT-X-CUE-OUT:DURATION=15"); + expect(lines[41]).toEqual("#EXT-X-CUE-IN"); + expect(lines[52]).toEqual("#EXT-X-CUE-IN"); + expect(lines[lines.length - 2]).toEqual("#EXT-X-ENDLIST"); + const m3u8Audio = mockVod.getAudioManifest("mono", "en"); + lines = m3u8Audio.split("\n"); + expect(lines[28]).toEqual("#EXT-X-CUE-OUT:DURATION=15"); + expect(lines[41]).toEqual("#EXT-X-CUE-IN"); + expect(lines[52]).toEqual("#EXT-X-CUE-IN"); + expect(lines[lines.length - 2]).toEqual("#EXT-X-ENDLIST"); + done(); + }); }); - it("ensures that video bumper is always first", done => { - const mockVod = new HLSSpliceVod('http://mock.com/mock.m3u8'); - mockVod.load(mockMasterManifest, mockMediaManifest) - .then(() => { - return mockVod.insertBumper('http://mock.com/ad/mockbumper.m3u8', mockBumperMasterManifest, mockBumperMediaManifest); - }) - .then(() => { - return mockVod.insertAdAt(0, 'http://mock.com/ad/mockad.m3u8', mockAdMasterManifest, mockAdMediaManifest); - }) - .then(() => { - const m3u8 = mockVod.getMediaManifest(4497000); - const lines = m3u8.split('\n'); - expect(lines[8]).toEqual("http://mock.com/ad/ad1_0_av.ts"); - expect(lines[17]).toEqual("#EXT-X-DISCONTINUITY"); - expect(lines[18]).not.toEqual("#EXT-X-CUE-IN"); - expect(lines[18]).toEqual("#EXT-X-CUE-OUT:DURATION=15"); - done(); - }) + it("handles one pre-roll and one post-roll", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8"); + mockVod + .load(mockMasterManifest, mockMediaManifest, mockAudioManifest) + .then(() => { + return mockVod.insertAdAt( + 0, + "http://mock.com/ad/mockad.m3u8", + mockAdMasterManifest, + mockAdMediaManifest, + mockAdAudioManifest + ); + }) + .then(() => { + return mockVod.insertAdAt( + -1, + "http://mock.com/ad/mockad.m3u8", + mockAdMasterManifest, + mockAdMediaManifest, + mockAdAudioManifest + ); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + let lines = m3u8.split("\n"); + expect(lines[lines.length - 3]).toEqual("#EXT-X-CUE-IN"); + expect(lines[lines.length - 2]).toEqual("#EXT-X-ENDLIST"); + const m3u8Audio = mockVod.getAudioManifest("mono", "en"); + lines = m3u8Audio.split("\n"); + expect(lines[lines.length - 3]).toEqual("#EXT-X-CUE-IN"); + expect(lines[lines.length - 2]).toEqual("#EXT-X-ENDLIST"); + done(); + }); }); - it("can insert interstitial with an assetlist", done => { - const mockVod = new HLSSpliceVod('http://mock.com/mock.m3u8'); - mockVod.load(mockMasterManifest, mockMediaManifest) - .then(() => { - return mockVod.insertInterstitialAt(16000, "001", "http://mock.com/assetlist", true); - }) - .then(() => { - const m3u8 = mockVod.getMediaManifest(4497000); - const lines = m3u8.split('\n'); - expect(lines[12]).toEqual('#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:16.001Z",X-ASSET-LIST="http://mock.com/assetlist"'); - done(); - }); + it("handles video bumper without any ads", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8"); + mockVod + .load(mockMasterManifest, mockMediaManifest, mockAudioManifest) + .then(() => { + return mockVod.insertBumper( + "http://mock.com/ad/mockbumper.m3u8", + mockBumperMasterManifest, + mockBumperMediaManifest, + mockBumperAudioManifest + ); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + const m3u8Audio = mockVod.getAudioManifest("mono", "en"); + let lines = m3u8.split("\n"); + expect(lines[8]).toEqual("http://mock.com/ad/ad1_0_av.ts"); + expect(lines[17]).toEqual("#EXT-X-DISCONTINUITY"); + expect(lines[18]).not.toEqual("#EXT-X-CUE-IN"); + lines = m3u8Audio.split("\n"); + expect(lines[8]).toEqual("http://mock.com/ad/ad1_men_a.ts"); + expect(lines[17]).toEqual("#EXT-X-DISCONTINUITY"); + expect(lines[18]).not.toEqual("#EXT-X-CUE-IN"); + done(); + }); }); - it("can insert interstitial with an relative assetlist URL", done => { - const mockVod = new HLSSpliceVod('http://mock.com/mock.m3u8'); - mockVod.load(mockMasterManifest, mockMediaManifest) - .then(() => { - return mockVod.insertInterstitialAt(16000, "001", "/assetlist/sdfsdfjlsdfsdf", true); - }) - .then(() => { - const m3u8 = mockVod.getMediaManifest(4497000); - const lines = m3u8.split('\n'); - expect(lines[12]).toEqual('#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:16.001Z",X-ASSET-LIST="/assetlist/sdfsdfjlsdfsdf"'); - done(); - }); + it("handles video bumper with one ad", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8"); + mockVod + .load(mockMasterManifest, mockMediaManifest, mockAudioManifest) + .then(() => { + return mockVod.insertAdAt( + 0, + "http://mock.com/ad/mockad.m3u8", + mockAdMasterManifest, + mockAdMediaManifest, + mockAdAudioManifest + ); + }) + .then(() => { + return mockVod.insertBumper( + "http://mock.com/ad/mockbumper.m3u8", + mockBumperMasterManifest, + mockBumperMediaManifest, + mockBumperAudioManifest + ); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + let lines = m3u8.split("\n"); + expect(lines[8]).toEqual("http://mock.com/ad/ad1_0_av.ts"); + expect(lines[17]).toEqual("#EXT-X-DISCONTINUITY"); + expect(lines[18]).not.toEqual("#EXT-X-CUE-IN"); + expect(lines[18]).toEqual("#EXT-X-CUE-OUT:DURATION=15"); + const m3u8Audio = mockVod.getAudioManifest("mono", "en"); + lines = m3u8Audio.split("\n"); + expect(lines[8]).toEqual("http://mock.com/ad/ad1_men_a.ts"); + expect(lines[17]).toEqual("#EXT-X-DISCONTINUITY"); + expect(lines[18]).not.toEqual("#EXT-X-CUE-IN"); + expect(lines[18]).toEqual("#EXT-X-CUE-OUT:DURATION=15"); + done(); + }); }); - it("can insert interstitial with an asset uri", done => { - const mockVod = new HLSSpliceVod('http://mock.com/mock.m3u8'); - mockVod.load(mockMasterManifest, mockMediaManifest) - .then(() => { - return mockVod.insertInterstitialAt(18000, "001", "http://mock.com/asseturi", false); - }) - .then(() => { - const m3u8 = mockVod.getMediaManifest(4497000); - const lines = m3u8.split('\n'); - expect(lines[12]).toEqual('#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",X-ASSET-URI="http://mock.com/asseturi"'); - done(); - }); + it("handles video bumper and two ads in a row merged into one break", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8", { merge: true }); + mockVod + .load(mockMasterManifest, mockMediaManifest, mockAudioManifest) + .then(() => { + return mockVod.insertAdAt( + 0, + "http://mock.com/ad/mockad.m3u8", + mockAdMasterManifest, + mockAdMediaManifest, + mockAdAudioManifest + ); + }) + .then(() => { + // This one will go first + return mockVod.insertAdAt( + 0, + "http://mock.com/ad/mockad.m3u8", + mockAdMasterManifest3, + mockAdMediaManifest3, + mockAdAudioManifest3 + ); + }) + .then(() => { + return mockVod.insertBumper( + "http://mock.com/ad/mockbumper.m3u8", + mockBumperMasterManifest, + mockBumperMediaManifest, + mockBumperAudioManifest + ); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + let lines = m3u8.split("\n"); + expect(lines[10 + 8]).toEqual("#EXT-X-CUE-OUT:DURATION=18"); + expect(lines[lines.length - 2]).toEqual("#EXT-X-ENDLIST"); + const m3u8Audio = mockVod.getAudioManifest("stereo", "sv"); + lines = m3u8Audio.split("\n"); + expect(lines[10 + 8]).toEqual("#EXT-X-CUE-OUT:DURATION=18"); + expect(lines[lines.length - 2]).toEqual("#EXT-X-ENDLIST"); + done(); + }); }); - it("can insert interstitial with an asset uri and a resume offset", done => { - const mockVod = new HLSSpliceVod('http://mock.com/mock.m3u8'); - mockVod.load(mockMasterManifest, mockMediaManifest) - .then(() => { - return mockVod.insertInterstitialAt(18000, "001", "http://mock.com/asseturi", false, { - resumeOffset: 10500, + it("handles target duration for video bumper and two ads in a row merged into one break", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8", { merge: true }); + mockVod + .load(mockMasterManifest1b, mockMediaManifest1b, mockAudioManifest1b) + .then(() => { + return mockVod.insertAdAt( + 0, + "http://mock.com/ad/mockad.m3u8", + mockAdMasterManifest, + mockAdMediaManifest, + mockAdAudioManifest + ); + }) + .then(() => { + return mockVod.insertAdAt( + 0, + "http://mock.com/ad/mockad.m3u8", + mockAdMasterManifest3, + mockAdMediaManifest3, + mockAdAudioManifest3 + ); + }) + .then(() => { + // This one will go first + return mockVod.insertAdAt( + 0, + "http://mock.com/ad/mockad.m3u8", + mockAdMasterManifest4, + mockAdMediaManifest4, + mockAdAudioManifest4 + ); + }) + .then(() => { + return mockVod.insertBumper( + "http://mock.com/ad/mockbumper.m3u8", + mockBumperMasterManifest, + mockBumperMediaManifest, + mockBumperAudioManifest + ); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + let lines = m3u8.split("\n"); + expect(lines[1]).toEqual("#EXT-X-TARGETDURATION:8"); + expect(lines[10 + 8]).toEqual("#EXT-X-CUE-OUT:DURATION=23"); + expect(lines[lines.length - 2]).toEqual("#EXT-X-ENDLIST"); + const m3u8Audio = mockVod.getAudioManifest("mono", "en"); + lines = m3u8Audio.split("\n"); + expect(lines[1]).toEqual("#EXT-X-TARGETDURATION:8"); + expect(lines[10 + 8]).toEqual("#EXT-X-CUE-OUT:DURATION=23"); + expect(lines[lines.length - 2]).toEqual("#EXT-X-ENDLIST"); + done(); }); - }) - .then(() => { - const m3u8 = mockVod.getMediaManifest(4497000); - const lines = m3u8.split('\n'); - expect(lines[12]).toEqual('#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",X-ASSET-URI="http://mock.com/asseturi",X-RESUME-OFFSET=10.5'); - done(); - }); }); - it("can insert interstitial with an asset uri and a resume offset that is 0", done => { - const mockVod = new HLSSpliceVod('http://mock.com/mock.m3u8'); - mockVod.load(mockMasterManifest, mockMediaManifest) - .then(() => { - return mockVod.insertInterstitialAt(18000, "001", "http://mock.com/asseturi", false, { - resumeOffset: 0, + it("handles target duration with video bumper and no ads", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8", { merge: true }); + mockVod + .load(mockMasterManifest1b, mockMediaManifest1b, mockAudioManifest1b) + .then(() => { + return mockVod.insertBumper( + "http://mock.com/ad/mockbumper.m3u8", + mockAdMasterManifest4, + mockAdMediaManifest4, + mockAdAudioManifest4 + ); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + let lines = m3u8.split("\n"); + expect(lines[1]).toEqual("#EXT-X-TARGETDURATION:8"); + const m3u8Audio = mockVod.getAudioManifest("mono", "en"); + lines = m3u8Audio.split("\n"); + expect(lines[1]).toEqual("#EXT-X-TARGETDURATION:8"); + done(); }); - }) - .then(() => { - const m3u8 = mockVod.getMediaManifest(4497000); - const lines = m3u8.split('\n'); - expect(lines[12]).toEqual('#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",X-ASSET-URI="http://mock.com/asseturi",X-RESUME-OFFSET=0'); - done(); - }); }); - it("can insert interstitial with an asset uri and a playout limit", done => { - const mockVod = new HLSSpliceVod('http://mock.com/mock.m3u8'); - mockVod.load(mockMasterManifest, mockMediaManifest) - .then(() => { - return mockVod.insertInterstitialAt(18000, "001", "http://mock.com/asseturi", false, { - playoutLimit: 12500, + it("ensures that video bumper is always first", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8"); + mockVod + .load(mockMasterManifest, mockMediaManifest, mockAudioManifest) + .then(() => { + return mockVod.insertBumper( + "http://mock.com/ad_bumper/mockbumper.m3u8", + mockBumperMasterManifest, + mockBumperMediaManifest, + mockBumperAudioManifest + ); + }) + .then(() => { + return mockVod.insertAdAt( + 0, + "http://mock.com/ad/mockad.m3u8", + mockAdMasterManifest, + mockAdMediaManifest, + mockAdAudioManifest + ); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + let lines = m3u8.split("\n"); + expect(lines[8]).toEqual("http://mock.com/ad_bumper/ad1_0_av.ts"); + expect(lines[17]).toEqual("#EXT-X-DISCONTINUITY"); + expect(lines[18]).not.toEqual("#EXT-X-CUE-IN"); + expect(lines[18]).toEqual("#EXT-X-CUE-OUT:DURATION=15"); + const m3u8Audio = mockVod.getAudioManifest("mono", "en"); + lines = m3u8Audio.split("\n"); + expect(lines[8]).toEqual("http://mock.com/ad_bumper/ad1_men_a.ts"); + expect(lines[17]).toEqual("#EXT-X-DISCONTINUITY"); + expect(lines[18]).not.toEqual("#EXT-X-CUE-IN"); + expect(lines[18]).toEqual("#EXT-X-CUE-OUT:DURATION=15"); + + done(); }); - }) - .then(() => { - const m3u8 = mockVod.getMediaManifest(4497000); - const lines = m3u8.split('\n'); - expect(lines[12]).toEqual('#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",X-ASSET-URI="http://mock.com/asseturi",X-PLAYOUT-LIMIT=12.5'); - done(); - }); }); - it("can insert interstitial with an asset uri and a snap IN", done => { - const mockVod = new HLSSpliceVod('http://mock.com/mock.m3u8'); - mockVod.load(mockMasterManifest, mockMediaManifest) - .then(() => { - return mockVod.insertInterstitialAt(18000, "001", "http://mock.com/asseturi", false, { - snap: "IN", + it("can insert interstitial with an assetlist", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8"); + mockVod + .load(mockMasterManifest, mockMediaManifest, mockAudioManifest) + .then(() => { + return mockVod.insertInterstitialAt(16000, "001", "http://mock.com/assetlist", true); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + let lines = m3u8.split("\n"); + expect(lines[12]).toEqual( + '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:16.001Z",X-ASSET-LIST="http://mock.com/assetlist"' + ); + const m3u8Audio = mockVod.getAudioManifest("stereo", "sv"); + lines = m3u8Audio.split("\n"); + expect(lines[12]).toEqual( + '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:16.001Z",X-ASSET-LIST="http://mock.com/assetlist"' + ); + done(); }); - }) - .then(() => { - const m3u8 = mockVod.getMediaManifest(4497000); - const lines = m3u8.split('\n'); - expect(lines[12]).toEqual('#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",X-ASSET-URI="http://mock.com/asseturi",X-SNAP="IN"'); - done(); - }); }); - it("can insert interstitial with an asset uri and a snap OUT", done => { - const mockVod = new HLSSpliceVod('http://mock.com/mock.m3u8'); - mockVod.load(mockMasterManifest, mockMediaManifest) - .then(() => { - return mockVod.insertInterstitialAt(18000, "001", "http://mock.com/asseturi", false, { - snap: "OUT", + it("can insert interstitial with an relative assetlist URL", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8"); + mockVod + .load(mockMasterManifest, mockMediaManifest, mockAudioManifest) + .then(() => { + return mockVod.insertInterstitialAt(16000, "001", "/assetlist/sdfsdfjlsdfsdf", true); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + let lines = m3u8.split("\n"); + expect(lines[12]).toEqual( + '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:16.001Z",X-ASSET-LIST="/assetlist/sdfsdfjlsdfsdf"' + ); + const m3u8Audio = mockVod.getAudioManifest("stereo", "sv"); + lines = m3u8Audio.split("\n"); + expect(lines[12]).toEqual( + '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:16.001Z",X-ASSET-LIST="/assetlist/sdfsdfjlsdfsdf"' + ); + done(); }); - }) - .then(() => { - const m3u8 = mockVod.getMediaManifest(4497000); - const lines = m3u8.split('\n'); - expect(lines[12]).toEqual('#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",X-ASSET-URI="http://mock.com/asseturi",X-SNAP="OUT"'); - done(); - }); }); - it("can insert interstitial with an assetlist uri and a planned duration", done => { - const mockVod = new HLSSpliceVod('http://mock.com/mock.m3u8'); - mockVod.load(mockMasterManifest, mockMediaManifest) - .then(() => { - return mockVod.insertInterstitialAt(18000, "001", "http://mock.com/asseturi", true, { - plannedDuration: 30000, + it("can insert interstitial with an asset uri", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8"); + mockVod + .load(mockMasterManifest, mockMediaManifest, mockAudioManifest) + .then(() => { + return mockVod.insertInterstitialAt(18000, "001", "http://mock.com/asseturi", false); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + let lines = m3u8.split("\n"); + expect(lines[12]).toEqual( + '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",X-ASSET-URI="http://mock.com/asseturi"' + ); + const m3u8Audio = mockVod.getAudioManifest("stereo", "sv"); + lines = m3u8Audio.split("\n"); + expect(lines[12]).toEqual( + '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",X-ASSET-URI="http://mock.com/asseturi"' + ); + done(); + }); + }); + + it("can insert interstitial with an asset uri and a resume offset", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8"); + mockVod + .load(mockMasterManifest, mockMediaManifest, mockAudioManifest) + .then(() => { + return mockVod.insertInterstitialAt(18000, "001", "http://mock.com/asseturi", false, { + resumeOffset: 10500, + }); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + let lines = m3u8.split("\n"); + expect(lines[12]).toEqual( + '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",X-ASSET-URI="http://mock.com/asseturi",X-RESUME-OFFSET="10.5"' + ); + const m3u8Audio = mockVod.getAudioManifest("stereo", "sv"); + lines = m3u8Audio.split("\n"); + expect(lines[12]).toEqual( + '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",X-ASSET-URI="http://mock.com/asseturi",X-RESUME-OFFSET="10.5"' + ); + done(); + }); + }); + + it("can insert interstitial with an asset uri and a resume offset that is 0", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8"); + mockVod + .load(mockMasterManifest, mockMediaManifest, mockAudioManifest) + .then(() => { + return mockVod.insertInterstitialAt(18000, "001", "http://mock.com/asseturi", false, { + resumeOffset: 0, + }); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + let lines = m3u8.split("\n"); + expect(lines[12]).toEqual( + '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",X-ASSET-URI="http://mock.com/asseturi",X-RESUME-OFFSET="0"' + ); + const m3u8Audio = mockVod.getAudioManifest("stereo", "sv"); + lines = m3u8Audio.split("\n"); + expect(lines[12]).toEqual( + '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",X-ASSET-URI="http://mock.com/asseturi",X-RESUME-OFFSET="0"' + ); + done(); + }); + }); + + it("can insert interstitial with an asset uri and a playout limit", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8"); + mockVod + .load(mockMasterManifest, mockMediaManifest, mockAudioManifest) + .then(() => { + return mockVod.insertInterstitialAt(18000, "001", "http://mock.com/asseturi", false, { + playoutLimit: 12500, + }); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + let lines = m3u8.split("\n"); + expect(lines[12]).toEqual( + '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",X-ASSET-URI="http://mock.com/asseturi",X-PLAYOUT-LIMIT="12.5"' + ); + const m3u8Audio = mockVod.getAudioManifest("stereo", "sv"); + lines = m3u8Audio.split("\n"); + expect(lines[12]).toEqual( + '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",X-ASSET-URI="http://mock.com/asseturi",X-PLAYOUT-LIMIT="12.5"' + ); + done(); + }); + }); + + it("can insert interstitial with an asset uri and a snap IN", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8"); + mockVod + .load(mockMasterManifest, mockMediaManifest, mockAudioManifest) + .then(() => { + return mockVod.insertInterstitialAt(18000, "001", "http://mock.com/asseturi", false, { + snap: "IN", + }); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + let lines = m3u8.split("\n"); + expect(lines[12]).toEqual( + '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",X-ASSET-URI="http://mock.com/asseturi",X-SNAP="IN"' + ); + const m3u8Audio = mockVod.getAudioManifest("stereo", "sv"); + lines = m3u8Audio.split("\n"); + expect(lines[12]).toEqual( + '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",X-ASSET-URI="http://mock.com/asseturi",X-SNAP="IN"' + ); + done(); + }); + }); + + it("can insert interstitial with an asset uri and a snap OUT", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8"); + mockVod + .load(mockMasterManifest, mockMediaManifest, mockAudioManifest) + .then(() => { + return mockVod.insertInterstitialAt(18000, "001", "http://mock.com/asseturi", false, { + snap: "OUT", + }); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + let lines = m3u8.split("\n"); + expect(lines[12]).toEqual( + '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",X-ASSET-URI="http://mock.com/asseturi",X-SNAP="OUT"' + ); + const m3u8Audio = mockVod.getAudioManifest("stereo", "sv"); + lines = m3u8Audio.split("\n"); + expect(lines[12]).toEqual( + '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",X-ASSET-URI="http://mock.com/asseturi",X-SNAP="OUT"' + ); + done(); + }); + }); + + it("can insert interstitial with an assetlist uri and a planned duration", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8"); + mockVod + .load(mockMasterManifest, mockMediaManifest, mockAudioManifest) + .then(() => { + return mockVod.insertInterstitialAt(18000, "001", "http://mock.com/asseturi", true, { + plannedDuration: 30000, + }); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + let lines = m3u8.split("\n"); + expect(lines[12]).toEqual( + '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",DURATION="30",X-ASSET-LIST="http://mock.com/asseturi"' + ); + const m3u8Audio = mockVod.getAudioManifest("stereo", "sv"); + lines = m3u8Audio.split("\n"); + expect(lines[12]).toEqual( + '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",DURATION="30",X-ASSET-LIST="http://mock.com/asseturi"' + ); + done(); + }); + }); +}); + +describe("HLSSpliceVod with CMAF and Demuxed Audio Tracks,", () => { + let mockMasterManifest; + let mockMediaManifest; + let mockAudioManifest; + let mockAdMasterManifest; + let mockAdMediaManifest; + let mockAdAudioManifest; + let mockAdMasterManifest2; + let mockAdMediaManifest2; + let mockAdAudioManifest2; + const _log = (s) => console.log(JSON.stringify(s, null, 2)); + beforeEach(() => { + // MOCK VOD #8 TEST + mockCmafMasterManifest = () => { + return fs.createReadStream("testvectors/demux_n_cmaf/hls1/master.m3u8"); + }; + mockCmafMediaManifest = (bw) => { + const bwmap = { + 4497000: "0", + 2497000: "1", + }; + return fs.createReadStream(`testvectors/demux_n_cmaf/hls1/index_${bwmap[bw]}_v.m3u8`); + }; + mockCmafAudioManifest = (g, l) => { + return fs.createReadStream(`testvectors/demux_n_cmaf/hls1/index_${g}-${l}_a.m3u8`); + }; + // MOCK VOD #8 TEST + mockCmafAdMasterManifest = () => { + return fs.createReadStream("testvectors/demux_n_cmaf/ad1/master.m3u8"); + }; + mockCmafAdMediaManifest = (bw) => { + const bwmap = { + 4545000: "0", + 2525000: "1", + }; + return fs.createReadStream(`testvectors/demux_n_cmaf/ad1/index_${bwmap[bw]}_v.m3u8`); + }; + mockCmafAdAudioManifest = (g, l) => { + return fs.createReadStream(`testvectors/demux_n_cmaf/ad1/index_${g}-${l}_a.m3u8`); + }; + // MOCK VOD #9 TEST + mockCmafAdMasterManifest3 = () => { + return fs.createReadStream("testvectors/demux_n_cmaf/ad3/master.m3u8"); + }; + mockCmafAdMediaManifest3 = (bw) => { + const bwmap = { + 4545000: "0", + 2525000: "1", + }; + return fs.createReadStream(`testvectors/demux_n_cmaf/ad3/index_${bwmap[bw]}_v.m3u8`); + }; + mockCmafAdAudioManifest3 = (g, l) => { + return fs.createReadStream(`testvectors/demux_n_cmaf/ad3/index_${g}-${l}_a.m3u8`); + }; + }); + + it("contains a 15 second splice at 9 seconds from start", (done) => { + // TEZT + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8"); + mockVod + .load(mockCmafMasterManifest, mockCmafMediaManifest, mockCmafAudioManifest) + .then(() => { + return mockVod.insertAdAt( + 9000, + "http://mock.com/ad/mockad.m3u8", + mockCmafAdMasterManifest, + mockCmafAdMediaManifest, + mockCmafAdAudioManifest + ); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + const m3u8Audio = mockVod.getAudioManifest("stereo", "sv"); + const expectedChunkVideo = `#EXT-X-MAP:URI="test-video=2500000.m4s" +#EXTINF:3.0000, no desc +test-video=2500000-1.m4s +#EXTINF:3.0000, no desc +test-video=2500000-2.m4s +#EXTINF:3.0000, no desc +test-video=2500000-3.m4s +#EXT-X-MAP:URI="mock-ad-video=2525000.m4s" +#EXT-X-DISCONTINUITY +#EXT-X-CUE-OUT:DURATION=14.16 +#EXTINF:3.0000, no desc +http://mock.com/ad/mock-ad-video=2525000-1.m4s +#EXTINF:3.0000, no desc +http://mock.com/ad/mock-ad-video=2525000-2.m4s +#EXTINF:3.0000, no desc +http://mock.com/ad/mock-ad-video=2525000-3.m4s +#EXTINF:3.0000, no desc +http://mock.com/ad/mock-ad-video=2525000-4.m4s +#EXTINF:2.1600, no desc +http://mock.com/ad/mock-ad-video=2525000-5.m4s +#EXT-X-MAP:URI="http://mock.com/test-video=2500000.m4s" +#EXT-X-DISCONTINUITY +#EXT-X-CUE-IN +#EXTINF:3.0000, no desc +test-video=2500000-4.m4s`; + const expectedChunkAudio = `#EXT-X-MAP:URI="test-audio=256000.m4s" +#EXTINF:1.9200, no desc +test-audio=256000-1.m4s +#EXTINF:1.9200, no desc +test-audio=256000-2.m4s +#EXTINF:1.9200, no desc +test-audio=256000-3.m4s +#EXTINF:1.9200, no desc +test-audio=256000-4.m4s +#EXTINF:1.9200, no desc +test-audio=256000-5.m4s +#EXT-X-MAP:URI="mock-ad-audio=256000.m4s" +#EXT-X-DISCONTINUITY +#EXT-X-CUE-OUT:DURATION=14.16 +#EXTINF:1.9200, no desc +http://mock.com/ad/mock-ad-audio=256000-1.m4s +#EXTINF:1.9200, no desc +http://mock.com/ad/mock-ad-audio=256000-2.m4s +#EXTINF:1.9200, no desc +http://mock.com/ad/mock-ad-audio=256000-3.m4s +#EXTINF:1.9200, no desc +http://mock.com/ad/mock-ad-audio=256000-4.m4s +#EXTINF:1.9200, no desc +http://mock.com/ad/mock-ad-audio=256000-5.m4s +#EXTINF:1.9200, no desc +http://mock.com/ad/mock-ad-audio=256000-6.m4s +#EXTINF:1.9200, no desc +http://mock.com/ad/mock-ad-audio=256000-7.m4s +#EXTINF:1.4933, no desc +http://mock.com/ad/mock-ad-audio=256000-92.m4s +#EXT-X-MAP:URI="http://mock.com/test-audio=256000.m4s" +#EXT-X-DISCONTINUITY +#EXT-X-CUE-IN +#EXTINF:1.9200, no desc +test-audio=256000-6.m4s`; + expect(m3u8.includes(expectedChunkVideo)).toBe(true); + expect(m3u8Audio.includes(expectedChunkAudio)).toBe(true); + done(); + }); + }); + + it("handles two ads in a row merged into one break", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8", { merge: true }); + mockVod + .load(mockCmafMasterManifest, mockCmafMediaManifest, mockCmafAudioManifest) + .then(() => { + return mockVod.insertAdAt( + 0, + "http://mock.com/ad/mockad.m3u8", + mockCmafAdMasterManifest, + mockCmafAdMediaManifest, + mockCmafAdAudioManifest + ); + }) + .then(() => { + // This one will go first + return mockVod.insertAdAt( + 0, + "http://mock.com/ad/mockad.m3u8", + mockCmafAdMasterManifest3, + mockCmafAdMediaManifest3, + mockCmafAdAudioManifest3 + ); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + let lines = m3u8.split("\n"); + expect(lines[8]).toEqual(`#EXT-X-MAP:URI="mock-ad3-video=2525000.m4s"`); + expect(lines[9]).toEqual(`#EXT-X-DISCONTINUITY`); + expect(lines[10]).toEqual(`#EXT-X-CUE-OUT:DURATION=17.16`); + expect(lines[12]).toEqual(`http://mock.com/ad/mock-ad3-video=2525000-1.m4s`); + expect(lines[13]).toEqual(`#EXT-X-MAP:URI="http://mock.com/test-video=2500000.m4s"`); + expect(lines[14]).toEqual(`#EXT-X-DISCONTINUITY`); + expect(lines[16]).toEqual(`http://mock.com/ad/mock-ad-video=2525000-1.m4s`); + expect(lines[25]).toEqual(`#EXT-X-MAP:URI="http://mock.com/test-video=2500000.m4s"`); + expect(lines[26]).toEqual(`#EXT-X-DISCONTINUITY`); + expect(lines[27]).toEqual(`#EXT-X-CUE-IN`); + expect(lines[29]).toEqual(`test-video=2500000-1.m4s`); + expect(lines[lines.length - 2]).toEqual("#EXT-X-ENDLIST"); + const m3u8Audio = mockVod.getAudioManifest("stereo", "sv"); + const linesAudio = m3u8Audio.split("\n"); + expect(linesAudio[8]).toEqual(`#EXT-X-MAP:URI="mock-ad3-audio=256000.m4s"`); + expect(linesAudio[9]).toEqual(`#EXT-X-DISCONTINUITY`); + expect(linesAudio[10]).toEqual(`#EXT-X-CUE-OUT:DURATION=18.3466`); + expect(linesAudio[12]).toEqual(`http://mock.com/ad/mock-ad3-audio=256000-1.m4s`); + expect(linesAudio[15]).toEqual(`#EXT-X-MAP:URI="http://mock.com/test-audio=256000.m4s"`); + expect(linesAudio[16]).toEqual(`#EXT-X-DISCONTINUITY`); + expect(linesAudio[18]).toEqual(`http://mock.com/ad/mock-ad-audio=256000-1.m4s`); + expect(linesAudio[33]).toEqual(`#EXT-X-MAP:URI="http://mock.com/test-audio=256000.m4s"`); + expect(linesAudio[34]).toEqual(`#EXT-X-DISCONTINUITY`); + expect(linesAudio[35]).toEqual(`#EXT-X-CUE-IN`); + expect(linesAudio[37]).toEqual(`test-audio=256000-1.m4s`); + expect(linesAudio[linesAudio.length - 2]).toEqual("#EXT-X-ENDLIST"); + done(); + }); + }); + + it("handles two ads that should not be merged into one break", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8", { merge: true }); + mockVod + .load(mockCmafMasterManifest, mockCmafMediaManifest, mockCmafAudioManifest) + .then(() => { + return mockVod.insertAdAt( + 9000, + "http://mock.com/ad/mockad.m3u8", + mockCmafAdMasterManifest, + mockCmafAdMediaManifest, + mockCmafAdAudioManifest + ); + }) + .then(() => { + // This one will go first + return mockVod.insertAdAt( + 0, + "http://mock.com/ad/mockad.m3u8", + mockCmafAdMasterManifest3, + mockCmafAdMediaManifest3, + mockCmafAdAudioManifest3 + ); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + const lines = m3u8.split("\n"); + expect(lines[8]).toEqual(`#EXT-X-MAP:URI="mock-ad3-video=2525000.m4s"`); + expect(lines[9]).toEqual(`#EXT-X-DISCONTINUITY`); + expect(lines[10]).toEqual(`#EXT-X-CUE-OUT:DURATION=3`); + expect(lines[12]).toEqual(`http://mock.com/ad/mock-ad3-video=2525000-1.m4s`); + expect(lines[13]).toEqual(`#EXT-X-MAP:URI="http://mock.com/test-video=2500000.m4s"`); + expect(lines[15]).toEqual(`#EXT-X-CUE-IN`); + expect(lines[17]).toEqual(`test-video=2500000-1.m4s`); + expect(lines[22]).toEqual(`#EXT-X-MAP:URI="mock-ad-video=2525000.m4s"`); + expect(lines[24]).toEqual(`#EXT-X-CUE-OUT:DURATION=14.16`); + expect(lines[26]).toEqual(`http://mock.com/ad/mock-ad-video=2525000-1.m4s`); + expect(lines[35]).toEqual(`#EXT-X-MAP:URI="http://mock.com/test-video=2500000.m4s"`); + expect(lines[37]).toEqual(`#EXT-X-CUE-IN`); + expect(lines[39]).toEqual(`test-video=2500000-4.m4s`); + expect(lines[lines.length - 2]).toEqual("#EXT-X-ENDLIST"); + const m3u8Audio = mockVod.getAudioManifest("stereo", "sv"); + const linesAudio = m3u8Audio.split("\n"); + expect(linesAudio[8]).toEqual(`#EXT-X-MAP:URI="mock-ad3-audio=256000.m4s"`); + expect(linesAudio[10]).toEqual(`#EXT-X-CUE-OUT:DURATION=3.4133`); + expect(linesAudio[12]).toEqual(`http://mock.com/ad/mock-ad3-audio=256000-1.m4s`); + expect(linesAudio[15]).toEqual(`#EXT-X-MAP:URI="http://mock.com/test-audio=256000.m4s"`); + expect(linesAudio[17]).toEqual(`#EXT-X-CUE-IN`); + expect(linesAudio[19]).toEqual(`test-audio=256000-1.m4s`); + expect(linesAudio[28]).toEqual(`#EXT-X-MAP:URI="mock-ad-audio=256000.m4s"`); + expect(linesAudio[30]).toEqual(`#EXT-X-CUE-OUT:DURATION=14.9333`); + expect(linesAudio[32]).toEqual(`http://mock.com/ad/mock-ad-audio=256000-1.m4s`); + expect(linesAudio[47]).toEqual(`#EXT-X-MAP:URI="http://mock.com/test-audio=256000.m4s"`); + expect(linesAudio[49]).toEqual(`#EXT-X-CUE-IN`); + expect(linesAudio[51]).toEqual(`test-audio=256000-6.m4s`); + done(); + }); + }); + + it("handles one pre-roll and one post-roll", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8"); + mockVod + .load(mockCmafMasterManifest, mockCmafMediaManifest, mockCmafAudioManifest) + .then(() => { + return mockVod.insertAdAt( + 0, + "http://mock.com/ad/mockad.m3u8", + mockCmafAdMasterManifest, + mockCmafAdMediaManifest, + mockCmafAdAudioManifest + ); + }) + .then(() => { + return mockVod.insertAdAt( + -1, + "http://mock.com/ad/mockad.m3u8", + mockCmafAdMasterManifest, + mockCmafAdMediaManifest, + mockCmafAdAudioManifest + ); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + const lines = m3u8.split("\n"); + lines.map((l, i) => console.log(l, i)); + expect(lines[lines.length - 3]).toEqual("#EXT-X-CUE-IN"); + expect(lines[lines.length - 2]).toEqual("#EXT-X-ENDLIST"); + const m3u8Audio = mockVod.getAudioManifest("stereo", "sv"); + const linesAudio = m3u8Audio.split("\n"); + expect(linesAudio[linesAudio.length - 3]).toEqual("#EXT-X-CUE-IN"); + expect(linesAudio[linesAudio.length - 2]).toEqual("#EXT-X-ENDLIST"); + done(); }); - }) - .then(() => { - const m3u8 = mockVod.getMediaManifest(4497000); - const lines = m3u8.split('\n'); - expect(lines[12]).toEqual('#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",DURATION=30,X-ASSET-LIST="http://mock.com/asseturi"'); - done(); - }); }); }); diff --git a/spec/support/jasmine.json b/spec/support/jasmine.json index 370fc44..ba00985 100644 --- a/spec/support/jasmine.json +++ b/spec/support/jasmine.json @@ -1,11 +1,11 @@ -{ - "spec_dir": "spec", - "spec_files": [ - "**/*[sS]pec.js" - ], - "helpers": [ - "helpers/**/*.js" - ], - "stopSpecOnExpectationFailure": false, - "random": true -} +{ + "spec_dir": "spec", + "spec_files": [ + "**/*[sS]pec.js" + ], + "helpers": [ + "helpers/**/*.js" + ], + "stopSpecOnExpectationFailure": false, + "random": true +} diff --git a/testvectors/ad1/index_0_av.m3u8 b/testvectors/ad1/index_0_av.m3u8 index 92afad2..5e0ff83 100644 --- a/testvectors/ad1/index_0_av.m3u8 +++ b/testvectors/ad1/index_0_av.m3u8 @@ -1,17 +1,17 @@ -#EXTM3U -#EXT-X-TARGETDURATION:3 -#EXT-X-ALLOW-CACHE:YES -#EXT-X-PLAYLIST-TYPE:VOD -#EXT-X-VERSION:3 -#EXT-X-MEDIA-SEQUENCE:1 -#EXTINF:3.000, -ad1_0_av.ts -#EXTINF:3.000, -ad2_0_av.ts -#EXTINF:3.000, -ad3_0_av.ts -#EXTINF:3.000, -ad4_0_av.ts -#EXTINF:3.000, -ad5_0_av.ts -#EXT-X-ENDLIST +#EXTM3U +#EXT-X-TARGETDURATION:3 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:3.000, +ad1_0_av.ts +#EXTINF:3.000, +ad2_0_av.ts +#EXTINF:3.000, +ad3_0_av.ts +#EXTINF:3.000, +ad4_0_av.ts +#EXTINF:3.000, +ad5_0_av.ts +#EXT-X-ENDLIST diff --git a/testvectors/ad1/index_1_av.m3u8 b/testvectors/ad1/index_1_av.m3u8 index b20b1b5..e40904e 100644 --- a/testvectors/ad1/index_1_av.m3u8 +++ b/testvectors/ad1/index_1_av.m3u8 @@ -1,17 +1,17 @@ -#EXTM3U -#EXT-X-TARGETDURATION:3 -#EXT-X-ALLOW-CACHE:YES -#EXT-X-PLAYLIST-TYPE:VOD -#EXT-X-VERSION:3 -#EXT-X-MEDIA-SEQUENCE:1 -#EXTINF:3.000, -ad1_1_av.ts -#EXTINF:3.000, -ad2_1_av.ts -#EXTINF:3.000, -ad3_1_av.ts -#EXTINF:3.000, -ad4_1_av.ts -#EXTINF:3.000, -ad5_1_av.ts -#EXT-X-ENDLIST +#EXTM3U +#EXT-X-TARGETDURATION:3 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:3.000, +ad1_1_av.ts +#EXTINF:3.000, +ad2_1_av.ts +#EXTINF:3.000, +ad3_1_av.ts +#EXTINF:3.000, +ad4_1_av.ts +#EXTINF:3.000, +ad5_1_av.ts +#EXT-X-ENDLIST diff --git a/testvectors/ad1/master.m3u8 b/testvectors/ad1/master.m3u8 index 157ac3a..8bee973 100644 --- a/testvectors/ad1/master.m3u8 +++ b/testvectors/ad1/master.m3u8 @@ -1,5 +1,5 @@ -#EXTM3U -#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=4497000,RESOLUTION=1280x720,CODECS="avc1.77.30, mp4a.40.2",CLOSED-CAPTIONS=NONE -index_0_av.m3u8 -#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2497000,RESOLUTION=1024x576,CODECS="avc1.77.30, mp4a.40.2",CLOSED-CAPTIONS=NONE -index_1_av.m3u8 +#EXTM3U +#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=4497000,RESOLUTION=1280x720,CODECS="avc1.77.30, mp4a.40.2",CLOSED-CAPTIONS=NONE +index_0_av.m3u8 +#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2497000,RESOLUTION=1024x576,CODECS="avc1.77.30, mp4a.40.2",CLOSED-CAPTIONS=NONE +index_1_av.m3u8 diff --git a/testvectors/ad2/index_0_av.m3u8 b/testvectors/ad2/index_0_av.m3u8 index 92afad2..5e0ff83 100644 --- a/testvectors/ad2/index_0_av.m3u8 +++ b/testvectors/ad2/index_0_av.m3u8 @@ -1,17 +1,17 @@ -#EXTM3U -#EXT-X-TARGETDURATION:3 -#EXT-X-ALLOW-CACHE:YES -#EXT-X-PLAYLIST-TYPE:VOD -#EXT-X-VERSION:3 -#EXT-X-MEDIA-SEQUENCE:1 -#EXTINF:3.000, -ad1_0_av.ts -#EXTINF:3.000, -ad2_0_av.ts -#EXTINF:3.000, -ad3_0_av.ts -#EXTINF:3.000, -ad4_0_av.ts -#EXTINF:3.000, -ad5_0_av.ts -#EXT-X-ENDLIST +#EXTM3U +#EXT-X-TARGETDURATION:3 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:3.000, +ad1_0_av.ts +#EXTINF:3.000, +ad2_0_av.ts +#EXTINF:3.000, +ad3_0_av.ts +#EXTINF:3.000, +ad4_0_av.ts +#EXTINF:3.000, +ad5_0_av.ts +#EXT-X-ENDLIST diff --git a/testvectors/ad2/index_1_av.m3u8 b/testvectors/ad2/index_1_av.m3u8 index b20b1b5..e40904e 100644 --- a/testvectors/ad2/index_1_av.m3u8 +++ b/testvectors/ad2/index_1_av.m3u8 @@ -1,17 +1,17 @@ -#EXTM3U -#EXT-X-TARGETDURATION:3 -#EXT-X-ALLOW-CACHE:YES -#EXT-X-PLAYLIST-TYPE:VOD -#EXT-X-VERSION:3 -#EXT-X-MEDIA-SEQUENCE:1 -#EXTINF:3.000, -ad1_1_av.ts -#EXTINF:3.000, -ad2_1_av.ts -#EXTINF:3.000, -ad3_1_av.ts -#EXTINF:3.000, -ad4_1_av.ts -#EXTINF:3.000, -ad5_1_av.ts -#EXT-X-ENDLIST +#EXTM3U +#EXT-X-TARGETDURATION:3 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:3.000, +ad1_1_av.ts +#EXTINF:3.000, +ad2_1_av.ts +#EXTINF:3.000, +ad3_1_av.ts +#EXTINF:3.000, +ad4_1_av.ts +#EXTINF:3.000, +ad5_1_av.ts +#EXT-X-ENDLIST diff --git a/testvectors/ad2/master.m3u8 b/testvectors/ad2/master.m3u8 index 11a369b..e633ee8 100644 --- a/testvectors/ad2/master.m3u8 +++ b/testvectors/ad2/master.m3u8 @@ -1,5 +1,5 @@ -#EXTM3U -#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=4397000,RESOLUTION=1280x720,CODECS="avc1.77.30, mp4a.40.2",CLOSED-CAPTIONS=NONE -index_0_av.m3u8 -#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2597000,RESOLUTION=1024x576,CODECS="avc1.77.30, mp4a.40.2",CLOSED-CAPTIONS=NONE -index_1_av.m3u8 +#EXTM3U +#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=4397000,RESOLUTION=1280x720,CODECS="avc1.77.30, mp4a.40.2",CLOSED-CAPTIONS=NONE +index_0_av.m3u8 +#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2597000,RESOLUTION=1024x576,CODECS="avc1.77.30, mp4a.40.2",CLOSED-CAPTIONS=NONE +index_1_av.m3u8 diff --git a/testvectors/ad3/index_0_av.m3u8 b/testvectors/ad3/index_0_av.m3u8 index 84be49c..48d1179 100644 --- a/testvectors/ad3/index_0_av.m3u8 +++ b/testvectors/ad3/index_0_av.m3u8 @@ -1,9 +1,9 @@ -#EXTM3U -#EXT-X-TARGETDURATION:3 -#EXT-X-ALLOW-CACHE:YES -#EXT-X-PLAYLIST-TYPE:VOD -#EXT-X-VERSION:3 -#EXT-X-MEDIA-SEQUENCE:1 -#EXTINF:3.000, -ad1_0_av.ts -#EXT-X-ENDLIST +#EXTM3U +#EXT-X-TARGETDURATION:3 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:3.000, +ad1_0_av.ts +#EXT-X-ENDLIST diff --git a/testvectors/ad3/index_1_av.m3u8 b/testvectors/ad3/index_1_av.m3u8 index ba26cbf..4ea5076 100644 --- a/testvectors/ad3/index_1_av.m3u8 +++ b/testvectors/ad3/index_1_av.m3u8 @@ -1,9 +1,9 @@ -#EXTM3U -#EXT-X-TARGETDURATION:3 -#EXT-X-ALLOW-CACHE:YES -#EXT-X-PLAYLIST-TYPE:VOD -#EXT-X-VERSION:3 -#EXT-X-MEDIA-SEQUENCE:1 -#EXTINF:3.000, -ad1_1_av.ts -#EXT-X-ENDLIST +#EXTM3U +#EXT-X-TARGETDURATION:3 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:3.000, +ad1_1_av.ts +#EXT-X-ENDLIST diff --git a/testvectors/ad3/master.m3u8 b/testvectors/ad3/master.m3u8 index 11a369b..e633ee8 100644 --- a/testvectors/ad3/master.m3u8 +++ b/testvectors/ad3/master.m3u8 @@ -1,5 +1,5 @@ -#EXTM3U -#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=4397000,RESOLUTION=1280x720,CODECS="avc1.77.30, mp4a.40.2",CLOSED-CAPTIONS=NONE -index_0_av.m3u8 -#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2597000,RESOLUTION=1024x576,CODECS="avc1.77.30, mp4a.40.2",CLOSED-CAPTIONS=NONE -index_1_av.m3u8 +#EXTM3U +#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=4397000,RESOLUTION=1280x720,CODECS="avc1.77.30, mp4a.40.2",CLOSED-CAPTIONS=NONE +index_0_av.m3u8 +#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2597000,RESOLUTION=1024x576,CODECS="avc1.77.30, mp4a.40.2",CLOSED-CAPTIONS=NONE +index_1_av.m3u8 diff --git a/testvectors/ad4/index_0_av.m3u8 b/testvectors/ad4/index_0_av.m3u8 index 9eb5b5c..583976f 100644 --- a/testvectors/ad4/index_0_av.m3u8 +++ b/testvectors/ad4/index_0_av.m3u8 @@ -1,9 +1,9 @@ -#EXTM3U -#EXT-X-TARGETDURATION:5 -#EXT-X-ALLOW-CACHE:YES -#EXT-X-PLAYLIST-TYPE:VOD -#EXT-X-VERSION:3 -#EXT-X-MEDIA-SEQUENCE:1 -#EXTINF:5.000, -ad1_0_av.ts -#EXT-X-ENDLIST +#EXTM3U +#EXT-X-TARGETDURATION:5 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:5.000, +ad1_0_av.ts +#EXT-X-ENDLIST diff --git a/testvectors/ad4/index_1_av.m3u8 b/testvectors/ad4/index_1_av.m3u8 index b9ace14..844c4e5 100644 --- a/testvectors/ad4/index_1_av.m3u8 +++ b/testvectors/ad4/index_1_av.m3u8 @@ -1,9 +1,9 @@ -#EXTM3U -#EXT-X-TARGETDURATION:5 -#EXT-X-ALLOW-CACHE:YES -#EXT-X-PLAYLIST-TYPE:VOD -#EXT-X-VERSION:3 -#EXT-X-MEDIA-SEQUENCE:1 -#EXTINF:5.000, -ad1_1_av.ts -#EXT-X-ENDLIST +#EXTM3U +#EXT-X-TARGETDURATION:5 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:5.000, +ad1_1_av.ts +#EXT-X-ENDLIST diff --git a/testvectors/ad4/master.m3u8 b/testvectors/ad4/master.m3u8 index 11a369b..e633ee8 100644 --- a/testvectors/ad4/master.m3u8 +++ b/testvectors/ad4/master.m3u8 @@ -1,5 +1,5 @@ -#EXTM3U -#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=4397000,RESOLUTION=1280x720,CODECS="avc1.77.30, mp4a.40.2",CLOSED-CAPTIONS=NONE -index_0_av.m3u8 -#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2597000,RESOLUTION=1024x576,CODECS="avc1.77.30, mp4a.40.2",CLOSED-CAPTIONS=NONE -index_1_av.m3u8 +#EXTM3U +#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=4397000,RESOLUTION=1280x720,CODECS="avc1.77.30, mp4a.40.2",CLOSED-CAPTIONS=NONE +index_0_av.m3u8 +#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2597000,RESOLUTION=1024x576,CODECS="avc1.77.30, mp4a.40.2",CLOSED-CAPTIONS=NONE +index_1_av.m3u8 diff --git a/testvectors/demux/ad1/index_0_v.m3u8 b/testvectors/demux/ad1/index_0_v.m3u8 new file mode 100644 index 0000000..5e0ff83 --- /dev/null +++ b/testvectors/demux/ad1/index_0_v.m3u8 @@ -0,0 +1,17 @@ +#EXTM3U +#EXT-X-TARGETDURATION:3 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:3.000, +ad1_0_av.ts +#EXTINF:3.000, +ad2_0_av.ts +#EXTINF:3.000, +ad3_0_av.ts +#EXTINF:3.000, +ad4_0_av.ts +#EXTINF:3.000, +ad5_0_av.ts +#EXT-X-ENDLIST diff --git a/testvectors/demux/ad1/index_1_v.m3u8 b/testvectors/demux/ad1/index_1_v.m3u8 new file mode 100644 index 0000000..e40904e --- /dev/null +++ b/testvectors/demux/ad1/index_1_v.m3u8 @@ -0,0 +1,17 @@ +#EXTM3U +#EXT-X-TARGETDURATION:3 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:3.000, +ad1_1_av.ts +#EXTINF:3.000, +ad2_1_av.ts +#EXTINF:3.000, +ad3_1_av.ts +#EXTINF:3.000, +ad4_1_av.ts +#EXTINF:3.000, +ad5_1_av.ts +#EXT-X-ENDLIST diff --git a/testvectors/demux/ad1/index_mono-en_a.m3u8 b/testvectors/demux/ad1/index_mono-en_a.m3u8 new file mode 100644 index 0000000..94428fd --- /dev/null +++ b/testvectors/demux/ad1/index_mono-en_a.m3u8 @@ -0,0 +1,17 @@ +#EXTM3U +#EXT-X-TARGETDURATION:3 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:3.000, +ad1_men_a.ts +#EXTINF:3.000, +ad2_men_a.ts +#EXTINF:3.000, +ad3_men_a.ts +#EXTINF:3.000, +ad4_men_a.ts +#EXTINF:3.000, +ad5_men_a.ts +#EXT-X-ENDLIST diff --git a/testvectors/demux/ad1/index_mono-sv_a.m3u8 b/testvectors/demux/ad1/index_mono-sv_a.m3u8 new file mode 100644 index 0000000..e6c5ae8 --- /dev/null +++ b/testvectors/demux/ad1/index_mono-sv_a.m3u8 @@ -0,0 +1,17 @@ +#EXTM3U +#EXT-X-TARGETDURATION:3 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:3.000, +ad1_msv_a.ts +#EXTINF:3.000, +ad2_msv_a.ts +#EXTINF:3.000, +ad3_msv_a.ts +#EXTINF:3.000, +ad4_msv_a.ts +#EXTINF:3.000, +ad5_msv_a.ts +#EXT-X-ENDLIST diff --git a/testvectors/demux/ad1/index_stereo-en_a.m3u8 b/testvectors/demux/ad1/index_stereo-en_a.m3u8 new file mode 100644 index 0000000..120aa89 --- /dev/null +++ b/testvectors/demux/ad1/index_stereo-en_a.m3u8 @@ -0,0 +1,17 @@ +#EXTM3U +#EXT-X-TARGETDURATION:3 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:3.000, +ad1_sen_a.ts +#EXTINF:3.000, +ad2_sen_a.ts +#EXTINF:3.000, +ad3_sen_a.ts +#EXTINF:3.000, +ad4_sen_a.ts +#EXTINF:3.000, +ad5_sen_a.ts +#EXT-X-ENDLIST diff --git a/testvectors/demux/ad1/index_stereo-sv_a.m3u8 b/testvectors/demux/ad1/index_stereo-sv_a.m3u8 new file mode 100644 index 0000000..b01b2c3 --- /dev/null +++ b/testvectors/demux/ad1/index_stereo-sv_a.m3u8 @@ -0,0 +1,17 @@ +#EXTM3U +#EXT-X-TARGETDURATION:3 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:3.000, +ad1_ssv_a.ts +#EXTINF:3.000, +ad2_ssv_a.ts +#EXTINF:3.000, +ad3_ssv_a.ts +#EXTINF:3.000, +ad4_ssv_a.ts +#EXTINF:3.000, +ad5_ssv_a.ts +#EXT-X-ENDLIST diff --git a/testvectors/demux/ad1/master.m3u8 b/testvectors/demux/ad1/master.m3u8 new file mode 100644 index 0000000..e851383 --- /dev/null +++ b/testvectors/demux/ad1/master.m3u8 @@ -0,0 +1,9 @@ +#EXTM3U +#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=4497000,RESOLUTION=1280x720,CODECS="avc1.77.30, mp4a.40.2",CLOSED-CAPTIONS=NONE +index_0_av.m3u8 +#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2497000,RESOLUTION=1024x576,CODECS="avc1.77.30, mp4a.40.2",CLOSED-CAPTIONS=NONE +index_1_av.m3u8 +#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="mono",LANGUAGE="sv",NAME="Swedish",AUTOSELECT=YES,DEFAULT=YES,URI="index_mono-sv_a.m3u8" +#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="mono",LANGUAGE="en",NAME="English",AUTOSELECT=NO,DEFAULT=NO,URI="index_mono-en_a.m3u8" +#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="stereo",LANGUAGE="sv",NAME="Swedish",AUTOSELECT=YES,DEFAULT=YES,URI="index_stereo-sv_a.m3u8" +#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="stereo",LANGUAGE="en",NAME="English",AUTOSELECT=NO,DEFAULT=NO,URI="index_stereo-en_a.m3u8" diff --git a/testvectors/demux/ad2/index_0_v.m3u8 b/testvectors/demux/ad2/index_0_v.m3u8 new file mode 100644 index 0000000..5e0ff83 --- /dev/null +++ b/testvectors/demux/ad2/index_0_v.m3u8 @@ -0,0 +1,17 @@ +#EXTM3U +#EXT-X-TARGETDURATION:3 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:3.000, +ad1_0_av.ts +#EXTINF:3.000, +ad2_0_av.ts +#EXTINF:3.000, +ad3_0_av.ts +#EXTINF:3.000, +ad4_0_av.ts +#EXTINF:3.000, +ad5_0_av.ts +#EXT-X-ENDLIST diff --git a/testvectors/demux/ad2/index_1_v.m3u8 b/testvectors/demux/ad2/index_1_v.m3u8 new file mode 100644 index 0000000..e40904e --- /dev/null +++ b/testvectors/demux/ad2/index_1_v.m3u8 @@ -0,0 +1,17 @@ +#EXTM3U +#EXT-X-TARGETDURATION:3 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:3.000, +ad1_1_av.ts +#EXTINF:3.000, +ad2_1_av.ts +#EXTINF:3.000, +ad3_1_av.ts +#EXTINF:3.000, +ad4_1_av.ts +#EXTINF:3.000, +ad5_1_av.ts +#EXT-X-ENDLIST diff --git a/testvectors/demux/ad2/index_audio-no_a.m3u8 b/testvectors/demux/ad2/index_audio-no_a.m3u8 new file mode 100644 index 0000000..d42b875 --- /dev/null +++ b/testvectors/demux/ad2/index_audio-no_a.m3u8 @@ -0,0 +1,17 @@ +#EXTM3U +#EXT-X-TARGETDURATION:3 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:3.000, +ad1_ano_a.ts +#EXTINF:3.000, +ad2_ano_a.ts +#EXTINF:3.000, +ad3_ano_a.ts +#EXTINF:3.000, +ad4_ano_a.ts +#EXTINF:3.000, +ad5_ano_a.ts +#EXT-X-ENDLIST \ No newline at end of file diff --git a/testvectors/demux/ad2/master.m3u8 b/testvectors/demux/ad2/master.m3u8 new file mode 100644 index 0000000..82260c2 --- /dev/null +++ b/testvectors/demux/ad2/master.m3u8 @@ -0,0 +1,6 @@ +#EXTM3U +#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=4397000,RESOLUTION=1280x720,CODECS="avc1.77.30, mp4a.40.2",CLOSED-CAPTIONS=NONE,AUDIO="mono" +index_0_v.m3u8 +#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2597000,RESOLUTION=1024x576,CODECS="avc1.77.30, mp4a.40.2",CLOSED-CAPTIONS=NONE,AUDIO="stereo" +index_1_v.m3u8 +#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",LANGUAGE="no",NAME="Norsk",AUTOSELECT=YES,DEFAULT=YES,URI="index_audio-no_a.m3u8" diff --git a/testvectors/demux/ad3/index_0_v.m3u8 b/testvectors/demux/ad3/index_0_v.m3u8 new file mode 100644 index 0000000..48d1179 --- /dev/null +++ b/testvectors/demux/ad3/index_0_v.m3u8 @@ -0,0 +1,9 @@ +#EXTM3U +#EXT-X-TARGETDURATION:3 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:3.000, +ad1_0_av.ts +#EXT-X-ENDLIST diff --git a/testvectors/demux/ad3/index_1_v.m3u8 b/testvectors/demux/ad3/index_1_v.m3u8 new file mode 100644 index 0000000..4ea5076 --- /dev/null +++ b/testvectors/demux/ad3/index_1_v.m3u8 @@ -0,0 +1,9 @@ +#EXTM3U +#EXT-X-TARGETDURATION:3 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:3.000, +ad1_1_av.ts +#EXT-X-ENDLIST diff --git a/testvectors/demux/ad3/index_mono-en_a.m3u8 b/testvectors/demux/ad3/index_mono-en_a.m3u8 new file mode 100644 index 0000000..09c121e --- /dev/null +++ b/testvectors/demux/ad3/index_mono-en_a.m3u8 @@ -0,0 +1,9 @@ +#EXTM3U +#EXT-X-TARGETDURATION:3 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:3.000, +ad1_men_a.ts +#EXT-X-ENDLIST diff --git a/testvectors/demux/ad3/index_mono-sv_a.m3u8 b/testvectors/demux/ad3/index_mono-sv_a.m3u8 new file mode 100644 index 0000000..488f379 --- /dev/null +++ b/testvectors/demux/ad3/index_mono-sv_a.m3u8 @@ -0,0 +1,9 @@ +#EXTM3U +#EXT-X-TARGETDURATION:3 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:3.000, +ad1_msv_a.ts +#EXT-X-ENDLIST diff --git a/testvectors/demux/ad3/index_stereo-en_a.m3u8 b/testvectors/demux/ad3/index_stereo-en_a.m3u8 new file mode 100644 index 0000000..8e17628 --- /dev/null +++ b/testvectors/demux/ad3/index_stereo-en_a.m3u8 @@ -0,0 +1,9 @@ +#EXTM3U +#EXT-X-TARGETDURATION:3 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:3.000, +ad1_sen_a.ts +#EXT-X-ENDLIST diff --git a/testvectors/demux/ad3/index_stereo-sv_a.m3u8 b/testvectors/demux/ad3/index_stereo-sv_a.m3u8 new file mode 100644 index 0000000..ad1bc9f --- /dev/null +++ b/testvectors/demux/ad3/index_stereo-sv_a.m3u8 @@ -0,0 +1,9 @@ +#EXTM3U +#EXT-X-TARGETDURATION:3 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:3.000, +ad1_ssv_a.ts +#EXT-X-ENDLIST diff --git a/testvectors/demux/ad3/master.m3u8 b/testvectors/demux/ad3/master.m3u8 new file mode 100644 index 0000000..9a6230e --- /dev/null +++ b/testvectors/demux/ad3/master.m3u8 @@ -0,0 +1,10 @@ +#EXTM3U +#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=4497000,RESOLUTION=1280x720,CODECS="avc1.77.30, mp4a.40.2",CLOSED-CAPTIONS=NONE,AUDIO="mono" +index_0_v.m3u8 +#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2497000,RESOLUTION=1024x576,CODECS="avc1.77.30, mp4a.40.2",CLOSED-CAPTIONS=NONE,AUDIO="stereo" +index_1_v.m3u8 +#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="mono",LANGUAGE="sv",NAME="Swedish",AUTOSELECT=YES,DEFAULT=YES,URI="index_mono-sv_a.m3u8" +#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="mono",LANGUAGE="en",NAME="English",AUTOSELECT=NO,DEFAULT=NO,URI="index_mono-en_a.m3u8" +#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="stereo",LANGUAGE="sv",NAME="Swedish",AUTOSELECT=YES,DEFAULT=YES,URI="index_stereo-sv_a.m3u8" +#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="stereo",LANGUAGE="en",NAME="English",AUTOSELECT=NO,DEFAULT=NO,URI="index_stereo-en_a.m3u8" + diff --git a/testvectors/demux/ad4/index_0_v.m3u8 b/testvectors/demux/ad4/index_0_v.m3u8 new file mode 100644 index 0000000..583976f --- /dev/null +++ b/testvectors/demux/ad4/index_0_v.m3u8 @@ -0,0 +1,9 @@ +#EXTM3U +#EXT-X-TARGETDURATION:5 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:5.000, +ad1_0_av.ts +#EXT-X-ENDLIST diff --git a/testvectors/demux/ad4/index_1_v.m3u8 b/testvectors/demux/ad4/index_1_v.m3u8 new file mode 100644 index 0000000..844c4e5 --- /dev/null +++ b/testvectors/demux/ad4/index_1_v.m3u8 @@ -0,0 +1,9 @@ +#EXTM3U +#EXT-X-TARGETDURATION:5 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:5.000, +ad1_1_av.ts +#EXT-X-ENDLIST diff --git a/testvectors/demux/ad4/index_mono-en_a.m3u8 b/testvectors/demux/ad4/index_mono-en_a.m3u8 new file mode 100644 index 0000000..ecf641d --- /dev/null +++ b/testvectors/demux/ad4/index_mono-en_a.m3u8 @@ -0,0 +1,9 @@ +#EXTM3U +#EXT-X-TARGETDURATION:5 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:5.000, +ad1_men_a.ts +#EXT-X-ENDLIST diff --git a/testvectors/demux/ad4/index_mono-sv_a.m3u8 b/testvectors/demux/ad4/index_mono-sv_a.m3u8 new file mode 100644 index 0000000..b019713 --- /dev/null +++ b/testvectors/demux/ad4/index_mono-sv_a.m3u8 @@ -0,0 +1,9 @@ +#EXTM3U +#EXT-X-TARGETDURATION:5 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:5.000, +ad1_msv_a.ts +#EXT-X-ENDLIST diff --git a/testvectors/demux/ad4/index_stereo-en_a.m3u8 b/testvectors/demux/ad4/index_stereo-en_a.m3u8 new file mode 100644 index 0000000..d996e3f --- /dev/null +++ b/testvectors/demux/ad4/index_stereo-en_a.m3u8 @@ -0,0 +1,9 @@ +#EXTM3U +#EXT-X-TARGETDURATION:5 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:5.000, +ad1_sen_a.ts +#EXT-X-ENDLIST diff --git a/testvectors/demux/ad4/index_stereo-sv_a.m3u8 b/testvectors/demux/ad4/index_stereo-sv_a.m3u8 new file mode 100644 index 0000000..ae7cfff --- /dev/null +++ b/testvectors/demux/ad4/index_stereo-sv_a.m3u8 @@ -0,0 +1,9 @@ +#EXTM3U +#EXT-X-TARGETDURATION:5 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:5.000, +ad1_ssv_a.ts +#EXT-X-ENDLIST diff --git a/testvectors/demux/ad4/master.m3u8 b/testvectors/demux/ad4/master.m3u8 new file mode 100644 index 0000000..0a98766 --- /dev/null +++ b/testvectors/demux/ad4/master.m3u8 @@ -0,0 +1,9 @@ +#EXTM3U +#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=4497000,RESOLUTION=1280x720,CODECS="avc1.77.30, mp4a.40.2",CLOSED-CAPTIONS=NONE,AUDIO="mono" +index_0_v.m3u8 +#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2497000,RESOLUTION=1024x576,CODECS="avc1.77.30, mp4a.40.2",CLOSED-CAPTIONS=NONE,AUDIO="stereo" +index_1_v.m3u8 +#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="mono",LANGUAGE="sv",NAME="Swedish",AUTOSELECT=YES,DEFAULT=YES,URI="index_mono-sv_a.m3u8" +#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="mono",LANGUAGE="en",NAME="English",AUTOSELECT=NO,DEFAULT=NO,URI="index_mono-en_a.m3u8" +#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="stereo",LANGUAGE="sv",NAME="Swedish",AUTOSELECT=YES,DEFAULT=YES,URI="index_stereo-sv_a.m3u8" +#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="stereo",LANGUAGE="en",NAME="English",AUTOSELECT=NO,DEFAULT=NO,URI="index_stereo-en_a.m3u8" diff --git a/testvectors/demux/hls1/index_0_v.m3u8 b/testvectors/demux/hls1/index_0_v.m3u8 new file mode 100644 index 0000000..b74e314 --- /dev/null +++ b/testvectors/demux/hls1/index_0_v.m3u8 @@ -0,0 +1,28 @@ +#EXTM3U +#EXT-X-TARGETDURATION:9 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:9.000, +segment1_0_av.ts +#EXTINF:9.000, +segment2_0_av.ts +#EXTINF:9.000, +segment3_0_av.ts +#EXTINF:9.000, +segment4_0_av.ts +#EXTINF:9.000, +segment5_0_av.ts +#EXTINF:9.000, +segment6_0_av.ts +#EXTINF:9.000, +segment7_0_av.ts +#EXTINF:9.000, +segment8_0_av.ts +#EXTINF:9.000, +segment9_0_av.ts +#EXTINF:6.266, +segment10_0_av.ts +#EXT-X-ENDLIST diff --git a/testvectors/demux/hls1/index_1_v.m3u8 b/testvectors/demux/hls1/index_1_v.m3u8 new file mode 100644 index 0000000..042ee6c --- /dev/null +++ b/testvectors/demux/hls1/index_1_v.m3u8 @@ -0,0 +1,28 @@ +#EXTM3U +#EXT-X-TARGETDURATION:9 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:9.000, +segment1_1_av.ts +#EXTINF:9.000, +segment2_1_av.ts +#EXTINF:9.000, +segment3_1_av.ts +#EXTINF:9.000, +segment4_1_av.ts +#EXTINF:9.000, +segment5_1_av.ts +#EXTINF:9.000, +segment6_1_av.ts +#EXTINF:9.000, +segment7_1_av.ts +#EXTINF:9.000, +segment8_1_av.ts +#EXTINF:9.000, +segment9_1_av.ts +#EXTINF:6.266, +segment10_1_av.ts +#EXT-X-ENDLIST diff --git a/testvectors/demux/hls1/index_mono-en_a.m3u8 b/testvectors/demux/hls1/index_mono-en_a.m3u8 new file mode 100644 index 0000000..90672a9 --- /dev/null +++ b/testvectors/demux/hls1/index_mono-en_a.m3u8 @@ -0,0 +1,28 @@ +#EXTM3U +#EXT-X-TARGETDURATION:9 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:9.000, +segment1_men_a.ts +#EXTINF:9.000, +segment2_men_a.ts +#EXTINF:9.000, +segment3_men_a.ts +#EXTINF:9.000, +segment4_men_a.ts +#EXTINF:9.000, +segment5_men_a.ts +#EXTINF:9.000, +segment6_men_a.ts +#EXTINF:9.000, +segment7_men_a.ts +#EXTINF:9.000, +segment8_men_a.ts +#EXTINF:9.000, +segment9_men_a.ts +#EXTINF:6.266, +segment10_men_a.ts +#EXT-X-ENDLIST diff --git a/testvectors/demux/hls1/index_mono-sv_a.m3u8 b/testvectors/demux/hls1/index_mono-sv_a.m3u8 new file mode 100644 index 0000000..5c887c0 --- /dev/null +++ b/testvectors/demux/hls1/index_mono-sv_a.m3u8 @@ -0,0 +1,28 @@ +#EXTM3U +#EXT-X-TARGETDURATION:9 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:9.000, +segment1_msv_a.ts +#EXTINF:9.000, +segment2_msv_a.ts +#EXTINF:9.000, +segment3_msv_a.ts +#EXTINF:9.000, +segment4_msv_a.ts +#EXTINF:9.000, +segment5_msv_a.ts +#EXTINF:9.000, +segment6_msv_a.ts +#EXTINF:9.000, +segment7_msv_a.ts +#EXTINF:9.000, +segment8_msv_a.ts +#EXTINF:9.000, +segment9_msv_a.ts +#EXTINF:6.266, +segment10_msv_a.ts +#EXT-X-ENDLIST diff --git a/testvectors/demux/hls1/index_stereo-en_a.m3u8 b/testvectors/demux/hls1/index_stereo-en_a.m3u8 new file mode 100644 index 0000000..d8df36d --- /dev/null +++ b/testvectors/demux/hls1/index_stereo-en_a.m3u8 @@ -0,0 +1,28 @@ +#EXTM3U +#EXT-X-TARGETDURATION:9 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:9.000, +segment1_sen_a.ts +#EXTINF:9.000, +segment2_sen_a.ts +#EXTINF:9.000, +segment3_sen_a.ts +#EXTINF:9.000, +segment4_sen_a.ts +#EXTINF:9.000, +segment5_sen_a.ts +#EXTINF:9.000, +segment6_sen_a.ts +#EXTINF:9.000, +segment7_sen_a.ts +#EXTINF:9.000, +segment8_sen_a.ts +#EXTINF:9.000, +segment9_sen_a.ts +#EXTINF:6.266, +segment10_sen_a.ts +#EXT-X-ENDLIST diff --git a/testvectors/demux/hls1/index_stereo-sv_a.m3u8 b/testvectors/demux/hls1/index_stereo-sv_a.m3u8 new file mode 100644 index 0000000..3995129 --- /dev/null +++ b/testvectors/demux/hls1/index_stereo-sv_a.m3u8 @@ -0,0 +1,28 @@ +#EXTM3U +#EXT-X-TARGETDURATION:9 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:9.000, +segment1_ssv_a.ts +#EXTINF:9.000, +segment2_ssv_a.ts +#EXTINF:9.000, +segment3_ssv_a.ts +#EXTINF:9.000, +segment4_ssv_a.ts +#EXTINF:9.000, +segment5_ssv_a.ts +#EXTINF:9.000, +segment6_ssv_a.ts +#EXTINF:9.000, +segment7_ssv_a.ts +#EXTINF:9.000, +segment8_ssv_a.ts +#EXTINF:9.000, +segment_ssv_a.ts +#EXTINF:6.266, +segment10_ssv_a.ts +#EXT-X-ENDLIST diff --git a/testvectors/demux/hls1/master.m3u8 b/testvectors/demux/hls1/master.m3u8 new file mode 100644 index 0000000..0a98766 --- /dev/null +++ b/testvectors/demux/hls1/master.m3u8 @@ -0,0 +1,9 @@ +#EXTM3U +#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=4497000,RESOLUTION=1280x720,CODECS="avc1.77.30, mp4a.40.2",CLOSED-CAPTIONS=NONE,AUDIO="mono" +index_0_v.m3u8 +#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2497000,RESOLUTION=1024x576,CODECS="avc1.77.30, mp4a.40.2",CLOSED-CAPTIONS=NONE,AUDIO="stereo" +index_1_v.m3u8 +#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="mono",LANGUAGE="sv",NAME="Swedish",AUTOSELECT=YES,DEFAULT=YES,URI="index_mono-sv_a.m3u8" +#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="mono",LANGUAGE="en",NAME="English",AUTOSELECT=NO,DEFAULT=NO,URI="index_mono-en_a.m3u8" +#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="stereo",LANGUAGE="sv",NAME="Swedish",AUTOSELECT=YES,DEFAULT=YES,URI="index_stereo-sv_a.m3u8" +#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="stereo",LANGUAGE="en",NAME="English",AUTOSELECT=NO,DEFAULT=NO,URI="index_stereo-en_a.m3u8" diff --git a/testvectors/demux/hls1b/index_0_v.m3u8 b/testvectors/demux/hls1b/index_0_v.m3u8 new file mode 100644 index 0000000..e82dbdc --- /dev/null +++ b/testvectors/demux/hls1b/index_0_v.m3u8 @@ -0,0 +1,30 @@ +#EXTM3U +#EXT-X-TARGETDURATION:8 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:8.000, +b_segment1_0_av.ts +#EXTINF:8.000, +b_segment2_0_av.ts +#EXTINF:8.000, +b_segment3_0_av.ts +#EXTINF:8.000, +b_segment4_0_av.ts +#EXTINF:8.000, +b_segment5_0_av.ts +#EXTINF:8.000, +b_segment6_0_av.ts +#EXTINF:8.000, +b_segment7_0_av.ts +#EXTINF:8.000, +b_segment8_0_av.ts +#EXTINF:8.000, +b_segment9_0_av.ts +#EXTINF:8.000, +b_segment10_0_av.ts +#EXTINF:7.266, +b_segment11_0_av.ts +#EXT-X-ENDLIST diff --git a/testvectors/demux/hls1b/index_1_v.m3u8 b/testvectors/demux/hls1b/index_1_v.m3u8 new file mode 100644 index 0000000..0175378 --- /dev/null +++ b/testvectors/demux/hls1b/index_1_v.m3u8 @@ -0,0 +1,30 @@ +#EXTM3U +#EXT-X-TARGETDURATION:8 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:8.000, +b_segment1_1_av.ts +#EXTINF:8.000, +b_segment2_1_av.ts +#EXTINF:8.000, +b_segment3_1_av.ts +#EXTINF:8.000, +b_segment4_1_av.ts +#EXTINF:8.000, +b_segment5_1_av.ts +#EXTINF:8.000, +b_segment6_1_av.ts +#EXTINF:8.000, +b_segment7_1_av.ts +#EXTINF:8.000, +b_segment8_1_av.ts +#EXTINF:8.000, +b_segment9_1_av.ts +#EXTINF:8.000, +b_segment10_1_av.ts +#EXTINF:7.266, +b_segment11_1_av.ts +#EXT-X-ENDLIST diff --git a/testvectors/demux/hls1b/index_mono-en_a.m3u8 b/testvectors/demux/hls1b/index_mono-en_a.m3u8 new file mode 100644 index 0000000..286ca96 --- /dev/null +++ b/testvectors/demux/hls1b/index_mono-en_a.m3u8 @@ -0,0 +1,30 @@ +#EXTM3U +#EXT-X-TARGETDURATION:8 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:8.000, +b_segment1_men_a.ts +#EXTINF:8.000, +b_segment2_men_a.ts +#EXTINF:8.000, +b_segment3_men_a.ts +#EXTINF:8.000, +b_segment4_men_a.ts +#EXTINF:8.000, +b_segment5_men_a.ts +#EXTINF:8.000, +b_segment6_men_a.ts +#EXTINF:8.000, +b_segment7_men_a.ts +#EXTINF:8.000, +b_segment8_men_a.ts +#EXTINF:8.000, +b_segment9_men_a.ts +#EXTINF:8.000, +b_segment10_men_a.ts +#EXTINF:7.266, +b_segment11_men_a.ts +#EXT-X-ENDLIST diff --git a/testvectors/demux/hls1b/index_mono-sv_a.m3u8 b/testvectors/demux/hls1b/index_mono-sv_a.m3u8 new file mode 100644 index 0000000..cf92127 --- /dev/null +++ b/testvectors/demux/hls1b/index_mono-sv_a.m3u8 @@ -0,0 +1,30 @@ +#EXTM3U +#EXT-X-TARGETDURATION:8 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:8.000, +b_segment1_msv_a.ts +#EXTINF:8.000, +b_segment2_msv_a.ts +#EXTINF:8.000, +b_segment3_msv_a.ts +#EXTINF:8.000, +b_segment4_msv_a.ts +#EXTINF:8.000, +b_segment5_msv_a.ts +#EXTINF:8.000, +b_segment6_msv_a.ts +#EXTINF:8.000, +b_segment7_msv_a.ts +#EXTINF:8.000, +b_segment8_msv_a.ts +#EXTINF:8.000, +b_segment9_msv_a.ts +#EXTINF:8.000, +b_segment10_msv_a.ts +#EXTINF:7.266, +b_segment11_msv_a.ts +#EXT-X-ENDLIST diff --git a/testvectors/demux/hls1b/index_stereo-en_a.m3u8 b/testvectors/demux/hls1b/index_stereo-en_a.m3u8 new file mode 100644 index 0000000..03359d1 --- /dev/null +++ b/testvectors/demux/hls1b/index_stereo-en_a.m3u8 @@ -0,0 +1,30 @@ +#EXTM3U +#EXT-X-TARGETDURATION:8 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:8.000, +b_segment1_sen_a.ts +#EXTINF:8.000, +b_segment2_sen_a.ts +#EXTINF:8.000, +b_segment3_sen_a.ts +#EXTINF:8.000, +b_segment4_sen_a.ts +#EXTINF:8.000, +b_segment5_sen_a.ts +#EXTINF:8.000, +b_segment6_sen_a.ts +#EXTINF:8.000, +b_segment7_sen_a.ts +#EXTINF:8.000, +b_segment8_sen_a.ts +#EXTINF:8.000, +b_segment9_sen_a.ts +#EXTINF:8.000, +b_segment10_sen_a.ts +#EXTINF:7.266, +b_segment11_sen_a.ts +#EXT-X-ENDLIST diff --git a/testvectors/demux/hls1b/index_stereo-sv_a.m3u8 b/testvectors/demux/hls1b/index_stereo-sv_a.m3u8 new file mode 100644 index 0000000..6b966bd --- /dev/null +++ b/testvectors/demux/hls1b/index_stereo-sv_a.m3u8 @@ -0,0 +1,30 @@ +#EXTM3U +#EXT-X-TARGETDURATION:8 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:8.000, +b_segment1_ssv_a.ts +#EXTINF:8.000, +b_segment2_ssv_a.ts +#EXTINF:8.000, +b_segment3_ssv_a.ts +#EXTINF:8.000, +b_segment4_ssv_a.ts +#EXTINF:8.000, +b_segment5_ssv_a.ts +#EXTINF:8.000, +b_segment6_ssv_a.ts +#EXTINF:8.000, +b_segment7_ssv_a.ts +#EXTINF:8.000, +b_segment8_ssv_a.ts +#EXTINF:8.000, +b_segment9_ssv_a.ts +#EXTINF:8.000, +b_segment10_ssv_a.ts +#EXTINF:7.266, +b_segment11_ssv_a.ts +#EXT-X-ENDLIST diff --git a/testvectors/demux/hls1b/master.m3u8 b/testvectors/demux/hls1b/master.m3u8 new file mode 100644 index 0000000..0a98766 --- /dev/null +++ b/testvectors/demux/hls1b/master.m3u8 @@ -0,0 +1,9 @@ +#EXTM3U +#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=4497000,RESOLUTION=1280x720,CODECS="avc1.77.30, mp4a.40.2",CLOSED-CAPTIONS=NONE,AUDIO="mono" +index_0_v.m3u8 +#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2497000,RESOLUTION=1024x576,CODECS="avc1.77.30, mp4a.40.2",CLOSED-CAPTIONS=NONE,AUDIO="stereo" +index_1_v.m3u8 +#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="mono",LANGUAGE="sv",NAME="Swedish",AUTOSELECT=YES,DEFAULT=YES,URI="index_mono-sv_a.m3u8" +#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="mono",LANGUAGE="en",NAME="English",AUTOSELECT=NO,DEFAULT=NO,URI="index_mono-en_a.m3u8" +#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="stereo",LANGUAGE="sv",NAME="Swedish",AUTOSELECT=YES,DEFAULT=YES,URI="index_stereo-sv_a.m3u8" +#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="stereo",LANGUAGE="en",NAME="English",AUTOSELECT=NO,DEFAULT=NO,URI="index_stereo-en_a.m3u8" diff --git a/testvectors/demux/hls2/index_0_v.m3u8 b/testvectors/demux/hls2/index_0_v.m3u8 new file mode 100644 index 0000000..b74e314 --- /dev/null +++ b/testvectors/demux/hls2/index_0_v.m3u8 @@ -0,0 +1,28 @@ +#EXTM3U +#EXT-X-TARGETDURATION:9 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:9.000, +segment1_0_av.ts +#EXTINF:9.000, +segment2_0_av.ts +#EXTINF:9.000, +segment3_0_av.ts +#EXTINF:9.000, +segment4_0_av.ts +#EXTINF:9.000, +segment5_0_av.ts +#EXTINF:9.000, +segment6_0_av.ts +#EXTINF:9.000, +segment7_0_av.ts +#EXTINF:9.000, +segment8_0_av.ts +#EXTINF:9.000, +segment9_0_av.ts +#EXTINF:6.266, +segment10_0_av.ts +#EXT-X-ENDLIST diff --git a/testvectors/demux/hls2/index_1_v.m3u8 b/testvectors/demux/hls2/index_1_v.m3u8 new file mode 100644 index 0000000..a3fe245 --- /dev/null +++ b/testvectors/demux/hls2/index_1_v.m3u8 @@ -0,0 +1,28 @@ +#EXTM3U +#EXT-X-TARGETDURATION:9 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-INDEPENDENT-SEGMENTS +##EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:9.000, +segment1_1_av.ts +#EXTINF:9.000, +segment2_1_av.ts +#EXTINF:9.000, +segment3_1_av.ts +#EXTINF:9.000, +segment4_1_av.ts +#EXTINF:9.000, +segment5_1_av.ts +#EXTINF:9.000, +segment6_1_av.ts +#EXTINF:9.000, +segment7_1_av.ts +#EXTINF:9.000, +segment8_1_av.ts +#EXTINF:9.000, +segment9_1_av.ts +#EXTINF:6.266, +segment10_1_av.ts +#EXT-X-ENDLIST diff --git a/testvectors/demux/hls2/index_audio-no_a.m3u8 b/testvectors/demux/hls2/index_audio-no_a.m3u8 new file mode 100644 index 0000000..8a6a3b1 --- /dev/null +++ b/testvectors/demux/hls2/index_audio-no_a.m3u8 @@ -0,0 +1,28 @@ +#EXTM3U +#EXT-X-TARGETDURATION:9 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-INDEPENDENT-SEGMENTS +##EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:9.000, +segment1_ano.ts +#EXTINF:9.000, +segment2_ano.ts +#EXTINF:9.000, +segment3_ano.ts +#EXTINF:9.000, +segment4_ano.ts +#EXTINF:9.000, +segment5_ano.ts +#EXTINF:9.000, +segment6_ano.ts +#EXTINF:9.000, +segment7_ano.ts +#EXTINF:9.000, +segment8_ano.ts +#EXTINF:9.000, +segment9_ano.ts +#EXTINF:6.266, +segment10_ano.ts +#EXT-X-ENDLIST diff --git a/testvectors/demux/hls2/master.m3u8 b/testvectors/demux/hls2/master.m3u8 new file mode 100644 index 0000000..0049522 --- /dev/null +++ b/testvectors/demux/hls2/master.m3u8 @@ -0,0 +1,6 @@ +#EXTM3U +#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=4497000,RESOLUTION=1280x720,CODECS="avc1.77.30, mp4a.40.2",CLOSED-CAPTIONS=NONE,AUDIO="mono" +index_0_v.m3u8 +#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2497000,RESOLUTION=1024x576,CODECS="avc1.77.30, mp4a.40.2",CLOSED-CAPTIONS=NONE,AUDIO="stereo" +index_1_v.m3u8 +#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",LANGUAGE="no",NAME="Norsk",AUTOSELECT=YES,DEFAULT=YES,URI="index_audio-no_a.m3u8" diff --git a/testvectors/demux_n_cmaf/ad1/index_0_v.m3u8 b/testvectors/demux_n_cmaf/ad1/index_0_v.m3u8 new file mode 100644 index 0000000..c51f864 --- /dev/null +++ b/testvectors/demux_n_cmaf/ad1/index_0_v.m3u8 @@ -0,0 +1,21 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Created with Unified Streaming Platform (version=1.11.20-26889) +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MEDIA-SEQUENCE:1 +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-TARGETDURATION:3 +#USP-X-TIMESTAMP-MAP:MPEGTS=900000,LOCAL=1970-01-01T00:00:00Z +#EXT-X-MAP:URI="mock-ad-video=2525000.m4s" +#EXTINF:3, no desc +mock-ad-video=2525000-1.m4s +#EXTINF:3, no desc +mock-ad-video=2525000-2.m4s +#EXTINF:3, no desc +mock-ad-video=2525000-3.m4s +#EXTINF:3, no desc +mock-ad-video=2525000-4.m4s +#EXTINF:2.16, no desc +mock-ad-video=2525000-5.m4s +#EXT-X-ENDLIST + diff --git a/testvectors/demux_n_cmaf/ad1/index_1_v.m3u8 b/testvectors/demux_n_cmaf/ad1/index_1_v.m3u8 new file mode 100644 index 0000000..b5624e4 --- /dev/null +++ b/testvectors/demux_n_cmaf/ad1/index_1_v.m3u8 @@ -0,0 +1,20 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Created with Unified Streaming Platform (version=1.11.20-26889) +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MEDIA-SEQUENCE:1 +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-TARGETDURATION:3 +#USP-X-TIMESTAMP-MAP:MPEGTS=900000,LOCAL=1970-01-01T00:00:00Z +#EXT-X-MAP:URI="mock-ad-video=4545000.m4s" +#EXTINF:3, no desc +mock-ad-video=4545000-1.m4s +#EXTINF:3, no desc +mock-ad-video=4545000-2.m4s +#EXTINF:3, no desc +mock-ad-video=4545000-3.m4s +#EXTINF:3, no desc +mock-ad-video=4545000-4.m4s +#EXTINF:2.16, no desc +mock-ad-video=4545000-5.m4s +#EXT-X-ENDLIST \ No newline at end of file diff --git a/testvectors/demux_n_cmaf/ad1/index_stereo-sv_a.m3u8 b/testvectors/demux_n_cmaf/ad1/index_stereo-sv_a.m3u8 new file mode 100644 index 0000000..64963c7 --- /dev/null +++ b/testvectors/demux_n_cmaf/ad1/index_stereo-sv_a.m3u8 @@ -0,0 +1,26 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Created with Unified Streaming Platform (version=1.11.20-26889) +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MEDIA-SEQUENCE:1 +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-TARGETDURATION:2 +#USP-X-TIMESTAMP-MAP:MPEGTS=900000,LOCAL=1970-01-01T00:00:00Z +#EXT-X-MAP:URI="mock-ad-audio=256000.m4s" +#EXTINF:1.92, no desc +mock-ad-audio=256000-1.m4s +#EXTINF:1.92, no desc +mock-ad-audio=256000-2.m4s +#EXTINF:1.92, no desc +mock-ad-audio=256000-3.m4s +#EXTINF:1.92, no desc +mock-ad-audio=256000-4.m4s +#EXTINF:1.92, no desc +mock-ad-audio=256000-5.m4s +#EXTINF:1.92, no desc +mock-ad-audio=256000-6.m4s +#EXTINF:1.92, no desc +mock-ad-audio=256000-7.m4s +#EXTINF:1.4933, no desc +mock-ad-audio=256000-8.m4s +#EXT-X-ENDLIST diff --git a/testvectors/demux_n_cmaf/ad1/master.m3u8 b/testvectors/demux_n_cmaf/ad1/master.m3u8 new file mode 100644 index 0000000..8ab4f77 --- /dev/null +++ b/testvectors/demux_n_cmaf/ad1/master.m3u8 @@ -0,0 +1,6 @@ +#EXTM3U +#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=4545000,RESOLUTION=1280x720,CODECS="avc1.77.30, mp4a.40.2",CLOSED-CAPTIONS=NONE +index_0_av.m3u8 +#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2525000,RESOLUTION=1024x576,CODECS="avc1.77.30, mp4a.40.2",CLOSED-CAPTIONS=NONE +index_1_av.m3u8 +#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="stereo",LANGUAGE="sv",NAME="Swedish",AUTOSELECT=YES,DEFAULT=YES,URI="index_stereo-sv_a.m3u8" diff --git a/testvectors/demux_n_cmaf/ad3/index_0_v.m3u8 b/testvectors/demux_n_cmaf/ad3/index_0_v.m3u8 new file mode 100644 index 0000000..9ef0d5d --- /dev/null +++ b/testvectors/demux_n_cmaf/ad3/index_0_v.m3u8 @@ -0,0 +1,13 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Created with Unified Streaming Platform (version=1.11.20-26889) +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MEDIA-SEQUENCE:1 +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-TARGETDURATION:3 +#USP-X-TIMESTAMP-MAP:MPEGTS=900000,LOCAL=1970-01-01T00:00:00Z +#EXT-X-MAP:URI="mock-ad3-video=2525000.m4s" +#EXTINF:3, no desc +mock-ad3-video=2525000-1.m4s +#EXT-X-ENDLIST + diff --git a/testvectors/demux_n_cmaf/ad3/index_1_v.m3u8 b/testvectors/demux_n_cmaf/ad3/index_1_v.m3u8 new file mode 100644 index 0000000..ea6e688 --- /dev/null +++ b/testvectors/demux_n_cmaf/ad3/index_1_v.m3u8 @@ -0,0 +1,12 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Created with Unified Streaming Platform (version=1.11.20-26889) +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MEDIA-SEQUENCE:1 +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-TARGETDURATION:3 +#USP-X-TIMESTAMP-MAP:MPEGTS=900000,LOCAL=1970-01-01T00:00:00Z +#EXT-X-MAP:URI="mock-ad3-video=4545000.m4s" +#EXTINF:3, no desc +mock-ad3-video=4545000-1.m4s +#EXT-X-ENDLIST \ No newline at end of file diff --git a/testvectors/demux_n_cmaf/ad3/index_stereo-sv_a.m3u8 b/testvectors/demux_n_cmaf/ad3/index_stereo-sv_a.m3u8 new file mode 100644 index 0000000..01dfac2 --- /dev/null +++ b/testvectors/demux_n_cmaf/ad3/index_stereo-sv_a.m3u8 @@ -0,0 +1,14 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Created with Unified Streaming Platform (version=1.11.20-26889) +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MEDIA-SEQUENCE:1 +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-TARGETDURATION:2 +#USP-X-TIMESTAMP-MAP:MPEGTS=900000,LOCAL=1970-01-01T00:00:00Z +#EXT-X-MAP:URI="mock-ad3-audio=256000.m4s" +#EXTINF:1.92, no desc +mock-ad3-audio=256000-1.m4s +#EXTINF:1.4933, no desc +mock-ad3-audio=256000-2.m4s +#EXT-X-ENDLIST diff --git a/testvectors/demux_n_cmaf/ad3/master.m3u8 b/testvectors/demux_n_cmaf/ad3/master.m3u8 new file mode 100644 index 0000000..8ab4f77 --- /dev/null +++ b/testvectors/demux_n_cmaf/ad3/master.m3u8 @@ -0,0 +1,6 @@ +#EXTM3U +#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=4545000,RESOLUTION=1280x720,CODECS="avc1.77.30, mp4a.40.2",CLOSED-CAPTIONS=NONE +index_0_av.m3u8 +#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2525000,RESOLUTION=1024x576,CODECS="avc1.77.30, mp4a.40.2",CLOSED-CAPTIONS=NONE +index_1_av.m3u8 +#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="stereo",LANGUAGE="sv",NAME="Swedish",AUTOSELECT=YES,DEFAULT=YES,URI="index_stereo-sv_a.m3u8" diff --git a/testvectors/demux_n_cmaf/hls1/index_0_v.m3u8 b/testvectors/demux_n_cmaf/hls1/index_0_v.m3u8 new file mode 100644 index 0000000..ec0e596 --- /dev/null +++ b/testvectors/demux_n_cmaf/hls1/index_0_v.m3u8 @@ -0,0 +1,129 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Created with Unified Streaming Platform (version=1.11.20-26889) +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MEDIA-SEQUENCE:1 +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-TARGETDURATION:3 +#USP-X-TIMESTAMP-MAP:MPEGTS=900000,LOCAL=1970-01-01T00:00:00Z +#EXT-X-MAP:URI="test-video=2500000.m4s" +#EXTINF:3, no desc +test-video=2500000-1.m4s +#EXTINF:3, no desc +test-video=2500000-2.m4s +#EXTINF:3, no desc +test-video=2500000-3.m4s +#EXTINF:3, no desc +test-video=2500000-4.m4s +#EXTINF:3, no desc +test-video=2500000-5.m4s +#EXTINF:3, no desc +test-video=2500000-6.m4s +#EXTINF:3, no desc +test-video=2500000-7.m4s +#EXTINF:3, no desc +test-video=2500000-8.m4s +#EXTINF:3, no desc +test-video=2500000-9.m4s +#EXTINF:3, no desc +test-video=2500000-10.m4s +#EXTINF:3, no desc +test-video=2500000-11.m4s +#EXTINF:3, no desc +test-video=2500000-12.m4s +#EXTINF:3, no desc +test-video=2500000-13.m4s +#EXTINF:3, no desc +test-video=2500000-14.m4s +#EXTINF:3, no desc +test-video=2500000-15.m4s +#EXTINF:3, no desc +test-video=2500000-16.m4s +#EXTINF:3, no desc +test-video=2500000-17.m4s +#EXTINF:3, no desc +test-video=2500000-18.m4s +#EXTINF:3, no desc +test-video=2500000-19.m4s +#EXTINF:3, no desc +test-video=2500000-20.m4s +#EXTINF:3, no desc +test-video=2500000-21.m4s +#EXTINF:3, no desc +test-video=2500000-22.m4s +#EXTINF:3, no desc +test-video=2500000-23.m4s +#EXTINF:3, no desc +test-video=2500000-24.m4s +#EXTINF:3, no desc +test-video=2500000-25.m4s +#EXTINF:3, no desc +test-video=2500000-26.m4s +#EXTINF:3, no desc +test-video=2500000-27.m4s +#EXTINF:3, no desc +test-video=2500000-28.m4s +#EXTINF:3, no desc +test-video=2500000-29.m4s +#EXTINF:3, no desc +test-video=2500000-30.m4s +#EXTINF:3, no desc +test-video=2500000-31.m4s +#EXTINF:3, no desc +test-video=2500000-32.m4s +#EXTINF:3, no desc +test-video=2500000-33.m4s +#EXTINF:3, no desc +test-video=2500000-34.m4s +#EXTINF:3, no desc +test-video=2500000-35.m4s +#EXTINF:3, no desc +test-video=2500000-36.m4s +#EXTINF:3, no desc +test-video=2500000-37.m4s +#EXTINF:3, no desc +test-video=2500000-38.m4s +#EXTINF:3, no desc +test-video=2500000-39.m4s +#EXTINF:3, no desc +test-video=2500000-40.m4s +#EXTINF:3, no desc +test-video=2500000-41.m4s +#EXTINF:3, no desc +test-video=2500000-42.m4s +#EXTINF:3, no desc +test-video=2500000-43.m4s +#EXTINF:3, no desc +test-video=2500000-44.m4s +#EXTINF:3, no desc +test-video=2500000-45.m4s +#EXTINF:3, no desc +test-video=2500000-46.m4s +#EXTINF:3, no desc +test-video=2500000-47.m4s +#EXTINF:3, no desc +test-video=2500000-48.m4s +#EXTINF:3, no desc +test-video=2500000-49.m4s +#EXTINF:3, no desc +test-video=2500000-50.m4s +#EXTINF:3, no desc +test-video=2500000-51.m4s +#EXTINF:3, no desc +test-video=2500000-52.m4s +#EXTINF:3, no desc +test-video=2500000-53.m4s +#EXTINF:3, no desc +test-video=2500000-54.m4s +#EXTINF:3, no desc +test-video=2500000-55.m4s +#EXTINF:3, no desc +test-video=2500000-56.m4s +#EXTINF:3, no desc +test-video=2500000-57.m4s +#EXTINF:3, no desc +test-video=2500000-58.m4s +#EXTINF:2.16, no desc +test-video=2500000-59.m4s +#EXT-X-ENDLIST + diff --git a/testvectors/demux_n_cmaf/hls1/index_1_v.m3u8 b/testvectors/demux_n_cmaf/hls1/index_1_v.m3u8 new file mode 100644 index 0000000..d3505de --- /dev/null +++ b/testvectors/demux_n_cmaf/hls1/index_1_v.m3u8 @@ -0,0 +1,129 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Created with Unified Streaming Platform (version=1.11.20-26889) +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MEDIA-SEQUENCE:1 +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-TARGETDURATION:3 +#USP-X-TIMESTAMP-MAP:MPEGTS=900000,LOCAL=1970-01-01T00:00:00Z +#EXT-X-MAP:URI="test-video=3500000.m4s" +#EXTINF:3, no desc +test-video=3500000-1.m4s +#EXTINF:3, no desc +test-video=3500000-2.m4s +#EXTINF:3, no desc +test-video=3500000-3.m4s +#EXTINF:3, no desc +test-video=3500000-4.m4s +#EXTINF:3, no desc +test-video=3500000-5.m4s +#EXTINF:3, no desc +test-video=3500000-6.m4s +#EXTINF:3, no desc +test-video=3500000-7.m4s +#EXTINF:3, no desc +test-video=3500000-8.m4s +#EXTINF:3, no desc +test-video=3500000-9.m4s +#EXTINF:3, no desc +test-video=3500000-10.m4s +#EXTINF:3, no desc +test-video=3500000-11.m4s +#EXTINF:3, no desc +test-video=3500000-12.m4s +#EXTINF:3, no desc +test-video=3500000-13.m4s +#EXTINF:3, no desc +test-video=3500000-14.m4s +#EXTINF:3, no desc +test-video=3500000-15.m4s +#EXTINF:3, no desc +test-video=3500000-16.m4s +#EXTINF:3, no desc +test-video=3500000-17.m4s +#EXTINF:3, no desc +test-video=3500000-18.m4s +#EXTINF:3, no desc +test-video=3500000-19.m4s +#EXTINF:3, no desc +test-video=3500000-20.m4s +#EXTINF:3, no desc +test-video=3500000-21.m4s +#EXTINF:3, no desc +test-video=3500000-22.m4s +#EXTINF:3, no desc +test-video=3500000-23.m4s +#EXTINF:3, no desc +test-video=3500000-24.m4s +#EXTINF:3, no desc +test-video=3500000-25.m4s +#EXTINF:3, no desc +test-video=3500000-26.m4s +#EXTINF:3, no desc +test-video=3500000-27.m4s +#EXTINF:3, no desc +test-video=3500000-28.m4s +#EXTINF:3, no desc +test-video=3500000-29.m4s +#EXTINF:3, no desc +test-video=3500000-30.m4s +#EXTINF:3, no desc +test-video=3500000-31.m4s +#EXTINF:3, no desc +test-video=3500000-32.m4s +#EXTINF:3, no desc +test-video=3500000-33.m4s +#EXTINF:3, no desc +test-video=3500000-34.m4s +#EXTINF:3, no desc +test-video=3500000-35.m4s +#EXTINF:3, no desc +test-video=3500000-36.m4s +#EXTINF:3, no desc +test-video=3500000-37.m4s +#EXTINF:3, no desc +test-video=3500000-38.m4s +#EXTINF:3, no desc +test-video=3500000-39.m4s +#EXTINF:3, no desc +test-video=3500000-40.m4s +#EXTINF:3, no desc +test-video=3500000-41.m4s +#EXTINF:3, no desc +test-video=3500000-42.m4s +#EXTINF:3, no desc +test-video=3500000-43.m4s +#EXTINF:3, no desc +test-video=3500000-44.m4s +#EXTINF:3, no desc +test-video=3500000-45.m4s +#EXTINF:3, no desc +test-video=3500000-46.m4s +#EXTINF:3, no desc +test-video=3500000-47.m4s +#EXTINF:3, no desc +test-video=3500000-48.m4s +#EXTINF:3, no desc +test-video=3500000-49.m4s +#EXTINF:3, no desc +test-video=3500000-50.m4s +#EXTINF:3, no desc +test-video=3500000-51.m4s +#EXTINF:3, no desc +test-video=3500000-52.m4s +#EXTINF:3, no desc +test-video=3500000-53.m4s +#EXTINF:3, no desc +test-video=3500000-54.m4s +#EXTINF:3, no desc +test-video=3500000-55.m4s +#EXTINF:3, no desc +test-video=3500000-56.m4s +#EXTINF:3, no desc +test-video=3500000-57.m4s +#EXTINF:3, no desc +test-video=3500000-58.m4s +#EXTINF:2.16, no desc +test-video=3500000-59.m4s +#EXT-X-ENDLIST + diff --git a/testvectors/demux_n_cmaf/hls1/index_stereo-sv_a.m3u8 b/testvectors/demux_n_cmaf/hls1/index_stereo-sv_a.m3u8 new file mode 100644 index 0000000..59c6b47 --- /dev/null +++ b/testvectors/demux_n_cmaf/hls1/index_stereo-sv_a.m3u8 @@ -0,0 +1,194 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Created with Unified Streaming Platform (version=1.11.20-26889) +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MEDIA-SEQUENCE:1 +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-TARGETDURATION:2 +#USP-X-TIMESTAMP-MAP:MPEGTS=900000,LOCAL=1970-01-01T00:00:00Z +#EXT-X-MAP:URI="test-audio=256000.m4s" +#EXTINF:1.92, no desc +test-audio=256000-1.m4s +#EXTINF:1.92, no desc +test-audio=256000-2.m4s +#EXTINF:1.92, no desc +test-audio=256000-3.m4s +#EXTINF:1.92, no desc +test-audio=256000-4.m4s +#EXTINF:1.92, no desc +test-audio=256000-5.m4s +#EXTINF:1.92, no desc +test-audio=256000-6.m4s +#EXTINF:1.92, no desc +test-audio=256000-7.m4s +#EXTINF:1.92, no desc +test-audio=256000-8.m4s +#EXTINF:1.92, no desc +test-audio=256000-9.m4s +#EXTINF:1.92, no desc +test-audio=256000-10.m4s +#EXTINF:1.92, no desc +test-audio=256000-11.m4s +#EXTINF:1.92, no desc +test-audio=256000-12.m4s +#EXTINF:1.92, no desc +test-audio=256000-13.m4s +#EXTINF:1.92, no desc +test-audio=256000-14.m4s +#EXTINF:1.92, no desc +test-audio=256000-15.m4s +#EXTINF:1.92, no desc +test-audio=256000-16.m4s +#EXTINF:1.92, no desc +test-audio=256000-17.m4s +#EXTINF:1.92, no desc +test-audio=256000-18.m4s +#EXTINF:1.92, no desc +test-audio=256000-19.m4s +#EXTINF:1.92, no desc +test-audio=256000-20.m4s +#EXTINF:1.92, no desc +test-audio=256000-21.m4s +#EXTINF:1.92, no desc +test-audio=256000-22.m4s +#EXTINF:1.92, no desc +test-audio=256000-23.m4s +#EXTINF:1.92, no desc +test-audio=256000-24.m4s +#EXTINF:1.92, no desc +test-audio=256000-25.m4s +#EXTINF:1.92, no desc +test-audio=256000-26.m4s +#EXTINF:1.92, no desc +test-audio=256000-27.m4s +#EXTINF:1.92, no desc +test-audio=256000-28.m4s +#EXTINF:1.92, no desc +test-audio=256000-29.m4s +#EXTINF:1.92, no desc +test-audio=256000-30.m4s +#EXTINF:1.92, no desc +test-audio=256000-31.m4s +#EXTINF:1.92, no desc +test-audio=256000-32.m4s +#EXTINF:1.92, no desc +test-audio=256000-33.m4s +#EXTINF:1.92, no desc +test-audio=256000-34.m4s +#EXTINF:1.92, no desc +test-audio=256000-35.m4s +#EXTINF:1.92, no desc +test-audio=256000-36.m4s +#EXTINF:1.92, no desc +test-audio=256000-37.m4s +#EXTINF:1.92, no desc +test-audio=256000-38.m4s +#EXTINF:1.92, no desc +test-audio=256000-39.m4s +#EXTINF:1.92, no desc +test-audio=256000-40.m4s +#EXTINF:1.92, no desc +test-audio=256000-41.m4s +#EXTINF:1.92, no desc +test-audio=256000-42.m4s +#EXTINF:1.92, no desc +test-audio=256000-43.m4s +#EXTINF:1.92, no desc +test-audio=256000-44.m4s +#EXTINF:1.92, no desc +test-audio=256000-45.m4s +#EXTINF:1.92, no desc +test-audio=256000-46.m4s +#EXTINF:1.92, no desc +test-audio=256000-47.m4s +#EXTINF:1.92, no desc +test-audio=256000-48.m4s +#EXTINF:1.92, no desc +test-audio=256000-49.m4s +#EXTINF:1.92, no desc +test-audio=256000-50.m4s +#EXTINF:1.92, no desc +test-audio=256000-51.m4s +#EXTINF:1.92, no desc +test-audio=256000-52.m4s +#EXTINF:1.92, no desc +test-audio=256000-53.m4s +#EXTINF:1.92, no desc +test-audio=256000-54.m4s +#EXTINF:1.92, no desc +test-audio=256000-55.m4s +#EXTINF:1.92, no desc +test-audio=256000-56.m4s +#EXTINF:1.92, no desc +test-audio=256000-57.m4s +#EXTINF:1.92, no desc +test-audio=256000-58.m4s +#EXTINF:1.92, no desc +test-audio=256000-59.m4s +#EXTINF:1.92, no desc +test-audio=256000-60.m4s +#EXTINF:1.92, no desc +test-audio=256000-61.m4s +#EXTINF:1.92, no desc +test-audio=256000-62.m4s +#EXTINF:1.92, no desc +test-audio=256000-63.m4s +#EXTINF:1.92, no desc +test-audio=256000-64.m4s +#EXTINF:1.92, no desc +test-audio=256000-65.m4s +#EXTINF:1.92, no desc +test-audio=256000-66.m4s +#EXTINF:1.92, no desc +test-audio=256000-67.m4s +#EXTINF:1.92, no desc +test-audio=256000-68.m4s +#EXTINF:1.92, no desc +test-audio=256000-69.m4s +#EXTINF:1.92, no desc +test-audio=256000-70.m4s +#EXTINF:1.92, no desc +test-audio=256000-71.m4s +#EXTINF:1.92, no desc +test-audio=256000-72.m4s +#EXTINF:1.92, no desc +test-audio=256000-73.m4s +#EXTINF:1.92, no desc +test-audio=256000-74.m4s +#EXTINF:1.92, no desc +test-audio=256000-75.m4s +#EXTINF:1.92, no desc +test-audio=256000-76.m4s +#EXTINF:1.92, no desc +test-audio=256000-77.m4s +#EXTINF:1.92, no desc +test-audio=256000-78.m4s +#EXTINF:1.92, no desc +test-audio=256000-79.m4s +#EXTINF:1.92, no desc +test-audio=256000-80.m4s +#EXTINF:1.92, no desc +test-audio=256000-81.m4s +#EXTINF:1.92, no desc +test-audio=256000-82.m4s +#EXTINF:1.92, no desc +test-audio=256000-83.m4s +#EXTINF:1.92, no desc +test-audio=256000-84.m4s +#EXTINF:1.92, no desc +test-audio=256000-85.m4s +#EXTINF:1.92, no desc +test-audio=256000-86.m4s +#EXTINF:1.92, no desc +test-audio=256000-87.m4s +#EXTINF:1.92, no desc +test-audio=256000-88.m4s +#EXTINF:1.92, no desc +test-audio=256000-89.m4s +#EXTINF:1.92, no desc +test-audio=256000-90.m4s +#EXTINF:1.92, no desc +test-audio=256000-91.m4s +#EXTINF:1.4933, no desc +test-audio=256000-92.m4s +#EXT-X-ENDLIST diff --git a/testvectors/demux_n_cmaf/hls1/master.m3u8 b/testvectors/demux_n_cmaf/hls1/master.m3u8 new file mode 100644 index 0000000..95933f2 --- /dev/null +++ b/testvectors/demux_n_cmaf/hls1/master.m3u8 @@ -0,0 +1,6 @@ +#EXTM3U +#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=4497000,RESOLUTION=1280x720,CODECS="avc1.77.30, mp4a.40.2",CLOSED-CAPTIONS=NONE,AUDIO="mono" +index_0_v.m3u8 +#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2497000,RESOLUTION=1024x576,CODECS="avc1.77.30, mp4a.40.2",CLOSED-CAPTIONS=NONE,AUDIO="stereo" +index_1_v.m3u8 +#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="stereo",LANGUAGE="sv",NAME="Swedish",AUTOSELECT=YES,DEFAULT=YES,URI="index_stereo-sv_a.m3u8" diff --git a/testvectors/demux_n_cmaf/hls2_demux_cmaf/index_0_av.m3u8 b/testvectors/demux_n_cmaf/hls2_demux_cmaf/index_0_av.m3u8 new file mode 100644 index 0000000..b74e314 --- /dev/null +++ b/testvectors/demux_n_cmaf/hls2_demux_cmaf/index_0_av.m3u8 @@ -0,0 +1,28 @@ +#EXTM3U +#EXT-X-TARGETDURATION:9 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:9.000, +segment1_0_av.ts +#EXTINF:9.000, +segment2_0_av.ts +#EXTINF:9.000, +segment3_0_av.ts +#EXTINF:9.000, +segment4_0_av.ts +#EXTINF:9.000, +segment5_0_av.ts +#EXTINF:9.000, +segment6_0_av.ts +#EXTINF:9.000, +segment7_0_av.ts +#EXTINF:9.000, +segment8_0_av.ts +#EXTINF:9.000, +segment9_0_av.ts +#EXTINF:6.266, +segment10_0_av.ts +#EXT-X-ENDLIST diff --git a/testvectors/demux_n_cmaf/hls2_demux_cmaf/index_1_av.m3u8 b/testvectors/demux_n_cmaf/hls2_demux_cmaf/index_1_av.m3u8 new file mode 100644 index 0000000..a3fe245 --- /dev/null +++ b/testvectors/demux_n_cmaf/hls2_demux_cmaf/index_1_av.m3u8 @@ -0,0 +1,28 @@ +#EXTM3U +#EXT-X-TARGETDURATION:9 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-INDEPENDENT-SEGMENTS +##EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:9.000, +segment1_1_av.ts +#EXTINF:9.000, +segment2_1_av.ts +#EXTINF:9.000, +segment3_1_av.ts +#EXTINF:9.000, +segment4_1_av.ts +#EXTINF:9.000, +segment5_1_av.ts +#EXTINF:9.000, +segment6_1_av.ts +#EXTINF:9.000, +segment7_1_av.ts +#EXTINF:9.000, +segment8_1_av.ts +#EXTINF:9.000, +segment9_1_av.ts +#EXTINF:6.266, +segment10_1_av.ts +#EXT-X-ENDLIST diff --git a/testvectors/demux_n_cmaf/hls2_demux_cmaf/master.m3u8 b/testvectors/demux_n_cmaf/hls2_demux_cmaf/master.m3u8 new file mode 100644 index 0000000..b08acc5 --- /dev/null +++ b/testvectors/demux_n_cmaf/hls2_demux_cmaf/master.m3u8 @@ -0,0 +1,13 @@ +#EXTM3U + +# AUDIO groups +#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="mono",LANGUAGE="sv",NAME="Swedish",AUTOSELECT=YES,DEFAULT=YES,URI="audio_mono-sv.m3u8" +#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="mono",LANGUAGE="en",NAME="English",AUTOSELECT=NO,DEFAULT=NO,URI="audio_mono-en.m3u8" +#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="stereo",LANGUAGE="sv",NAME="Swedish",AUTOSELECT=YES,DEFAULT=YES,URI="audio_stereo-sv.m3u8" +#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="stereo",LANGUAGE="en",NAME="English",AUTOSELECT=NO,DEFAULT=NO,URI="audio_stereo-en.m3u8" + + +#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=4497000,RESOLUTION=1280x720,CODECS="avc1.77.30, mp4a.40.2",CLOSED-CAPTIONS=NONE,AUDIO="mono" +index_0_av.m3u8 +#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2497000,RESOLUTION=1024x576,CODECS="avc1.77.30, mp4a.40.2",CLOSED-CAPTIONS=NONE,AUDIO="stereo" +index_1_av.m3u8 diff --git a/testvectors/hls1/index_0_av.m3u8 b/testvectors/hls1/index_0_av.m3u8 index ee1a83f..b74e314 100644 --- a/testvectors/hls1/index_0_av.m3u8 +++ b/testvectors/hls1/index_0_av.m3u8 @@ -1,28 +1,28 @@ -#EXTM3U -#EXT-X-TARGETDURATION:9 -#EXT-X-ALLOW-CACHE:YES -#EXT-X-PLAYLIST-TYPE:VOD -#EXT-X-VERSION:3 -#EXT-X-INDEPENDENT-SEGMENTS -#EXT-X-MEDIA-SEQUENCE:1 -#EXTINF:9.000, -segment1_0_av.ts -#EXTINF:9.000, -segment2_0_av.ts -#EXTINF:9.000, -segment3_0_av.ts -#EXTINF:9.000, -segment4_0_av.ts -#EXTINF:9.000, -segment5_0_av.ts -#EXTINF:9.000, -segment6_0_av.ts -#EXTINF:9.000, -segment7_0_av.ts -#EXTINF:9.000, -segment8_0_av.ts -#EXTINF:9.000, -segment9_0_av.ts -#EXTINF:6.266, -segment10_0_av.ts -#EXT-X-ENDLIST +#EXTM3U +#EXT-X-TARGETDURATION:9 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:9.000, +segment1_0_av.ts +#EXTINF:9.000, +segment2_0_av.ts +#EXTINF:9.000, +segment3_0_av.ts +#EXTINF:9.000, +segment4_0_av.ts +#EXTINF:9.000, +segment5_0_av.ts +#EXTINF:9.000, +segment6_0_av.ts +#EXTINF:9.000, +segment7_0_av.ts +#EXTINF:9.000, +segment8_0_av.ts +#EXTINF:9.000, +segment9_0_av.ts +#EXTINF:6.266, +segment10_0_av.ts +#EXT-X-ENDLIST diff --git a/testvectors/hls1/index_1_av.m3u8 b/testvectors/hls1/index_1_av.m3u8 index c9bc9bb..a3fe245 100644 --- a/testvectors/hls1/index_1_av.m3u8 +++ b/testvectors/hls1/index_1_av.m3u8 @@ -1,28 +1,28 @@ -#EXTM3U -#EXT-X-TARGETDURATION:9 -#EXT-X-ALLOW-CACHE:YES -#EXT-X-PLAYLIST-TYPE:VOD -#EXT-X-VERSION:3 -#EXT-X-INDEPENDENT-SEGMENTS -##EXT-X-MEDIA-SEQUENCE:1 -#EXTINF:9.000, -segment1_1_av.ts -#EXTINF:9.000, -segment2_1_av.ts -#EXTINF:9.000, -segment3_1_av.ts -#EXTINF:9.000, -segment4_1_av.ts -#EXTINF:9.000, -segment5_1_av.ts -#EXTINF:9.000, -segment6_1_av.ts -#EXTINF:9.000, -segment7_1_av.ts -#EXTINF:9.000, -segment8_1_av.ts -#EXTINF:9.000, -segment9_1_av.ts -#EXTINF:6.266, -segment10_1_av.ts -#EXT-X-ENDLIST +#EXTM3U +#EXT-X-TARGETDURATION:9 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-INDEPENDENT-SEGMENTS +##EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:9.000, +segment1_1_av.ts +#EXTINF:9.000, +segment2_1_av.ts +#EXTINF:9.000, +segment3_1_av.ts +#EXTINF:9.000, +segment4_1_av.ts +#EXTINF:9.000, +segment5_1_av.ts +#EXTINF:9.000, +segment6_1_av.ts +#EXTINF:9.000, +segment7_1_av.ts +#EXTINF:9.000, +segment8_1_av.ts +#EXTINF:9.000, +segment9_1_av.ts +#EXTINF:6.266, +segment10_1_av.ts +#EXT-X-ENDLIST diff --git a/testvectors/hls1b/index_0_av.m3u8 b/testvectors/hls1b/index_0_av.m3u8 index d8817dd..7c9054d 100644 --- a/testvectors/hls1b/index_0_av.m3u8 +++ b/testvectors/hls1b/index_0_av.m3u8 @@ -1,28 +1,28 @@ -#EXTM3U -#EXT-X-TARGETDURATION:3 -#EXT-X-ALLOW-CACHE:YES -#EXT-X-PLAYLIST-TYPE:VOD -#EXT-X-VERSION:3 -#EXT-X-INDEPENDENT-SEGMENTS -#EXT-X-MEDIA-SEQUENCE:1 -#EXTINF:3.000, -segment1_0_av.ts -#EXTINF:3.000, -segment2_0_av.ts -#EXTINF:3.000, -segment3_0_av.ts -#EXTINF:3.000, -segment4_0_av.ts -#EXTINF:3.000, -segment5_0_av.ts -#EXTINF:3.000, -segment6_0_av.ts -#EXTINF:3.000, -segment7_0_av.ts -#EXTINF:3.000, -segment8_0_av.ts -#EXTINF:3.000, -segment9_0_av.ts -#EXTINF:2.266, -segment10_0_av.ts -#EXT-X-ENDLIST +#EXTM3U +#EXT-X-TARGETDURATION:3 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:3.000, +segment1_0_av.ts +#EXTINF:3.000, +segment2_0_av.ts +#EXTINF:3.000, +segment3_0_av.ts +#EXTINF:3.000, +segment4_0_av.ts +#EXTINF:3.000, +segment5_0_av.ts +#EXTINF:3.000, +segment6_0_av.ts +#EXTINF:3.000, +segment7_0_av.ts +#EXTINF:3.000, +segment8_0_av.ts +#EXTINF:3.000, +segment9_0_av.ts +#EXTINF:2.266, +segment10_0_av.ts +#EXT-X-ENDLIST diff --git a/testvectors/hls1b/index_1_av.m3u8 b/testvectors/hls1b/index_1_av.m3u8 index 35c608b..19e4067 100644 --- a/testvectors/hls1b/index_1_av.m3u8 +++ b/testvectors/hls1b/index_1_av.m3u8 @@ -1,28 +1,28 @@ -#EXTM3U -#EXT-X-TARGETDURATION:3 -#EXT-X-ALLOW-CACHE:YES -#EXT-X-PLAYLIST-TYPE:VOD -#EXT-X-VERSION:3 -#EXT-X-INDEPENDENT-SEGMENTS -##EXT-X-MEDIA-SEQUENCE:1 -#EXTINF:3.000, -segment1_1_av.ts -#EXTINF:3.000, -segment2_1_av.ts -#EXTINF:3.000, -segment3_1_av.ts -#EXTINF:3.000, -segment4_1_av.ts -#EXTINF:3.000, -segment5_1_av.ts -#EXTINF:3.000, -segment6_1_av.ts -#EXTINF:3.000, -segment7_1_av.ts -#EXTINF:3.000, -segment8_1_av.ts -#EXTINF:3.000, -segment9_1_av.ts -#EXTINF:2.266, -segment10_1_av.ts -#EXT-X-ENDLIST +#EXTM3U +#EXT-X-TARGETDURATION:3 +#EXT-X-ALLOW-CACHE:YES +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-VERSION:3 +#EXT-X-INDEPENDENT-SEGMENTS +##EXT-X-MEDIA-SEQUENCE:1 +#EXTINF:3.000, +segment1_1_av.ts +#EXTINF:3.000, +segment2_1_av.ts +#EXTINF:3.000, +segment3_1_av.ts +#EXTINF:3.000, +segment4_1_av.ts +#EXTINF:3.000, +segment5_1_av.ts +#EXTINF:3.000, +segment6_1_av.ts +#EXTINF:3.000, +segment7_1_av.ts +#EXTINF:3.000, +segment8_1_av.ts +#EXTINF:3.000, +segment9_1_av.ts +#EXTINF:2.266, +segment10_1_av.ts +#EXT-X-ENDLIST diff --git a/testvectors/hls1b/master.m3u8 b/testvectors/hls1b/master.m3u8 index 157ac3a..8bee973 100644 --- a/testvectors/hls1b/master.m3u8 +++ b/testvectors/hls1b/master.m3u8 @@ -1,5 +1,5 @@ -#EXTM3U -#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=4497000,RESOLUTION=1280x720,CODECS="avc1.77.30, mp4a.40.2",CLOSED-CAPTIONS=NONE -index_0_av.m3u8 -#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2497000,RESOLUTION=1024x576,CODECS="avc1.77.30, mp4a.40.2",CLOSED-CAPTIONS=NONE -index_1_av.m3u8 +#EXTM3U +#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=4497000,RESOLUTION=1280x720,CODECS="avc1.77.30, mp4a.40.2",CLOSED-CAPTIONS=NONE +index_0_av.m3u8 +#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2497000,RESOLUTION=1024x576,CODECS="avc1.77.30, mp4a.40.2",CLOSED-CAPTIONS=NONE +index_1_av.m3u8 From 19fce82e2f68d18ba67828d6fea01509f1cb223c Mon Sep 17 00:00:00 2001 From: Nfrederiksen Date: Wed, 15 Feb 2023 17:19:18 +0100 Subject: [PATCH 2/5] tests: add for cmaf --- index.js | 2 +- spec/hls_splice_spec.js | 100 +++++++++++++++--- testvectors/demux_n_cmaf/ad4/index_0_v.m3u8 | 13 +++ testvectors/demux_n_cmaf/ad4/index_1_v.m3u8 | 12 +++ .../demux_n_cmaf/ad4/index_stereo-sv_a.m3u8 | 14 +++ testvectors/demux_n_cmaf/ad4/master.m3u8 | 6 ++ 6 files changed, 131 insertions(+), 16 deletions(-) create mode 100644 testvectors/demux_n_cmaf/ad4/index_0_v.m3u8 create mode 100644 testvectors/demux_n_cmaf/ad4/index_1_v.m3u8 create mode 100644 testvectors/demux_n_cmaf/ad4/index_stereo-sv_a.m3u8 create mode 100644 testvectors/demux_n_cmaf/ad4/master.m3u8 diff --git a/index.js b/index.js index f51439e..3d81f33 100644 --- a/index.js +++ b/index.js @@ -251,7 +251,7 @@ class HLSSpliceVod { if (!isPostRoll) { playlist.items.PlaylistItem[idx + adLength].set("discontinuity", true); } - if (this.cmafMapUri.audio[g][l]) { + if (this.cmafMapUri.audio[g] && this.cmafMapUri.audio[g][l]) { playlist.items.PlaylistItem[idx + adLength].set("map-uri", this.cmafMapUri.audio[g][l]); } } else { diff --git a/spec/hls_splice_spec.js b/spec/hls_splice_spec.js index a799fcf..607c61f 100644 --- a/spec/hls_splice_spec.js +++ b/spec/hls_splice_spec.js @@ -492,7 +492,7 @@ describe("HLSSpliceVod", () => { const m3u8 = mockVod.getMediaManifest(4497000); const lines = m3u8.split("\n"); expect(lines[12]).toEqual( - '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",X-ASSET-URI="http://mock.com/asseturi",X-RESUME-OFFSET="10.5"' + '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",X-ASSET-URI="http://mock.com/asseturi",X-RESUME-OFFSET=10.5' ); done(); }); @@ -511,7 +511,7 @@ describe("HLSSpliceVod", () => { const m3u8 = mockVod.getMediaManifest(4497000); const lines = m3u8.split("\n"); expect(lines[12]).toEqual( - '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",X-ASSET-URI="http://mock.com/asseturi",X-RESUME-OFFSET="0"' + '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",X-ASSET-URI="http://mock.com/asseturi",X-RESUME-OFFSET=0' ); done(); }); @@ -530,7 +530,7 @@ describe("HLSSpliceVod", () => { const m3u8 = mockVod.getMediaManifest(4497000); const lines = m3u8.split("\n"); expect(lines[12]).toEqual( - '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",X-ASSET-URI="http://mock.com/asseturi",X-PLAYOUT-LIMIT="12.5"' + '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",X-ASSET-URI="http://mock.com/asseturi",X-PLAYOUT-LIMIT=12.5' ); done(); }); @@ -587,7 +587,7 @@ describe("HLSSpliceVod", () => { const m3u8 = mockVod.getMediaManifest(4497000); const lines = m3u8.split("\n"); expect(lines[12]).toEqual( - '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",DURATION="30",X-ASSET-LIST="http://mock.com/asseturi"' + '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",DURATION=30,X-ASSET-LIST="http://mock.com/asseturi"' ); done(); }); @@ -922,7 +922,7 @@ describe("HLSSpliceVod with Demuxed Audio Tracks,", () => { .load(mockMasterManifest, mockMediaManifest, mockAudioManifest) .then(() => { return mockVod.insertAdAt( - 9, + 9000, "http://mock.com/ad/mockad.m3u8", mockAdMasterManifest, mockAdMediaManifest, @@ -1361,12 +1361,12 @@ describe("HLSSpliceVod with Demuxed Audio Tracks,", () => { const m3u8 = mockVod.getMediaManifest(4497000); let lines = m3u8.split("\n"); expect(lines[12]).toEqual( - '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",X-ASSET-URI="http://mock.com/asseturi",X-RESUME-OFFSET="10.5"' + '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",X-ASSET-URI="http://mock.com/asseturi",X-RESUME-OFFSET=10.5' ); const m3u8Audio = mockVod.getAudioManifest("stereo", "sv"); lines = m3u8Audio.split("\n"); expect(lines[12]).toEqual( - '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",X-ASSET-URI="http://mock.com/asseturi",X-RESUME-OFFSET="10.5"' + '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",X-ASSET-URI="http://mock.com/asseturi",X-RESUME-OFFSET=10.5' ); done(); }); @@ -1385,12 +1385,12 @@ describe("HLSSpliceVod with Demuxed Audio Tracks,", () => { const m3u8 = mockVod.getMediaManifest(4497000); let lines = m3u8.split("\n"); expect(lines[12]).toEqual( - '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",X-ASSET-URI="http://mock.com/asseturi",X-RESUME-OFFSET="0"' + '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",X-ASSET-URI="http://mock.com/asseturi",X-RESUME-OFFSET=0' ); const m3u8Audio = mockVod.getAudioManifest("stereo", "sv"); lines = m3u8Audio.split("\n"); expect(lines[12]).toEqual( - '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",X-ASSET-URI="http://mock.com/asseturi",X-RESUME-OFFSET="0"' + '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",X-ASSET-URI="http://mock.com/asseturi",X-RESUME-OFFSET=0' ); done(); }); @@ -1409,12 +1409,12 @@ describe("HLSSpliceVod with Demuxed Audio Tracks,", () => { const m3u8 = mockVod.getMediaManifest(4497000); let lines = m3u8.split("\n"); expect(lines[12]).toEqual( - '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",X-ASSET-URI="http://mock.com/asseturi",X-PLAYOUT-LIMIT="12.5"' + '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",X-ASSET-URI="http://mock.com/asseturi",X-PLAYOUT-LIMIT=12.5' ); const m3u8Audio = mockVod.getAudioManifest("stereo", "sv"); lines = m3u8Audio.split("\n"); expect(lines[12]).toEqual( - '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",X-ASSET-URI="http://mock.com/asseturi",X-PLAYOUT-LIMIT="12.5"' + '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",X-ASSET-URI="http://mock.com/asseturi",X-PLAYOUT-LIMIT=12.5' ); done(); }); @@ -1481,12 +1481,12 @@ describe("HLSSpliceVod with Demuxed Audio Tracks,", () => { const m3u8 = mockVod.getMediaManifest(4497000); let lines = m3u8.split("\n"); expect(lines[12]).toEqual( - '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",DURATION="30",X-ASSET-LIST="http://mock.com/asseturi"' + '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",DURATION=30,X-ASSET-LIST="http://mock.com/asseturi"' ); const m3u8Audio = mockVod.getAudioManifest("stereo", "sv"); lines = m3u8Audio.split("\n"); expect(lines[12]).toEqual( - '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",DURATION="30",X-ASSET-LIST="http://mock.com/asseturi"' + '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",DURATION=30,X-ASSET-LIST="http://mock.com/asseturi"' ); done(); }); @@ -1547,6 +1547,20 @@ describe("HLSSpliceVod with CMAF and Demuxed Audio Tracks,", () => { mockCmafAdAudioManifest3 = (g, l) => { return fs.createReadStream(`testvectors/demux_n_cmaf/ad3/index_${g}-${l}_a.m3u8`); }; + // MOCK VOD #10 TEST + mockCmafAdMasterManifest4 = () => { + return fs.createReadStream("testvectors/demux_n_cmaf/ad4/master.m3u8"); + }; + mockCmafAdMediaManifest4 = (bw) => { + const bwmap = { + 4545000: "0", + 2525000: "1", + }; + return fs.createReadStream(`testvectors/demux_n_cmaf/ad4/index_${bwmap[bw]}_v.m3u8`); + }; + mockCmafAdAudioManifest4 = (g, l) => { + return fs.createReadStream(`testvectors/demux_n_cmaf/ad4/index_${g}-${l}_a.m3u8`); + }; }); it("contains a 15 second splice at 9 seconds from start", (done) => { @@ -1604,7 +1618,7 @@ test-audio=256000-4.m4s test-audio=256000-5.m4s #EXT-X-MAP:URI="mock-ad-audio=256000.m4s" #EXT-X-DISCONTINUITY -#EXT-X-CUE-OUT:DURATION=14.16 +#EXT-X-CUE-OUT:DURATION=14.9333 #EXTINF:1.9200, no desc http://mock.com/ad/mock-ad-audio=256000-1.m4s #EXTINF:1.9200, no desc @@ -1620,7 +1634,7 @@ http://mock.com/ad/mock-ad-audio=256000-6.m4s #EXTINF:1.9200, no desc http://mock.com/ad/mock-ad-audio=256000-7.m4s #EXTINF:1.4933, no desc -http://mock.com/ad/mock-ad-audio=256000-92.m4s +http://mock.com/ad/mock-ad-audio=256000-8.m4s #EXT-X-MAP:URI="http://mock.com/test-audio=256000.m4s" #EXT-X-DISCONTINUITY #EXT-X-CUE-IN @@ -1781,4 +1795,60 @@ test-audio=256000-6.m4s`; done(); }); }); + + it("handles target duration for video bumper and two ads in a row merged into one break", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8", { merge: true }); + mockVod + .load(mockCmafMasterManifest, mockCmafMediaManifest, mockCmafAudioManifest) + .then(() => { + return mockVod.insertAdAt( + 0, + "http://mock.com/ad/mockad.m3u8", + mockCmafAdMasterManifest, + mockCmafAdMediaManifest, + mockCmafAdAudioManifest + ); + }) + .then(() => { + return mockVod.insertAdAt( + 0, + "http://mock.com/ad/mockad.m3u8", + mockCmafAdMasterManifest3, + mockCmafAdMediaManifest3, + mockCmafAdAudioManifest3 + ); + }) + .then(() => { + // This one will go first + return mockVod.insertAdAt( + 0, + "http://mock.com/ad/mockad.m3u8", + mockCmafAdMasterManifest4, + mockCmafAdMediaManifest4, + mockCmafAdAudioManifest4 + ); + }) + .then(() => { + return mockVod.insertBumper( + "http://mock.com/ad/mockbumper.m3u8", + mockCmafAdMasterManifest, + mockCmafAdMediaManifest, + mockCmafAdAudioManifest + ); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + let lines = m3u8.split("\n"); + expect(lines[6]).toEqual("#EXT-X-TARGETDURATION:5"); + expect(lines[21]).toEqual("#EXT-X-CUE-OUT:DURATION=22.16"); + expect(lines[lines.length - 2]).toEqual("#EXT-X-ENDLIST"); + const m3u8Audio = mockVod.getAudioManifest("stereo", "sv"); + lines = m3u8Audio.split("\n"); + lines.map((l, i) => console.log(l, i)); + expect(lines[6]).toEqual("#EXT-X-TARGETDURATION:4"); + expect(lines[27]).toEqual("#EXT-X-CUE-OUT:DURATION=23.7599"); + expect(lines[lines.length - 2]).toEqual("#EXT-X-ENDLIST"); + done(); + }); + }); }); diff --git a/testvectors/demux_n_cmaf/ad4/index_0_v.m3u8 b/testvectors/demux_n_cmaf/ad4/index_0_v.m3u8 new file mode 100644 index 0000000..92f25d9 --- /dev/null +++ b/testvectors/demux_n_cmaf/ad4/index_0_v.m3u8 @@ -0,0 +1,13 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Created with Unified Streaming Platform (version=1.11.20-26889) +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MEDIA-SEQUENCE:1 +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-TARGETDURATION:5 +#USP-X-TIMESTAMP-MAP:MPEGTS=900000,LOCAL=1970-01-01T00:00:00Z +#EXT-X-MAP:URI="mock-bumper-video=2525000.m4s" +#EXTINF:5, no desc +mock-bumper-video=2525000-1.m4s +#EXT-X-ENDLIST + diff --git a/testvectors/demux_n_cmaf/ad4/index_1_v.m3u8 b/testvectors/demux_n_cmaf/ad4/index_1_v.m3u8 new file mode 100644 index 0000000..024cf08 --- /dev/null +++ b/testvectors/demux_n_cmaf/ad4/index_1_v.m3u8 @@ -0,0 +1,12 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Created with Unified Streaming Platform (version=1.11.20-26889) +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MEDIA-SEQUENCE:1 +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-TARGETDURATION:5 +#USP-X-TIMESTAMP-MAP:MPEGTS=900000,LOCAL=1970-01-01T00:00:00Z +#EXT-X-MAP:URI="mock-bumper-video=4545000.m4s" +#EXTINF:5, no desc +mock-bumper-video=4545000-1.m4s +#EXT-X-ENDLIST \ No newline at end of file diff --git a/testvectors/demux_n_cmaf/ad4/index_stereo-sv_a.m3u8 b/testvectors/demux_n_cmaf/ad4/index_stereo-sv_a.m3u8 new file mode 100644 index 0000000..54e9a34 --- /dev/null +++ b/testvectors/demux_n_cmaf/ad4/index_stereo-sv_a.m3u8 @@ -0,0 +1,14 @@ +#EXTM3U +#EXT-X-VERSION:6 +## Created with Unified Streaming Platform (version=1.11.20-26889) +#EXT-X-PLAYLIST-TYPE:VOD +#EXT-X-MEDIA-SEQUENCE:1 +#EXT-X-INDEPENDENT-SEGMENTS +#EXT-X-TARGETDURATION:4 +#USP-X-TIMESTAMP-MAP:MPEGTS=900000,LOCAL=1970-01-01T00:00:00Z +#EXT-X-MAP:URI="mock-bumper-audio=256000.m4s" +#EXTINF:3.92, no desc +mock-bumper-audio=256000-1.m4s +#EXTINF:1.4933, no desc +mock-bumper-audio=256000-2.m4s +#EXT-X-ENDLIST diff --git a/testvectors/demux_n_cmaf/ad4/master.m3u8 b/testvectors/demux_n_cmaf/ad4/master.m3u8 new file mode 100644 index 0000000..8ab4f77 --- /dev/null +++ b/testvectors/demux_n_cmaf/ad4/master.m3u8 @@ -0,0 +1,6 @@ +#EXTM3U +#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=4545000,RESOLUTION=1280x720,CODECS="avc1.77.30, mp4a.40.2",CLOSED-CAPTIONS=NONE +index_0_av.m3u8 +#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2525000,RESOLUTION=1024x576,CODECS="avc1.77.30, mp4a.40.2",CLOSED-CAPTIONS=NONE +index_1_av.m3u8 +#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="stereo",LANGUAGE="sv",NAME="Swedish",AUTOSELECT=YES,DEFAULT=YES,URI="index_stereo-sv_a.m3u8" From 294d5fedc75f2d672fb9066a3dd6c1c4dfe25b6b Mon Sep 17 00:00:00 2001 From: Nicholas Frederiksen Date: Thu, 16 Feb 2023 11:20:57 +0100 Subject: [PATCH 3/5] test: add some cmaf demux interstitial specs --- spec/hls_splice_spec.js | 49 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/spec/hls_splice_spec.js b/spec/hls_splice_spec.js index 607c61f..49c6620 100644 --- a/spec/hls_splice_spec.js +++ b/spec/hls_splice_spec.js @@ -1785,7 +1785,6 @@ test-audio=256000-6.m4s`; .then(() => { const m3u8 = mockVod.getMediaManifest(4497000); const lines = m3u8.split("\n"); - lines.map((l, i) => console.log(l, i)); expect(lines[lines.length - 3]).toEqual("#EXT-X-CUE-IN"); expect(lines[lines.length - 2]).toEqual("#EXT-X-ENDLIST"); const m3u8Audio = mockVod.getAudioManifest("stereo", "sv"); @@ -1844,11 +1843,57 @@ test-audio=256000-6.m4s`; expect(lines[lines.length - 2]).toEqual("#EXT-X-ENDLIST"); const m3u8Audio = mockVod.getAudioManifest("stereo", "sv"); lines = m3u8Audio.split("\n"); - lines.map((l, i) => console.log(l, i)); expect(lines[6]).toEqual("#EXT-X-TARGETDURATION:4"); expect(lines[27]).toEqual("#EXT-X-CUE-OUT:DURATION=23.7599"); expect(lines[lines.length - 2]).toEqual("#EXT-X-ENDLIST"); done(); }); }); + + it("can insert interstitial with an assetlist", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8"); + mockVod + .load(mockCmafMasterManifest, mockCmafMediaManifest, mockCmafAudioManifest) + .then(() => { + return mockVod.insertInterstitialAt(16000, "001", "http://mock.com/assetlist", true); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + let lines = m3u8.split("\n"); + expect(lines[22]).toEqual( + '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:16.001Z",X-ASSET-LIST="http://mock.com/assetlist"' + ); + const m3u8Audio = mockVod.getAudioManifest("stereo", "sv"); + lines = m3u8Audio.split("\n"); + //lines.map((l, i) => console.log(l, i)); + expect(lines[28]).toEqual( + '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:16.001Z",X-ASSET-LIST="http://mock.com/assetlist"' + ); + done(); + }); + }); + + it("can insert interstitial with an assetlist uri and a planned duration", (done) => { + const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8"); + mockVod + .load(mockCmafMasterManifest, mockCmafMediaManifest, mockCmafAudioManifest) + .then(() => { + return mockVod.insertInterstitialAt(18000, "001", "http://mock.com/asseturi", true, { + plannedDuration: 30000, + }); + }) + .then(() => { + const m3u8 = mockVod.getMediaManifest(4497000); + let lines = m3u8.split("\n"); + expect(lines[22]).toEqual( + '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",DURATION=30,X-ASSET-LIST="http://mock.com/asseturi"' + ); + const m3u8Audio = mockVod.getAudioManifest("stereo", "sv"); + lines = m3u8Audio.split("\n"); + expect(lines[30]).toEqual( + '#EXT-X-DATERANGE:ID="001",CLASS="com.apple.hls.interstitial",START-DATE="1970-01-01T00:00:18.001Z",DURATION=30,X-ASSET-LIST="http://mock.com/asseturi"' + ); + done(); + }); + }); }); From 79c7c60268d0d230909361386c90d5404de01657 Mon Sep 17 00:00:00 2001 From: Nicholas Frederiksen Date: Fri, 17 Feb 2023 12:17:39 +0100 Subject: [PATCH 4/5] fix cmaf MAP uri not matching --- index.js | 60 ++++++++++++++++++++++++++--------------- spec/hls_splice_spec.js | 48 ++++++++++++++------------------- 2 files changed, 59 insertions(+), 49 deletions(-) diff --git a/index.js b/index.js index 3d81f33..6e6b58c 100644 --- a/index.js +++ b/index.js @@ -1,7 +1,7 @@ const m3u8 = require("@eyevinn/m3u8"); const request = require("request"); const url = require("url"); -const _log = (s, i = 0) => console.log(JSON.stringify(s, null, 2), 2002 + i); +//const _log = (s, i = 0) => console.log(JSON.stringify(s, null, 2), 2002 + i); /* const findNearestBw = (bw, array) => { // TO BE IMPLEMENTED @@ -121,7 +121,7 @@ class HLSSpliceVod { this._loadAudioManifest( audioManifestUrl, audioItem.get("group-id"), - audioItem.get("language"), + audioItem.get("language") ? audioItem.get("language") : audioItem.get("name"), _injectAudioManifest ) ); @@ -153,6 +153,7 @@ class HLSSpliceVod { const startOffset = offset; const isPostRoll = offset == -1; const bandwidths = Object.keys(this.playlists); + let closestCmafMapUri = ""; if (isPostRoll) { let duration = 0; @@ -168,12 +169,16 @@ class HLSSpliceVod { for (let b = 0; b < bandwidths.length; b++) { const bw = bandwidths[b]; - const adPlaylist = ad.playlist[findNearestBw(bw, Object.keys(ad.playlist))]; let pos = 0; let i = 0; + closestCmafMapUri = this._getCmafMapUri(this.playlists[bw], this.masterManifestUri, 1); + while (pos < offset && i < this.playlists[bw].items.PlaylistItem.length) { const plItem = this.playlists[bw].items.PlaylistItem[i]; + if (plItem.attributes.attributes["map-uri"]) { + closestCmafMapUri = this._getCmafMapUri(this.playlists[bw], this.masterManifestUri, 1, i); + } pos += plItem.get("duration") * 1000; i++; } @@ -182,9 +187,10 @@ class HLSSpliceVod { insertCueIn = true; } const adLength = adPlaylist.items.PlaylistItem.length; - for (let j = 0; j < adLength; j++) { - this.playlists[bw].items.PlaylistItem.splice(i + j, 0, adPlaylist.items.PlaylistItem[j]); - } + if (this.playlists[bw].items.PlaylistItem[0]) + for (let j = 0; j < adLength; j++) { + this.playlists[bw].items.PlaylistItem.splice(i + j, 0, adPlaylist.items.PlaylistItem[j]); + } this.playlists[bw].items.PlaylistItem[i].set("discontinuity", true); this.playlists[bw].items.PlaylistItem[i].set("cueout", ad.duration); if (insertCueIn) { @@ -195,8 +201,8 @@ class HLSSpliceVod { if (!isPostRoll) { this.playlists[bw].items.PlaylistItem[i + adLength].set("discontinuity", true); } - if (this.cmafMapUri.video[bw]) { - this.playlists[bw].items.PlaylistItem[i + adLength].set("map-uri", this.cmafMapUri.video[bw]); + if (closestCmafMapUri) { + this.playlists[bw].items.PlaylistItem[i + adLength].set("map-uri", closestCmafMapUri); } } else { this.playlists[bw].addPlaylistItem({ cuein: true }); @@ -227,8 +233,13 @@ class HLSSpliceVod { const adPlaylist = ad.playlistAudio[nearestGroup][nearestLang]; let pos = 0; let idx = 0; + closestCmafMapUri = this._getCmafMapUri(playlist, this.masterManifestUri, 1); + while (pos < offset && idx < playlist.items.PlaylistItem.length) { const plItem = playlist.items.PlaylistItem[idx]; + if (plItem.attributes.attributes["map-uri"]) { + closestCmafMapUri = this._getCmafMapUri(playlist, this.masterManifestUri, 1, i); + } pos += plItem.get("duration") * 1000; idx++; } @@ -251,8 +262,8 @@ class HLSSpliceVod { if (!isPostRoll) { playlist.items.PlaylistItem[idx + adLength].set("discontinuity", true); } - if (this.cmafMapUri.audio[g] && this.cmafMapUri.audio[g][l]) { - playlist.items.PlaylistItem[idx + adLength].set("map-uri", this.cmafMapUri.audio[g][l]); + if (closestCmafMapUri) { + playlist.items.PlaylistItem[idx + adLength].set("map-uri", closestCmafMapUri); } } else { playlist.addPlaylistItem({ cuein: true }); @@ -261,8 +272,6 @@ class HLSSpliceVod { } } } - - //console.log(this.playlists[bandwidths[0]].toString()); resolve(); }) .catch(reject); @@ -324,7 +333,6 @@ class HLSSpliceVod { const lang = langs[j]; let pos = 0; let idx = 0; - // todo this.playlistsAudio[group][lang].items.PlaylistItem[0].set("date", new Date(1)); while (pos < offset && idx < this.playlistsAudio[group][lang].items.PlaylistItem.length) { const plItem = this.playlistsAudio[group][lang].items.PlaylistItem[idx]; @@ -502,10 +510,11 @@ class HLSSpliceVod { this.targetDuration = targetDuration; } this.playlists[bandwidth].set("targetDuration", this.targetDuration); - const initSegUri = this._getCmafMapUri(m3u, mediaManifestUri); + const initSegUri = this._getCmafMapUri(m3u, mediaManifestUri, this.baseUrl); if (initSegUri) { if (!this.cmafMapUri.video[bandwidth]) { - this.cmafMapUri.video[bandwidth] = initSegUri; + this.cmafMapUri.video[bandwidth] = + this.baseUrl && !initSegUri.includes("http") ? this.baseUrl + initSegUri : initSegUri; } } resolve(); @@ -548,12 +557,13 @@ class HLSSpliceVod { this.targetDurationAudio = targetDuration; } this.playlistsAudio[group][lang].set("targetDuration", this.targetDurationAudio); - const initSegUri = this._getCmafMapUri(m3u, audioManifestUri); + const initSegUri = this._getCmafMapUri(m3u, audioManifestUri, this.baseUrl); if (initSegUri) { if (!this.cmafMapUri.audio[group]) { this.cmafMapUri.audio[group] = {}; } - this.cmafMapUri.audio[group][lang] = initSegUri; + this.cmafMapUri.audio[group][lang] = + this.baseUrl && !initSegUri.includes("http") ? this.baseUrl + initSegUri : initSegUri; } resolve(); }); @@ -606,6 +616,10 @@ class HLSSpliceVod { if (!plUri.match("^http")) { plItem.set("uri", url.resolve(baseUrl, plUri)); } + const plMapUri = plItem.attributes.attributes["map-uri"]; + if (plMapUri && !plMapUri.match("^http")) { + plItem.set("map-uri", url.resolve(baseUrl, plMapUri)); + } ad.duration += plItem.get("duration"); } ad.playlist[streamItem.get("bandwidth")] = m3u; @@ -658,6 +672,10 @@ class HLSSpliceVod { if (!plUri.match("^http")) { plItem.set("uri", url.resolve(baseUrl, plUri)); } + const plMapUri = plItem.attributes.attributes["map-uri"]; + if (plMapUri && !plMapUri.match("^http")) { + plItem.set("map-uri", url.resolve(baseUrl, plMapUri)); + } ad.durationAudio += plItem.get("duration"); } @@ -714,11 +732,11 @@ class HLSSpliceVod { }); } - _getCmafMapUri(m3u, manifestUri) { + _getCmafMapUri(m3u, manifestUri, useAbsUrl, index = 0) { let initSegment = undefined; - if (m3u.items.PlaylistItem[0].attributes.attributes["map-uri"]) { - initSegment = m3u.items.PlaylistItem[0].attributes.attributes["map-uri"]; - if (!initSegment.match("^http")) { + if (m3u.items.PlaylistItem[index].attributes.attributes["map-uri"]) { + initSegment = m3u.items.PlaylistItem[index].attributes.attributes["map-uri"]; + if (!initSegment.match("^http") && useAbsUrl) { const n = manifestUri.match("^(.*)/.*?$"); if (n) { initSegment = url.resolve(n[1] + "/", initSegment); diff --git a/spec/hls_splice_spec.js b/spec/hls_splice_spec.js index 49c6620..fba848d 100644 --- a/spec/hls_splice_spec.js +++ b/spec/hls_splice_spec.js @@ -1494,15 +1494,6 @@ describe("HLSSpliceVod with Demuxed Audio Tracks,", () => { }); describe("HLSSpliceVod with CMAF and Demuxed Audio Tracks,", () => { - let mockMasterManifest; - let mockMediaManifest; - let mockAudioManifest; - let mockAdMasterManifest; - let mockAdMediaManifest; - let mockAdAudioManifest; - let mockAdMasterManifest2; - let mockAdMediaManifest2; - let mockAdAudioManifest2; const _log = (s) => console.log(JSON.stringify(s, null, 2)); beforeEach(() => { // MOCK VOD #8 TEST @@ -1587,7 +1578,7 @@ test-video=2500000-1.m4s test-video=2500000-2.m4s #EXTINF:3.0000, no desc test-video=2500000-3.m4s -#EXT-X-MAP:URI="mock-ad-video=2525000.m4s" +#EXT-X-MAP:URI="http://mock.com/ad/mock-ad-video=2525000.m4s" #EXT-X-DISCONTINUITY #EXT-X-CUE-OUT:DURATION=14.16 #EXTINF:3.0000, no desc @@ -1600,7 +1591,7 @@ http://mock.com/ad/mock-ad-video=2525000-3.m4s http://mock.com/ad/mock-ad-video=2525000-4.m4s #EXTINF:2.1600, no desc http://mock.com/ad/mock-ad-video=2525000-5.m4s -#EXT-X-MAP:URI="http://mock.com/test-video=2500000.m4s" +#EXT-X-MAP:URI="test-video=2500000.m4s" #EXT-X-DISCONTINUITY #EXT-X-CUE-IN #EXTINF:3.0000, no desc @@ -1616,7 +1607,7 @@ test-audio=256000-3.m4s test-audio=256000-4.m4s #EXTINF:1.9200, no desc test-audio=256000-5.m4s -#EXT-X-MAP:URI="mock-ad-audio=256000.m4s" +#EXT-X-MAP:URI="http://mock.com/ad/mock-ad-audio=256000.m4s" #EXT-X-DISCONTINUITY #EXT-X-CUE-OUT:DURATION=14.9333 #EXTINF:1.9200, no desc @@ -1635,7 +1626,7 @@ http://mock.com/ad/mock-ad-audio=256000-6.m4s http://mock.com/ad/mock-ad-audio=256000-7.m4s #EXTINF:1.4933, no desc http://mock.com/ad/mock-ad-audio=256000-8.m4s -#EXT-X-MAP:URI="http://mock.com/test-audio=256000.m4s" +#EXT-X-MAP:URI="test-audio=256000.m4s" #EXT-X-DISCONTINUITY #EXT-X-CUE-IN #EXTINF:1.9200, no desc @@ -1672,28 +1663,28 @@ test-audio=256000-6.m4s`; .then(() => { const m3u8 = mockVod.getMediaManifest(4497000); let lines = m3u8.split("\n"); - expect(lines[8]).toEqual(`#EXT-X-MAP:URI="mock-ad3-video=2525000.m4s"`); + expect(lines[8]).toEqual(`#EXT-X-MAP:URI="http://mock.com/ad/mock-ad3-video=2525000.m4s"`); expect(lines[9]).toEqual(`#EXT-X-DISCONTINUITY`); expect(lines[10]).toEqual(`#EXT-X-CUE-OUT:DURATION=17.16`); expect(lines[12]).toEqual(`http://mock.com/ad/mock-ad3-video=2525000-1.m4s`); - expect(lines[13]).toEqual(`#EXT-X-MAP:URI="http://mock.com/test-video=2500000.m4s"`); + expect(lines[13]).toEqual(`#EXT-X-MAP:URI="http://mock.com/ad/mock-ad-video=2525000.m4s"`); expect(lines[14]).toEqual(`#EXT-X-DISCONTINUITY`); expect(lines[16]).toEqual(`http://mock.com/ad/mock-ad-video=2525000-1.m4s`); - expect(lines[25]).toEqual(`#EXT-X-MAP:URI="http://mock.com/test-video=2500000.m4s"`); + expect(lines[25]).toEqual(`#EXT-X-MAP:URI="test-video=2500000.m4s"`); expect(lines[26]).toEqual(`#EXT-X-DISCONTINUITY`); expect(lines[27]).toEqual(`#EXT-X-CUE-IN`); expect(lines[29]).toEqual(`test-video=2500000-1.m4s`); expect(lines[lines.length - 2]).toEqual("#EXT-X-ENDLIST"); const m3u8Audio = mockVod.getAudioManifest("stereo", "sv"); const linesAudio = m3u8Audio.split("\n"); - expect(linesAudio[8]).toEqual(`#EXT-X-MAP:URI="mock-ad3-audio=256000.m4s"`); + expect(linesAudio[8]).toEqual(`#EXT-X-MAP:URI="http://mock.com/ad/mock-ad3-audio=256000.m4s"`); expect(linesAudio[9]).toEqual(`#EXT-X-DISCONTINUITY`); expect(linesAudio[10]).toEqual(`#EXT-X-CUE-OUT:DURATION=18.3466`); expect(linesAudio[12]).toEqual(`http://mock.com/ad/mock-ad3-audio=256000-1.m4s`); - expect(linesAudio[15]).toEqual(`#EXT-X-MAP:URI="http://mock.com/test-audio=256000.m4s"`); + expect(linesAudio[15]).toEqual(`#EXT-X-MAP:URI="http://mock.com/ad/mock-ad-audio=256000.m4s"`); expect(linesAudio[16]).toEqual(`#EXT-X-DISCONTINUITY`); expect(linesAudio[18]).toEqual(`http://mock.com/ad/mock-ad-audio=256000-1.m4s`); - expect(linesAudio[33]).toEqual(`#EXT-X-MAP:URI="http://mock.com/test-audio=256000.m4s"`); + expect(linesAudio[33]).toEqual(`#EXT-X-MAP:URI="test-audio=256000.m4s"`); expect(linesAudio[34]).toEqual(`#EXT-X-DISCONTINUITY`); expect(linesAudio[35]).toEqual(`#EXT-X-CUE-IN`); expect(linesAudio[37]).toEqual(`test-audio=256000-1.m4s`); @@ -1728,39 +1719,39 @@ test-audio=256000-6.m4s`; .then(() => { const m3u8 = mockVod.getMediaManifest(4497000); const lines = m3u8.split("\n"); - expect(lines[8]).toEqual(`#EXT-X-MAP:URI="mock-ad3-video=2525000.m4s"`); + expect(lines[8]).toEqual(`#EXT-X-MAP:URI="http://mock.com/ad/mock-ad3-video=2525000.m4s"`); expect(lines[9]).toEqual(`#EXT-X-DISCONTINUITY`); expect(lines[10]).toEqual(`#EXT-X-CUE-OUT:DURATION=3`); expect(lines[12]).toEqual(`http://mock.com/ad/mock-ad3-video=2525000-1.m4s`); - expect(lines[13]).toEqual(`#EXT-X-MAP:URI="http://mock.com/test-video=2500000.m4s"`); + expect(lines[13]).toEqual(`#EXT-X-MAP:URI="test-video=2500000.m4s"`); expect(lines[15]).toEqual(`#EXT-X-CUE-IN`); expect(lines[17]).toEqual(`test-video=2500000-1.m4s`); - expect(lines[22]).toEqual(`#EXT-X-MAP:URI="mock-ad-video=2525000.m4s"`); + expect(lines[22]).toEqual(`#EXT-X-MAP:URI="http://mock.com/ad/mock-ad-video=2525000.m4s"`); expect(lines[24]).toEqual(`#EXT-X-CUE-OUT:DURATION=14.16`); expect(lines[26]).toEqual(`http://mock.com/ad/mock-ad-video=2525000-1.m4s`); - expect(lines[35]).toEqual(`#EXT-X-MAP:URI="http://mock.com/test-video=2500000.m4s"`); + expect(lines[35]).toEqual(`#EXT-X-MAP:URI="test-video=2500000.m4s"`); expect(lines[37]).toEqual(`#EXT-X-CUE-IN`); expect(lines[39]).toEqual(`test-video=2500000-4.m4s`); expect(lines[lines.length - 2]).toEqual("#EXT-X-ENDLIST"); const m3u8Audio = mockVod.getAudioManifest("stereo", "sv"); const linesAudio = m3u8Audio.split("\n"); - expect(linesAudio[8]).toEqual(`#EXT-X-MAP:URI="mock-ad3-audio=256000.m4s"`); + expect(linesAudio[8]).toEqual(`#EXT-X-MAP:URI="http://mock.com/ad/mock-ad3-audio=256000.m4s"`); expect(linesAudio[10]).toEqual(`#EXT-X-CUE-OUT:DURATION=3.4133`); expect(linesAudio[12]).toEqual(`http://mock.com/ad/mock-ad3-audio=256000-1.m4s`); - expect(linesAudio[15]).toEqual(`#EXT-X-MAP:URI="http://mock.com/test-audio=256000.m4s"`); + expect(linesAudio[15]).toEqual(`#EXT-X-MAP:URI="test-audio=256000.m4s"`); expect(linesAudio[17]).toEqual(`#EXT-X-CUE-IN`); expect(linesAudio[19]).toEqual(`test-audio=256000-1.m4s`); - expect(linesAudio[28]).toEqual(`#EXT-X-MAP:URI="mock-ad-audio=256000.m4s"`); + expect(linesAudio[28]).toEqual(`#EXT-X-MAP:URI="http://mock.com/ad/mock-ad-audio=256000.m4s"`); expect(linesAudio[30]).toEqual(`#EXT-X-CUE-OUT:DURATION=14.9333`); expect(linesAudio[32]).toEqual(`http://mock.com/ad/mock-ad-audio=256000-1.m4s`); - expect(linesAudio[47]).toEqual(`#EXT-X-MAP:URI="http://mock.com/test-audio=256000.m4s"`); + expect(linesAudio[47]).toEqual(`#EXT-X-MAP:URI="test-audio=256000.m4s"`); expect(linesAudio[49]).toEqual(`#EXT-X-CUE-IN`); expect(linesAudio[51]).toEqual(`test-audio=256000-6.m4s`); done(); }); }); - it("handles one pre-roll and one post-roll", (done) => { + fit("handles one pre-roll and one post-roll", (done) => { const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8"); mockVod .load(mockCmafMasterManifest, mockCmafMediaManifest, mockCmafAudioManifest) @@ -1784,6 +1775,7 @@ test-audio=256000-6.m4s`; }) .then(() => { const m3u8 = mockVod.getMediaManifest(4497000); + console.log(m3u8) const lines = m3u8.split("\n"); expect(lines[lines.length - 3]).toEqual("#EXT-X-CUE-IN"); expect(lines[lines.length - 2]).toEqual("#EXT-X-ENDLIST"); From 8a38179aac4099cc6aa5755a91d512fe7454d172 Mon Sep 17 00:00:00 2001 From: Nicholas Frederiksen Date: Fri, 17 Feb 2023 13:46:48 +0100 Subject: [PATCH 5/5] test: fix logs --- index.js | 10 +++++----- spec/hls_splice_spec.js | 3 +-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/index.js b/index.js index 6e6b58c..9298d37 100644 --- a/index.js +++ b/index.js @@ -172,12 +172,12 @@ class HLSSpliceVod { const adPlaylist = ad.playlist[findNearestBw(bw, Object.keys(ad.playlist))]; let pos = 0; let i = 0; - closestCmafMapUri = this._getCmafMapUri(this.playlists[bw], this.masterManifestUri, 1); - + closestCmafMapUri = this._getCmafMapUri(this.playlists[bw], this.masterManifestUri, this.baseUrl); + while (pos < offset && i < this.playlists[bw].items.PlaylistItem.length) { const plItem = this.playlists[bw].items.PlaylistItem[i]; if (plItem.attributes.attributes["map-uri"]) { - closestCmafMapUri = this._getCmafMapUri(this.playlists[bw], this.masterManifestUri, 1, i); + closestCmafMapUri = this._getCmafMapUri(this.playlists[bw], this.masterManifestUri, this.baseUrl, i); } pos += plItem.get("duration") * 1000; i++; @@ -233,12 +233,12 @@ class HLSSpliceVod { const adPlaylist = ad.playlistAudio[nearestGroup][nearestLang]; let pos = 0; let idx = 0; - closestCmafMapUri = this._getCmafMapUri(playlist, this.masterManifestUri, 1); + closestCmafMapUri = this._getCmafMapUri(playlist, this.masterManifestUri, this.baseUrl); while (pos < offset && idx < playlist.items.PlaylistItem.length) { const plItem = playlist.items.PlaylistItem[idx]; if (plItem.attributes.attributes["map-uri"]) { - closestCmafMapUri = this._getCmafMapUri(playlist, this.masterManifestUri, 1, i); + closestCmafMapUri = this._getCmafMapUri(playlist, this.masterManifestUri, this.baseUrl, i); } pos += plItem.get("duration") * 1000; idx++; diff --git a/spec/hls_splice_spec.js b/spec/hls_splice_spec.js index fba848d..e2fdfb4 100644 --- a/spec/hls_splice_spec.js +++ b/spec/hls_splice_spec.js @@ -1751,7 +1751,7 @@ test-audio=256000-6.m4s`; }); }); - fit("handles one pre-roll and one post-roll", (done) => { + it("handles one pre-roll and one post-roll", (done) => { const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8"); mockVod .load(mockCmafMasterManifest, mockCmafMediaManifest, mockCmafAudioManifest) @@ -1775,7 +1775,6 @@ test-audio=256000-6.m4s`; }) .then(() => { const m3u8 = mockVod.getMediaManifest(4497000); - console.log(m3u8) const lines = m3u8.split("\n"); expect(lines[lines.length - 3]).toEqual("#EXT-X-CUE-IN"); expect(lines[lines.length - 2]).toEqual("#EXT-X-ENDLIST");