Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

feat: support of trimming beginning of a VOD with an optional start offset option #6

Merged
merged 5 commits into from
Aug 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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