Skip to content

Commit

Permalink
feat: support of trimming beginning of a VOD with an optional start o…
Browse files Browse the repository at this point in the history
…ffset option (#6)

* chore: documented expected behaviour

* feat: can trim beginning of muxed VODs

* chore: ignore dist folder if exists

* feat: trim beginning of demuxed VODs

* fix: need to take into account when audio and video segment may differ
  • Loading branch information
birme committed Aug 8, 2023
1 parent 79f15e2 commit 490ede4
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 5 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
node_modules
.nyc_output
dist
65 changes: 64 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,76 @@ segment2_0_av.ts
#EXT-X-ENDLIST
```

You can also provide a start-time offset.

```
const hlsVod = new HLSTruncateVod('http://testcontent.eyevinn.technology/slates/30seconds/playlist.m3u8', 4, { offset: 10 });
hlsVod.load()
.then(() => {
const mediaManifest = hlsVod.getMediaManifest(4928000);
console.log(mediaManifest);
});
```

The library will first remove 10 seconds (approx depending on segment length) and then take the next 4 seconds of the original playlist. For example consider this playlist:

```
#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:3.000,
segment10_0_av.ts
#EXT-X-ENDLIST
```

will result in:

```
#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,
segment5_0_av.ts
#EXTINF:3.000,
segment6_0_av.ts
#EXT-X-ENDLIST
```

It will remove 12 seconds from the start (3 segments is 9 seconds and 4 segments is 12 seconds) and then truncate the remaining part to get a duration of 6 seconds (2 segments i 6 segments which is close to 4 seconds as requested).

# Authors

This open source project is maintained by Eyevinn Technology.

## Contributors

- Jonas Rydholm Birmé (jonas.birme@eyevinn.se)
- Jonas Birmé (jonas.birme@eyevinn.se)
- Alan Allard (alan.allard@eyevinn.se)

# [Contributing](CONTRIBUTING.md)
Expand Down
40 changes: 36 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ class HLSTruncateVod {
this.playlists = {};
this.duration = duration;
this.durationAudio = 0;
this.startVideoOffset = 0;
this.bandwiths = [];
this.audioSegments = {};
if (options && options.offset) {
this.startOffset = options.offset;
}
}

load(_injectMasterManifest, _injectMediaManifest, _injectAudioManifest) {
Expand Down Expand Up @@ -147,8 +151,21 @@ class HLSTruncateVod {
let accDuration = 0;
let prevAccDuration = 0;
let pos = 0;
let startPos = 0;

if (this.startOffset) {
let accStartOffset = 0;
m3u.items.PlaylistItem.map((item => {
if (accStartOffset + item.get('duration') <= this.startOffset) {
accStartOffset += item.get('duration');
startPos++;
}
}));

this.startVideoOffset = this.startVideoOffset === 0 ? accStartOffset : this.startVideoOffset;
}

m3u.items.PlaylistItem.map((item => {
m3u.items.PlaylistItem.slice(startPos).map((item => {
if (accDuration <= this.duration) {
prevAccDuration = accDuration;
accDuration += item.get('duration');
Expand All @@ -166,7 +183,7 @@ class HLSTruncateVod {

this.durationAudio = this.durationAudio === 0 ? accDuration : this.durationAudio;

this.playlists[bandwidth].items.PlaylistItem = m3u.items.PlaylistItem.slice(0, pos);
this.playlists[bandwidth].items.PlaylistItem = m3u.items.PlaylistItem.slice(startPos, startPos + pos);
resolve();
});
parser.on('error', (err) => {
Expand Down Expand Up @@ -195,8 +212,22 @@ class HLSTruncateVod {
let accDuration = 0;
let prevAccDuration = 0;
let pos = 0;
let startPos = 0;

m3u.items.PlaylistItem.map((item => {
if (this.startOffset) {
let accStartOffset = 0;
m3u.items.PlaylistItem.map((item => {
if (accStartOffset + item.get('duration') <= this.startOffset) {
accStartOffset += item.get('duration');
startPos++;
}
}));
if ((accStartOffset > this.startVideoOffset) && startPos > 1) {
startPos--;
}
}

m3u.items.PlaylistItem.slice(startPos).map((item => {
if (accDuration <= this.durationAudio) {
prevAccDuration = accDuration;
accDuration += item.get('duration');
Expand All @@ -210,7 +241,8 @@ class HLSTruncateVod {
if (this._similarSegItemDuration() && (accDuration - this.durationAudio) >= (this.durationAudio - prevAccDuration) && pos > 1) {
pos--;
}
this.audioSegments[audioGroupId][audioLang].items.PlaylistItem = m3u.items.PlaylistItem.slice(0, pos);

this.audioSegments[audioGroupId][audioLang].items.PlaylistItem = m3u.items.PlaylistItem.slice(startPos, startPos + pos);
resolve();
});
parser.on('error', (err) => {
Expand Down
20 changes: 20 additions & 0 deletions spec/hls_truncate_cmaf_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -290,5 +290,25 @@ describe("HLSTruncateVod", () => {
done();
})
});

it("cuts to the closest segment when requesting unaligned duration with equal time between them and has start offset", done => {
const mockVod = new HLSTruncateVod('http://mock.com/mock.m3u8', 7.5, { offset: 5 });

mockVod.load(mockMasterManifest, mockMediaManifest, mockAudioManifest)
.then(() => {
const bandwidths = mockVod.getBandwidths();
const videoManifest = mockVod.getMediaManifest(bandwidths[0]);
const audioManifest = mockVod.getAudioManifest("audio-aacl-256", "sv");
const linesVideo = videoManifest.split("\n");
const linesAudio = audioManifest.split("\n");
const durationVideo = calcDuration(videoManifest);
const durationAudio = calcDuration(audioManifest);
expect(durationVideo).toEqual(6);
expect(durationAudio).toEqual(7.68);
expect(linesVideo[9]).toEqual("test-video=2500000-2.m4s");
expect(linesAudio[9]).toEqual("test-audio=256000-2.m4s");
done();
})
});
});
});
37 changes: 37 additions & 0 deletions spec/hls_truncate_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,22 @@ describe("HLSTruncateVod for muxed TS HLS Vods", () => {
done();
})
});

it("can trim the beginning if start offset is requested", done => {
const mockVod = new HLSTruncateVod('http://mock.com/mock.m3u8', 6, { offset: 10 });

mockVod.load(mockMasterManifest, mockMediaManifest)
.then(() => {
const bandwidths = mockVod.getBandwidths();
const manifest = mockVod.getMediaManifest(bandwidths[0]);
const lines = manifest.split("\n");
expect(lines[8]).toEqual("segment4_0_av.ts");
expect(lines[10]).toEqual("segment5_0_av.ts");
const duration = calcDuration(manifest);
expect(duration).toEqual(6);
done();
})
});
});
describe("HLSTruncateVod,", () => {
describe("for Demuxed TS HLS Vods", () => {
Expand Down Expand Up @@ -178,6 +194,27 @@ describe("HLSTruncateVod,", () => {
done();
})
});

it("can trim the beginning if start offset is requested", done => {
const mockVod = new HLSTruncateVod('http://mock.com/mock.m3u8', 30, { offset: 30 });

mockVod.load(mockMasterManifest, mockMediaManifest, mockAudioManifest)
.then(() => {
const bandwidths = mockVod.getBandwidths();
const manifest = mockVod.getMediaManifest(bandwidths[0]);
const lines = manifest.split("\n");
expect(lines[8]).toEqual("level1/seg_36.ts");
expect(lines[11]).toEqual("level1/seg_37.ts");
const duration = calcDuration(manifest);
expect(duration).toEqual(30.03);

const audioManifest = mockVod.getAudioManifest("aac", "en");
const audioLines = audioManifest.split("\n");
expect(audioLines[8]).toEqual("audio/seg_en_36.ts");
expect(audioLines[11]).toEqual("audio/seg_en_37.ts");
done();
})
});
});
describe("for Demuxed TS HLS Vods which have different segments durations,", () => {
let mockMasterManifest;
Expand Down

0 comments on commit 490ede4

Please sign in to comment.