Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Fix: reload() handle PDT timestamps alignment #124

Merged
merged 2 commits into from
Oct 15, 2024
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
125 changes: 110 additions & 15 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -532,8 +532,13 @@ class HLSVod {
*/
reload(mediaSeqNo, additionalSegments, additionalAudioSegments, insertAfter) {
return new Promise((resolve, reject) => {

const allBandwidths = this.getBandwidths();

if (!insertAfter) {
// First handle case where we reload segments with Program Date time positions.
let newTimeOffset = this._getLastTimelinePositionVideo(additionalSegments);
let newTimeOffsetAudio = this._getLastTimelinePositionAudio(additionalAudioSegments);
// If there is anything to slice
if (mediaSeqNo > 0) {
let targetUri = "";
Expand Down Expand Up @@ -589,21 +594,31 @@ class HLSVod {

// Find nearest BW in SFL and prepend them to the corresponding segments bandwidth
allBandwidths.forEach((bw) => {
if (newTimeOffset > 0) {
let totalTimelinePos = 0;
for (let i = 0; i < this.segments[bw].length; i++) {
const segment = this.segments[bw][i];
if (segment.duration) {
totalTimelinePos += segment.duration * 1000;
this.segments[bw][i].timelinePosition = newTimeOffset + totalTimelinePos;
}
}
}
let nearestBw = this._getNearestBandwidthInList(bw, Object.keys(additionalSegments));
this.segments[bw] = additionalSegments[nearestBw].concat(this.segments[bw]);
});

if (!this._isEmpty(this.audioSegments) && additionalAudioSegments) {
const groupIdsInVod = this.getAudioGroups();
const groupIdsInSegments = Object.keys(additionalAudioSegments)
const groupIdsInSegments = Object.keys(additionalAudioSegments);

for (let i = 0; i < groupIdsInSegments.length; i++) {
let groupIdForVod = groupIdsInSegments[i];
let indexOfGroupId = groupIdsInVod.indexOf(groupIdsInSegments[i]);
if (indexOfGroupId < 0) {
groupIdForVod = groupIdsInVod[0]
groupIdForVod = groupIdsInVod[0];
} else {
groupIdForVod = groupIdsInVod[indexOfGroupId]
groupIdForVod = groupIdsInVod[indexOfGroupId];
}

const langsInVod = this.getAudioLangsForAudioGroup(groupIdForVod);
Expand All @@ -613,16 +628,31 @@ class HLSVod {
let langForVod = langsInSegment[j];
let indexOfLang = langsInVod.indexOf(langsInSegment[j]);
if (indexOfLang < 0) {
langForVod = langsInVod[0]
langForVod = langsInVod[0];
} else {
langForVod = langsInVod[indexOfLang]
langForVod = langsInVod[indexOfLang];
}
if (newTimeOffsetAudio > 0) {
let totalTimelinePos = 0;
for (let i = 0; i < this.audioSegments[groupIdForVod][langForVod].length; i++) {
const segment = this.audioSegments[groupIdForVod][langForVod][i];
if (segment.duration) {
totalTimelinePos += segment.duration * 1000;
this.audioSegments[groupIdForVod][langForVod][i].timelinePosition = newTimeOffsetAudio + totalTimelinePos;
}
}
}
this.audioSegments[groupIdForVod][langForVod] = additionalAudioSegments[groupIdsInSegments[i]][langsInSegment[j]].concat(this.audioSegments[groupIdForVod][langForVod]);

this.audioSegments[groupIdForVod][langForVod] = additionalAudioSegments[groupIdsInSegments[i]][langsInSegment[j]].concat(
this.audioSegments[groupIdForVod][langForVod]
);
}
}
}
} else {
// First handle case where we reload segments with Program Date time positions.
let newTimeOffset = this._getLastTimelinePositionVideo(this.segments);
let newTimeOffsetAudio = this._getLastTimelinePositionAudio(this.audioSegments);
// Slice Video segments
if (mediaSeqNo >= 0) {
let size = this.mediaSequences[mediaSeqNo].segments[allBandwidths[0]].length;
let targetUri = this.mediaSequences[mediaSeqNo].segments[allBandwidths[0]][0].uri;
Expand All @@ -635,7 +665,7 @@ class HLSVod {
}
allBandwidths.forEach((bw) => (this.segments[bw] = this.segments[bw].slice(targetPos, targetPos + size)));
}

// Slice Audio segments
if (!this._isEmpty(this.audioSegments) && additionalAudioSegments) {
const groupIds = this.getAudioGroups();
const lang = this.getAudioLangsForAudioGroup(groupIds[0])[0];
Expand All @@ -659,6 +689,20 @@ class HLSVod {
}
}
}
// Update Program Date Time positions in the additional segments video
if (newTimeOffset > 0) {
const allAdditionalSegmentsBandwidths = Object.keys(additionalSegments);
allAdditionalSegmentsBandwidths.forEach((bw) => {
let totalTimelinePos = 0;
for (let i = 0; i < additionalSegments[bw].length; i++) {
const segment = additionalSegments[bw][i];
if (segment.duration) {
totalTimelinePos += segment.duration * 1000;
additionalSegments[bw][i].timelinePosition = newTimeOffset + totalTimelinePos;
}
}
});
}

allBandwidths.forEach((bw) => {
let nearestBw = this._getNearestBandwidthInList(bw, Object.keys(additionalSegments));
Expand All @@ -667,15 +711,15 @@ class HLSVod {

if (!this._isEmpty(this.audioSegments) && additionalAudioSegments) {
const groupIdsInVod = this.getAudioGroups();
const groupIdsInSegments = Object.keys(additionalAudioSegments)
const groupIdsInSegments = Object.keys(additionalAudioSegments);

for (let i = 0; i < groupIdsInSegments.length; i++) {
let groupIdForVod = groupIdsInSegments[i];
let indexOfGroupId = groupIdsInVod.indexOf(groupIdsInSegments[i]);
if (indexOfGroupId < 0) {
groupIdForVod = groupIdsInVod[0]
groupIdForVod = groupIdsInVod[0];
} else {
groupIdForVod = groupIdsInVod[indexOfGroupId]
groupIdForVod = groupIdsInVod[indexOfGroupId];
}

const langsInVod = this.getAudioLangsForAudioGroup(groupIdForVod);
Expand All @@ -685,12 +729,23 @@ class HLSVod {
let langForVod = langsInSegment[j];
let indexOfLang = langsInVod.indexOf(langsInSegment[j]);
if (indexOfLang < 0) {
langForVod = langsInVod[0]
langForVod = langsInVod[0];
} else {
langForVod = langsInVod[indexOfLang]
langForVod = langsInVod[indexOfLang];
}
if (newTimeOffsetAudio > 0) {
let totalTimelinePos = 0;
for (let x = 0; x < additionalAudioSegments[groupIdsInSegments[i]][langsInSegment[j]].length; x++) {
const segment =additionalAudioSegments[groupIdsInSegments[i]][langsInSegment[j]][x];
if (segment.duration) {
totalTimelinePos += segment.duration * 1000;
additionalAudioSegments[groupIdsInSegments[i]][langsInSegment[j]][x].timelinePosition = newTimeOffsetAudio + totalTimelinePos;
}
}
}
this.audioSegments[groupIdForVod][langForVod] = this.audioSegments[groupIdForVod][langForVod].concat(additionalAudioSegments[groupIdsInSegments[i]][langsInSegment[j]]);

this.audioSegments[groupIdForVod][langForVod] = this.audioSegments[groupIdForVod][langForVod].concat(
additionalAudioSegments[groupIdsInSegments[i]][langsInSegment[j]]
);
}
}
}
Expand Down Expand Up @@ -1569,6 +1624,7 @@ class HLSVod {
}
return videoSequences;
}

generateSequencesTypeBExtraMedia(segments, firstGroupId, firstLanguage, type) {
let totalRemovedDiscTags = 0;
let totalRemovedSegments = 0;
Expand Down Expand Up @@ -3337,6 +3393,45 @@ class HLSVod {
}
}

_getLastTimelinePositionVideo(ObjectOfSegments) {
let newTimeOffset = 0;
if (!this._isEmpty(ObjectOfSegments)) {
// For Video
if (ObjectOfSegments[Object.keys(ObjectOfSegments)[0]].length > 0) {
for (let i = ObjectOfSegments[Object.keys(ObjectOfSegments)[0]].length - 1; i >= 0; i--) {
if (ObjectOfSegments[Object.keys(ObjectOfSegments)[0]][i].uri && !ObjectOfSegments[Object.keys(ObjectOfSegments)[0]][i].timelinePosition) {
break;
}
if (ObjectOfSegments[Object.keys(ObjectOfSegments)[0]][i].timelinePosition) {
newTimeOffset = ObjectOfSegments[Object.keys(ObjectOfSegments)[0]][i].timelinePosition;
break;
}
}
}
}
return newTimeOffset;
}

_getLastTimelinePositionAudio(ObjectOfSegments) {
let newTimeOffsetAudio = 0;
if (!this._isEmpty(ObjectOfSegments)) {
// For Audio
const groupIdsInSegments = Object.keys(ObjectOfSegments);
const langsInSegment = Object.keys(ObjectOfSegments[groupIdsInSegments[0]]);
if (!this._isEmpty(langsInSegment) && ObjectOfSegments[groupIdsInSegments[0]][langsInSegment[0]].length > 0) {
for (let i = ObjectOfSegments[groupIdsInSegments[0]][langsInSegment[0]].length - 1; i >= 0; i--) {
if (ObjectOfSegments[groupIdsInSegments[0]][langsInSegment[0]][i].uri && !ObjectOfSegments[groupIdsInSegments[0]][langsInSegment[0]][i].timelinePosition) {
break;
}
if (ObjectOfSegments[groupIdsInSegments[0]][langsInSegment[0]][i].timelinePosition) {
newTimeOffsetAudio = ObjectOfSegments[groupIdsInSegments[0]][langsInSegment[0]][i].timelinePosition;
break;
}
}
}
}
return newTimeOffsetAudio;
}
}

module.exports = HLSVod;
51 changes: 50 additions & 1 deletion spec/hlsvod_audio_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,10 +196,59 @@ describe("HLSVod with demuxed audio", () => {
});
});

it("can reload at the middle of a HLSVod, and set PDT Timestamps correctly if reload media has it", (done) => {
let vod1segments = {};
let vod1AudioSegments = {};
mockVod = new HLSVod("http://mock.com/mock.m3u8", null, Date.now(), 0, null, null);
mockVod2 = new HLSVod("http://mock.com/mock2.m3u8");

mockVod
.load(mockMasterManifest1, mockMediaManifest1, mockAudioManifest1)
.then(() => {
vod1segments = mockVod.getLiveMediaSequenceSegments(1);
vod1AudioSegments = mockVod.getLiveAudioSequenceSegments(1);
Object.keys(vod1segments).forEach((bw) => vod1segments[bw].push({ discontinuity: true }));
const groupIds = Object.keys(vod1AudioSegments);
for (let i = 0; i < groupIds.length; i++) {
const groupId = groupIds[i];
const langs = Object.keys(vod1AudioSegments[groupId])
for (let j = 0; j < langs.length; j++) {
const lang = langs[j];
vod1AudioSegments[groupId][lang].push({ discontinuity: true });
}
}
})
.then(() => {
mockVod2.load(mockMasterManifest2, mockMediaManifest2, mockAudioManifest2).then(() => {
let bottomSegmentPreReload =
mockVod2.getLiveMediaSequenceSegments(6)["401000"][mockVod2.getLiveMediaSequenceSegments(6)["401000"].length - 1];
let bottomAudioSegmentPreReload =
mockVod2.getLiveAudioSequenceSegments(6)["aac"]["en"][mockVod2.getLiveAudioSequenceSegments(6)["aac"]["en"].length - 1];
mockVod2.reload(6, vod1segments, vod1AudioSegments).then(() => {
let size = mockVod2.getLiveMediaSequenceSegments(1)["401000"].length;
expect(mockVod2.getLiveMediaSequenceSegments(1)["401000"][size - 1]).toEqual(bottomSegmentPreReload);
let sizeAudio = mockVod2.getLiveAudioSequenceSegments(1)["aac"]["en"].length;
expect(mockVod2.getLiveAudioSequenceSegments(1)["aac"]["en"][sizeAudio - 1]).toEqual(bottomAudioSegmentPreReload);
const lastSegmentItem = mockVod2.getLiveMediaSequenceSegments(1)["401000"][mockVod2.getLiveMediaSequenceSegments(1)["401000"].length - 1];
const secondLastSegmentItem = mockVod2.getLiveMediaSequenceSegments(1)["401000"][mockVod2.getLiveMediaSequenceSegments(1)["401000"].length - 3];
const differenceInPDT = lastSegmentItem.timelinePosition - secondLastSegmentItem.timelinePosition;
const lastSegmentItemDurationMs = lastSegmentItem.duration * 1000;
expect(differenceInPDT).toEqual(lastSegmentItemDurationMs);
const lastSegmentItemAudio = mockVod2.getLiveAudioSequenceSegments(1)["aac"]["en"][mockVod2.getLiveAudioSequenceSegments(1)["aac"]["en"].length - 1];
const secondLastSegmentItemAudio = mockVod2.getLiveAudioSequenceSegments(1)["aac"]["en"][mockVod2.getLiveAudioSequenceSegments(1)["aac"]["en"].length - 3];
const differenceInPDTAudio = lastSegmentItemAudio.timelinePosition - secondLastSegmentItemAudio.timelinePosition;
const lastSegmentItemDurationMsAudio = lastSegmentItemAudio.duration * 1000;
expect(differenceInPDTAudio).toEqual(lastSegmentItemDurationMsAudio);
done();
});
});
});
});

it("can reload at the middle of a HLSVod, and insert segments after live point", (done) => {
let vod1segments = {};
let vod1AudioSegments = {};
mockVod = new HLSVod("http://mock.com/mock.m3u8");
mockVod = new HLSVod("http://mock.com/mock.m3u8", null, Date.now(), 0, null, null);
mockVod2 = new HLSVod("http://mock.com/mock2.m3u8");

mockVod
Expand Down
Loading