From 50731731b3c87a481ec287914b1bdff52ed000dc Mon Sep 17 00:00:00 2001
From: Nicholas Frederiksen <nicholas.frederiksen@eyevinn.se>
Date: Mon, 25 Nov 2024 16:39:47 +0100
Subject: [PATCH 1/6] Fix: Extend HLS Interstitial Tag Insertion (#50)

* interstitial: handle more attributes and set start-date more accurately

* fix if statement

* update unit tests
---
 index.js                | 120 ++++++++++++++++++++++++++++++----------
 spec/hls_splice_spec.js |  26 +++++----
 2 files changed, 108 insertions(+), 38 deletions(-)

diff --git a/index.js b/index.js
index ad5273c..6653905 100644
--- a/index.js
+++ b/index.js
@@ -20,7 +20,7 @@ const NOT_MULTIVARIANT_ERROR_MSG = "Error: Source is Not a Multivariant Manifest
 let DUMMY_SUBTITLE_COUNT = 0;
 const getDummySubtitleSegmentId = () => {
   return DUMMY_SUBTITLE_COUNT++;
-}
+};
 
 const findNearestGroupAndLang = (_group, _language, _playlist) => {
   const groups = Object.keys(_playlist);
@@ -93,7 +93,6 @@ class HLSSpliceVod {
     }
 
     this.cmafMapUri = { video: {}, audio: {}, subtitle: {} };
-
   }
 
   loadMasterManifest(_injectMasterManifest, _injectMediaManifest, _injectAudioManifest, _injectSubtitleManifest) {
@@ -214,7 +213,9 @@ class HLSSpliceVod {
             subtitleItemUri = subtitleItem.get("uri");
           }
           const subtitleItemGroupId = subtitleItem.get("group-id");
-          const subtitleItemLanguage = subtitleItem.get("language") ? subtitleItem.get("language") : subtitleItem.get("name");
+          const subtitleItemLanguage = subtitleItem.get("language")
+            ? subtitleItem.get("language")
+            : subtitleItem.get("name");
           if (loadedSubtitleGroupLangs.includes(`${subtitleItemGroupId}-${subtitleItemLanguage}`)) {
             continue;
           } else {
@@ -222,7 +223,12 @@ class HLSSpliceVod {
           }
           const subtitleManifestUrl = url.resolve(baseUrl, subtitleItemUri);
           mediaManifestPromises.push(
-            this.loadSubtitleManifest(subtitleManifestUrl, subtitleItemGroupId, subtitleItemLanguage, _injectSubtitleManifest)
+            this.loadSubtitleManifest(
+              subtitleManifestUrl,
+              subtitleItemGroupId,
+              subtitleItemLanguage,
+              _injectSubtitleManifest
+            )
           );
         }
         Promise.all(mediaManifestPromises).then(resolve).catch(reject);
@@ -243,16 +249,16 @@ class HLSSpliceVod {
   _createFakeSubtitles(videoPlaylist) {
     let bw = Object.keys(videoPlaylist)[0];
 
-    const [nearestGroup, nearestLang] = findNearestGroupAndLang("temp", "temp", this.playlistsSubtitle)
+    const [nearestGroup, nearestLang] = findNearestGroupAndLang("temp", "temp", this.playlistsSubtitle);
     let subtitleItems = {};
     subtitleItems[nearestGroup] = {};
     subtitleItems[nearestGroup][nearestLang] = {};
 
-    const vp = videoPlaylist[bw]
+    const vp = videoPlaylist[bw];
 
     let m3u = m3u8.M3U.create();
 
-    vp.items.PlaylistItem.forEach(element => m3u.addPlaylistItem(element.properties))
+    vp.items.PlaylistItem.forEach((element) => m3u.addPlaylistItem(element.properties));
 
     subtitleItems[nearestGroup][nearestLang] = m3u;
     const playlist = subtitleItems[nearestGroup][nearestLang];
@@ -263,14 +269,17 @@ class HLSSpliceVod {
     for (let index = 0; index < playlist.items.PlaylistItem.length; index++) {
       if (playlist.items.PlaylistItem[index].get("duration")) {
         duration += playlist.items.PlaylistItem[index].get("duration");
-        playlist.items.PlaylistItem[index].set("uri", this.dummySubtitleEndpoint + `?id=${getDummySubtitleSegmentId()}`);
+        playlist.items.PlaylistItem[index].set(
+          "uri",
+          this.dummySubtitleEndpoint + `?id=${getDummySubtitleSegmentId()}`
+        );
       }
     }
     return [subtitleItems, duration];
   }
 
   _insertAdAtExtraMedia(startOffset, offset, playlists, adPlaylists, targetDuration, adDuration, isPostRoll) {
-    let groups = Object.keys(playlists)
+    let groups = Object.keys(playlists);
     if (isPostRoll) {
       let duration = 0;
       const langs = Object.keys(playlists[groups[0]]);
@@ -333,7 +342,14 @@ class HLSSpliceVod {
     }
   }
 
-  insertAdAt(offset, adMasterManifestUri, _injectAdMasterManifest, _injectAdMediaManifest, _injectAdAudioManifest, _injectAdSubtitleManifest) {
+  insertAdAt(
+    offset,
+    adMasterManifestUri,
+    _injectAdMasterManifest,
+    _injectAdMediaManifest,
+    _injectAdAudioManifest,
+    _injectAdSubtitleManifest
+  ) {
     this.ad = {};
     return new Promise((resolve, reject) => {
       this._parseAdMasterManifest(
@@ -416,11 +432,27 @@ class HLSSpliceVod {
           const audioGroups = Object.keys(this.playlistsAudio);
           const adAudioGroups = Object.keys(ad.playlistAudio);
           if (audioGroups.length > 0 && adAudioGroups.length > 0) {
-            this._insertAdAtExtraMedia(startOffset, offset, this.playlistsAudio, ad.playlistAudio, this.targetDurationAudio, ad.durationAudio, isPostRoll)
+            this._insertAdAtExtraMedia(
+              startOffset,
+              offset,
+              this.playlistsAudio,
+              ad.playlistAudio,
+              this.targetDurationAudio,
+              ad.durationAudio,
+              isPostRoll
+            );
           }
 
           if (subtitleGroups.length > 0) {
-            this._insertAdAtExtraMedia(startOffset, offset, this.playlistsSubtitle, ad.playlistSubtitle, this.targetDurationSubtitle, ad.durationSubtile, isPostRoll)
+            this._insertAdAtExtraMedia(
+              startOffset,
+              offset,
+              this.playlistsSubtitle,
+              ad.playlistSubtitle,
+              this.targetDurationSubtitle,
+              ad.durationSubtile,
+              isPostRoll
+            );
           }
           resolve();
         })
@@ -428,7 +460,7 @@ class HLSSpliceVod {
     });
   }
 
-  _insertInterstitialAtExtraMedia(offset, id, uri, isAssetList, extraAttrs, plannedDuration, playlists) {
+  _insertInterstitialAtExtraMedia(offset, id, uri, isAssetList, extraAttrs, startDate, playlists, opts) {
     const groups = Object.keys(playlists);
     for (let i = 0; i < groups.length; i++) {
       const group = groups[i];
@@ -442,12 +474,13 @@ class HLSSpliceVod {
         while (pos < offset && idx < playlist.items.PlaylistItem.length) {
           const plItem = playlist.items.PlaylistItem[idx];
           pos += plItem.get("duration") * 1000;
-          idx++;
+          if (pos <= offset) {
+            idx++;
+          }
         }
-        let startDate = new Date(1 + offset).toISOString();
         let durationTag = "";
-        if (plannedDuration) {
-          durationTag = `,DURATION=${plannedDuration / 1000}`;
+        if (opts && opts.plannedDuration) {
+          durationTag = `,DURATION=${opts.plannedDuration / 1000}`;
         }
         if (isAssetList) {
           playlist.items.PlaylistItem[idx].set(
@@ -469,7 +502,7 @@ class HLSSpliceVod {
       if (this.bumperDuration) {
         offset = this.bumperDuration + offset;
       }
-
+      let startDate; 
       let extraAttrs = "";
       if (opts) {
         if (opts.resumeOffset !== undefined) {
@@ -481,6 +514,31 @@ class HLSSpliceVod {
         if (opts.snap === "IN" || opts.snap === "OUT") {
           extraAttrs += `,X-SNAP="${opts.snap}"`;
         }
+        if (opts.restrict !== undefined) {
+          if (opts.restrict.includes("SKIP") || opts.restrict.includes("JUMP")) {
+            extraAttrs += `,X-RESTRICT="${opts.restrict}"`;
+          }
+        }
+        if (opts.contentmayvary === "YES" || opts.contentmayvary === "NO") {
+          extraAttrs += `,X-CONTENT-MAY-VARY="${opts.contentmayvary}"`;
+        }
+        if (opts.timelineoccupies === "POINT" || opts.timelineoccupies === "RANGE") {
+          extraAttrs += `,X-TIMELINE-OCCUPIES="${opts.timelineoccupies}"`;
+        }
+        if (opts.timelinestyle === "HIGHLIGHT" || opts.timelinestyle === "PRIMARY") {
+          extraAttrs += `,X-TIMELINE-STYLE="${opts.timelinestyle}"`;
+        }
+        if (opts.custombeacon !== undefined) {
+          let custombeacon = "";
+          if (opts.custombeacon.includes("%")) {
+            custombeacon = decodeURIComponent(opts.custombeacon);
+          } else {
+            custombeacon = opts.custombeacon;
+          }
+          if (custombeacon.charAt(0) === "X") {
+            extraAttrs += `,${custombeacon}`;
+          }
+        }
       }
 
       const bandwidths = Object.keys(this.playlists);
@@ -492,9 +550,11 @@ class HLSSpliceVod {
         while (pos < offset && i < this.playlists[bw].items.PlaylistItem.length) {
           const plItem = this.playlists[bw].items.PlaylistItem[i];
           pos += plItem.get("duration") * 1000;
-          i++;
+          if (pos <= offset) {
+            i++;
+          }
         }
-        let startDate = new Date(1 + offset).toISOString();
+        startDate = new Date(1 + Number(offset)).toISOString();
         let durationTag = "";
         if (opts && opts.plannedDuration) {
           durationTag = `,DURATION=${opts.plannedDuration / 1000}`;
@@ -512,11 +572,9 @@ class HLSSpliceVod {
         }
       }
 
-      let plannedDuration = (opts && opts.plannedDuration) ? opts.plannedDuration : 0;
-
-      this._insertInterstitialAtExtraMedia(offset, id, uri, isAssetList, extraAttrs, plannedDuration, this.playlistsAudio)
+      this._insertInterstitialAtExtraMedia(offset, id, uri, isAssetList, extraAttrs, startDate, this.playlistsAudio, opts);
 
-      this._insertInterstitialAtExtraMedia(offset, id, uri, isAssetList, extraAttrs, plannedDuration, this.playlistsSubtitle)
+      this._insertInterstitialAtExtraMedia(offset, id, uri, isAssetList, extraAttrs, startDate, this.playlistsSubtitle, opts);
 
       resolve();
     });
@@ -585,9 +643,9 @@ class HLSSpliceVod {
             this.playlists[bw].set("targetDuration", this.targetDuration);
           }
 
-          this._insertBumperExtraMedia(this.playlistsAudio, bumper.playlistAudio, this.targetDurationAudio)
+          this._insertBumperExtraMedia(this.playlistsAudio, bumper.playlistAudio, this.targetDurationAudio);
 
-          this._insertBumperExtraMedia(this.playlistsSubtitle, bumper.playlistSubtitle, this.targetDurationSubtitle)
+          this._insertBumperExtraMedia(this.playlistsSubtitle, bumper.playlistSubtitle, this.targetDurationSubtitle);
 
           resolve();
         })
@@ -901,7 +959,7 @@ class HLSSpliceVod {
           this.targetDurationSubtitle = targetDuration;
         }
         this.playlistsSubtitle[group][lang].set("targetDuration", this.targetDurationSubtitle);
-        
+
         const initSegUri = this._getCmafMapUri(m3u, subtitleManifestUri, this.baseUrl);
         if (initSegUri) {
           if (!this.cmafMapUri.subtitle[group]) {
@@ -925,7 +983,13 @@ class HLSSpliceVod {
     });
   }
 
-  _parseAdMasterManifest(manifestUri, _injectAdMasterManifest, _injectAdMediaManifest, _injectAdAudioManifest, _injectAdSubtitleManifest) {
+  _parseAdMasterManifest(
+    manifestUri,
+    _injectAdMasterManifest,
+    _injectAdMediaManifest,
+    _injectAdAudioManifest,
+    _injectAdSubtitleManifest
+  ) {
     return new Promise((resolve, reject) => {
       let ad = {};
       const parser = m3u8.createStream();
diff --git a/spec/hls_splice_spec.js b/spec/hls_splice_spec.js
index 560aad0..86f3a71 100644
--- a/spec/hls_splice_spec.js
+++ b/spec/hls_splice_spec.js
@@ -1,6 +1,10 @@
 const HLSSpliceVod = require("../index.js");
 const fs = require("fs");
 
+const ll = (log_lines) => {
+  log_lines.map((line, idx) => console.log(line, idx));
+}
+
 describe("HLSSpliceVod", () => {
   let mockMasterManifest;
   let mockMediaManifest;
@@ -439,7 +443,7 @@ describe("HLSSpliceVod", () => {
       .then(() => {
         const m3u8 = mockVod.getMediaManifest(4497000);
         const lines = m3u8.split("\n");
-        expect(lines[12]).toEqual(
+        expect(lines[10]).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();
@@ -456,7 +460,7 @@ describe("HLSSpliceVod", () => {
       .then(() => {
         const m3u8 = mockVod.getMediaManifest(4497000);
         const lines = m3u8.split("\n");
-        expect(lines[12]).toEqual(
+        expect(lines[10]).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();
@@ -1379,12 +1383,12 @@ describe("HLSSpliceVod with Demuxed Audio Tracks,", () => {
       .then(() => {
         const m3u8 = mockVod.getMediaManifest(4497000);
         let lines = m3u8.split("\n");
-        expect(lines[12]).toEqual(
+        expect(lines[10]).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(
+        expect(lines[10]).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();
@@ -1401,12 +1405,12 @@ describe("HLSSpliceVod with Demuxed Audio Tracks,", () => {
       .then(() => {
         const m3u8 = mockVod.getMediaManifest(4497000);
         let lines = m3u8.split("\n");
-        expect(lines[12]).toEqual(
+        expect(lines[10]).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(
+        expect(lines[10]).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();
@@ -1543,6 +1547,7 @@ describe("HLSSpliceVod with Demuxed Audio Tracks,", () => {
       .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"'
         );
@@ -1980,13 +1985,12 @@ test-audio=256000-6.m4s`;
       .then(() => {
         const m3u8 = mockVod.getMediaManifest(4497000);
         let lines = m3u8.split("\n");
-        expect(lines[22]).toEqual(
+        expect(lines[20]).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(
+        expect(lines[26]).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();
@@ -2005,12 +2009,14 @@ test-audio=256000-6.m4s`;
       .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(
+       
+        expect(lines[28]).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 2007119cff03b712d3c002e40d9357477251deb1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonas=20Birm=C3=A9?= <jonas.birme@eyevinn.se>
Date: Tue, 26 Nov 2024 17:01:36 +0100
Subject: [PATCH 2/6] 5.0.1

---
 package-lock.json | 4 ++--
 package.json      | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index bf4ae62..b9b7f11 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "@eyevinn/hls-splice",
-  "version": "0.4.12",
+  "version": "5.0.1",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "@eyevinn/hls-splice",
-      "version": "0.4.12",
+      "version": "5.0.1",
       "license": "MIT",
       "dependencies": {
         "@eyevinn/m3u8": "^0.5.6",
diff --git a/package.json b/package.json
index 635a2ea..baa6cca 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@eyevinn/hls-splice",
-  "version": "0.4.12",
+  "version": "5.0.1",
   "description": "NPM library to splice HLS VOD",
   "repository": "https://github.com/Eyevinn/hls-splice",
   "main": "index.js",

From 41ed4ac4fda26a840648ee9e008290333fa9b846 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jonas=20Birm=C3=A9?= <jonas.birme@eyevinn.se>
Date: Tue, 26 Nov 2024 17:02:11 +0100
Subject: [PATCH 3/6] 0.5.1

---
 package-lock.json | 4 ++--
 package.json      | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index b9b7f11..02a4ebc 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "@eyevinn/hls-splice",
-  "version": "5.0.1",
+  "version": "0.5.1",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "@eyevinn/hls-splice",
-      "version": "5.0.1",
+      "version": "0.5.1",
       "license": "MIT",
       "dependencies": {
         "@eyevinn/m3u8": "^0.5.6",
diff --git a/package.json b/package.json
index baa6cca..bf15e13 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@eyevinn/hls-splice",
-  "version": "5.0.1",
+  "version": "0.5.1",
   "description": "NPM library to splice HLS VOD",
   "repository": "https://github.com/Eyevinn/hls-splice",
   "main": "index.js",

From 45fc6a3f22c704d9a843c39261cb39ee94eb7ca0 Mon Sep 17 00:00:00 2001
From: Nfrederiksen <nicholas.frederiksen@eyevinn.se>
Date: Wed, 27 Nov 2024 10:34:52 +0100
Subject: [PATCH 4/6] test: add more coverage

---
 spec/hls_splice_spec.js | 30 +++++++++++++++++++++++++++---
 1 file changed, 27 insertions(+), 3 deletions(-)

diff --git a/spec/hls_splice_spec.js b/spec/hls_splice_spec.js
index f742f04..25763e6 100644
--- a/spec/hls_splice_spec.js
+++ b/spec/hls_splice_spec.js
@@ -503,7 +503,7 @@ describe("HLSSpliceVod", () => {
       });
   });
 
-  it("can insert interstitial with an asset uri and a resume offset and CUE tag", (done) => {
+  it("can insert interstitial with an asset uri and a resume offset and CUE attribute", (done) => {
     const mockVod = new HLSSpliceVod("http://mock.com/mock.m3u8");
     mockVod
       .load(mockMasterManifest, mockMediaManifest)
@@ -516,7 +516,6 @@ describe("HLSSpliceVod", () => {
       .then(() => {
         const m3u8 = mockVod.getMediaManifest(4497000);
         const lines = m3u8.split("\n");
-        // ll(lines);
         expect(lines[29]).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",CUE="PRE,ONCE",X-RESUME-OFFSET=10.5'
         );
@@ -1484,13 +1483,38 @@ describe("HLSSpliceVod with Demuxed Audio Tracks,", () => {
       });
   });
 
+  it("can insert interstitial with an asset uri and a resume offset and CUE attribute", (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,
+                cue: "POST,ONCE"
+        });
+      })
+      .then(() => {
+        const m3u8 = mockVod.getMediaManifest(4497000);
+        let lines = m3u8.split("\n");
+        expect(lines[29]).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",CUE="POST,ONCE",X-RESUME-OFFSET=10.5'
+        );
+        const m3u8Audio = mockVod.getAudioManifest("stereo", "sv");
+        lines = m3u8Audio.split("\n");
+        expect(lines[29]).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",CUE="POST,ONCE",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,
+          resumeOffset: 0
         });
       })
       .then(() => {

From ab23aebeecd5628466ceaa08c0da51c68f2bcba5 Mon Sep 17 00:00:00 2001
From: Nfrederiksen <nicholas.frederiksen@eyevinn.se>
Date: Wed, 27 Nov 2024 10:45:26 +0100
Subject: [PATCH 5/6] test: one more coverage

---
 spec/hls_splice_spec.js | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/spec/hls_splice_spec.js b/spec/hls_splice_spec.js
index 25763e6..d5947fd 100644
--- a/spec/hls_splice_spec.js
+++ b/spec/hls_splice_spec.js
@@ -2069,4 +2069,29 @@ test-audio=256000-6.m4s`;
         done();
       });
   });
+
+  it("can insert interstitial with an asset uri and a resume offset and CUE attribute", (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", false, {
+          resumeOffset: 10500,
+          cue: "POST,ONCE"
+        });
+      })
+      .then(() => {
+        const m3u8 = mockVod.getMediaManifest(4497000);
+        let lines = m3u8.split("\n");
+        expect(lines[129]).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",CUE="POST,ONCE",X-RESUME-OFFSET=10.5'
+        );
+        const m3u8Audio = mockVod.getAudioManifest("stereo", "sv");
+        lines = m3u8Audio.split("\n");
+        expect(lines[195]).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",CUE="POST,ONCE",X-RESUME-OFFSET=10.5'
+        );
+        done();
+      });
+  });
 });

From d2b11a68f11bafd6e7bc4c17e5accddc959b88bb Mon Sep 17 00:00:00 2001
From: Nfrederiksen <nicholas.frederiksen@eyevinn.se>
Date: Wed, 27 Nov 2024 10:56:54 +0100
Subject: [PATCH 6/6] test: add even more coverage

---
 spec/hls_splice_spec.js | 100 ++++++++++++++++++++++++++++++++++++++--
 1 file changed, 95 insertions(+), 5 deletions(-)

diff --git a/spec/hls_splice_spec.js b/spec/hls_splice_spec.js
index d5947fd..9b81093 100644
--- a/spec/hls_splice_spec.js
+++ b/spec/hls_splice_spec.js
@@ -523,6 +523,46 @@ describe("HLSSpliceVod", () => {
       });
   });
 
+  it("can insert interstitial with an asset uri and a resume offset and invalid CUE attribute", (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,
+          cue: "pre-once",
+        });
+      })
+      .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 and single CUE attribute", (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,
+          cue: "POST",
+        });
+      })
+      .then(() => {
+        const m3u8 = mockVod.getMediaManifest(4497000);
+        const lines = m3u8.split("\n");
+        expect(lines[29]).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",CUE="POST",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
@@ -1490,19 +1530,69 @@ describe("HLSSpliceVod with Demuxed Audio Tracks,", () => {
       .then(() => {
         return mockVod.insertInterstitialAt(18000, "001", "http://mock.com/asseturi", false, {
           resumeOffset: 10500,
-                cue: "POST,ONCE"
+          cue: "PRE,ONCE",
         });
       })
       .then(() => {
         const m3u8 = mockVod.getMediaManifest(4497000);
         let lines = m3u8.split("\n");
         expect(lines[29]).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",CUE="POST,ONCE",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",CUE="PRE,ONCE",X-RESUME-OFFSET=10.5'
         );
         const m3u8Audio = mockVod.getAudioManifest("stereo", "sv");
         lines = m3u8Audio.split("\n");
         expect(lines[29]).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",CUE="POST,ONCE",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",CUE="PRE,ONCE",X-RESUME-OFFSET=10.5'
+        );
+        done();
+      });
+  });
+
+  it("can insert interstitial with an asset uri and a resume offset and invalid CUE attribute", (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,
+          cue: "ROST,TWICE",
+        });
+      })
+      .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 and partially valid CUE attribute", (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,
+          cue: "POST,THRICE",
+        });
+      })
+      .then(() => {
+        const m3u8 = mockVod.getMediaManifest(4497000);
+        let lines = m3u8.split("\n");
+        expect(lines[29]).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",CUE="POST",X-RESUME-OFFSET=10.5'
+        );
+        const m3u8Audio = mockVod.getAudioManifest("stereo", "sv");
+        lines = m3u8Audio.split("\n");
+        expect(lines[29]).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",CUE="POST",X-RESUME-OFFSET=10.5'
         );
         done();
       });
@@ -1514,7 +1604,7 @@ describe("HLSSpliceVod with Demuxed Audio Tracks,", () => {
       .load(mockMasterManifest, mockMediaManifest, mockAudioManifest)
       .then(() => {
         return mockVod.insertInterstitialAt(18000, "001", "http://mock.com/asseturi", false, {
-          resumeOffset: 0
+          resumeOffset: 0,
         });
       })
       .then(() => {
@@ -2077,7 +2167,7 @@ test-audio=256000-6.m4s`;
       .then(() => {
         return mockVod.insertInterstitialAt(18000, "001", "http://mock.com/asseturi", false, {
           resumeOffset: 10500,
-          cue: "POST,ONCE"
+          cue: "POST,ONCE",
         });
       })
       .then(() => {