From d5b1887f704d3ea87f9d70a7bf25e67836fc0671 Mon Sep 17 00:00:00 2001 From: hswaminathan Date: Fri, 23 Jun 2023 01:43:19 -0400 Subject: [PATCH] endTime calculation with endOnNext --- src/util/text-tracks.js | 45 +++++++++++++---- test/text-tracks.test.js | 101 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 137 insertions(+), 9 deletions(-) diff --git a/src/util/text-tracks.js b/src/util/text-tracks.js index 746ef4e8e..19076ff3e 100644 --- a/src/util/text-tracks.js +++ b/src/util/text-tracks.js @@ -249,28 +249,57 @@ export const addDaterangeMetadata = ({ mediaPlaylist, timestampOffset }) => { - if (!mediaPlaylist) { - return; - } - const Cue = window.WebKitDataCue || window.VTTCue; const metadataTrack = inbandTextTracks.metadataTrack_; - if (!metadataTrack) { + if (!mediaPlaylist || !metadataTrack) { return; } const dateRanges = mediaPlaylist.daterange; + const dateRangeClasses = {}; + let classList; + let classListIndex; + let startTime; + let endTime; + + dateRanges.forEach((dateRange)=>{ + startTime = timestampOffset + (new Date(dateRange.startDate).getTime() - new Date(mediaPlaylist.dateTimeObject).getTime()) / 1000; + dateRange.startTime = startTime; + + if (dateRange.class) { + if (dateRangeClasses[dateRange.class]) { + dateRangeClasses[dateRange.class].push(dateRange); + } else { + dateRangeClasses[dateRange.class] = [dateRange]; + } + } + }); dateRanges.forEach((dateRange) => { - const startTime = timestampOffset + (new Date(dateRange.startDate).getTime() - new Date(mediaPlaylist.dateTimeObject).getTime()) / 1000; - const endTime = dateRange.endDate ? (new Date(dateRange.endDate).getTime() - new Date(mediaPlaylist.dateTimeObject).getTime()) / 1000 : 0; + if (dateRange.class) { + classList = dateRangeClasses[dateRange.class]; + classListIndex = classList.indexOf(dateRange); + } + + if (dateRange.endDate) { + endTime = (new Date(dateRange.endDate).getTime() - new Date(mediaPlaylist.dateTimeObject).getTime()) / 1000; + } else if (dateRange.endOnNext && classList[classListIndex + 1]) { + endTime = classList[classListIndex + 1].startTime; + } else if (dateRange.duration) { + endTime = startTime + dateRange.duration; + } else if (dateRange.plannedDuration) { + endTime = startTime + dateRange.plannedDuration; + } else { + endTime = 0; + } + const cue = new Cue(startTime, endTime, ''); cue.id = dateRange.id; cue.type = 'com.apple.quicktime.HLS'; Object.keys(dateRange).forEach((key) => { - if (!['id', 'class', 'startDate', 'duration', 'endDate', 'endOnNext'].includes(key)) { + if (!['id', 'class', 'startDate', 'duration', 'endDate', 'endOnNext', 'startTime'].includes(key)) { if (key === 'scte35Out') { dateRange[key] = new Uint8Array(dateRange[key].match(/[\da-f]{2}/gi)).buffer; } diff --git a/test/text-tracks.test.js b/test/text-tracks.test.js index c39894a0d..57d326354 100644 --- a/test/text-tracks.test.js +++ b/test/text-tracks.test.js @@ -5,7 +5,8 @@ import { addCaptionData, createMetadataTrackIfNotExists, addMetadata, - removeDuplicateCuesFromTrack + removeDuplicateCuesFromTrack, + addDaterangeMetadata } from '../src/util/text-tracks'; const { module, test } = Qunit; @@ -273,6 +274,104 @@ test('does nothing if there is no metadataTrack or no metadata cues given', func ); }); +test('daterange text track cues - endDate is used for endTime calculation', function(assert) { + const inbandTextTracks = { + metadataTrack_: new MockTextTrack() + }; + + addDaterangeMetadata({ + inbandTextTracks, + mediaPlaylist: { + daterange: [{ + startDate: new Date(0), + endDate: new Date(2000), + scte35Out: '0xFC30200000FFF00F0500D4DF747FFFFE0034BC00C00000E4612424', + id: 'testId' + }], + dateTimeObject: 5 + }, + timestampOffset: 10 + }); + + assert.ok(inbandTextTracks.metadataTrack_, 'metadataTrack exists'); + assert.equal(inbandTextTracks.metadataTrack_.cues[0].endTime, 1.995, 'endDate is used when available'); +}); + +test('daterange text track cues - duration is used for endTime calculation', function(assert) { + const inbandTextTracks = { + metadataTrack_: new MockTextTrack() + }; + + addDaterangeMetadata({ + inbandTextTracks, + mediaPlaylist: { + daterange: [{ + startDate: new Date(10), + scte35Out: '0xFC30200000FFF00F0500D4DF747FFFFE0034BC00C00000E4612424', + duration: 40, + id: 'testId' + }], + dateTimeObject: 5 + }, + timestampOffset: 10 + }); + + assert.ok(inbandTextTracks.metadataTrack_, 'metadataTrack exists'); + assert.equal(inbandTextTracks.metadataTrack_.cues[0].endTime, 40.005, 'duration is used when endDate and class not available'); +}); + +test('daterange text track cues - plannedDuration is used for endTime calculation', function(assert) { + const inbandTextTracks = { + metadataTrack_: new MockTextTrack() + }; + + addDaterangeMetadata({ + inbandTextTracks, + mediaPlaylist: { + daterange: [{ + startDate: new Date(2000), + scte35Out: '0xFC30200000FFF00F0500D4DF747FFFFE0034BC00C00000E4612424', + plannedDuration: 15, + id: 'testId' + }], + dateTimeObject: 1000 + }, + timestampOffset: 9 + }); + + assert.ok(inbandTextTracks.metadataTrack_, 'metadataTrack exists'); + assert.equal(inbandTextTracks.metadataTrack_.cues[0].endTime, 25, 'plannedDuration is used when endDate, class and duration not available'); +}); + +test('daterange text track cues - endOnNext and classList are used', function(assert) { + const inbandTextTracks = { + metadataTrack_: new MockTextTrack() + }; + + addDaterangeMetadata({ + inbandTextTracks, + mediaPlaylist: { + daterange: [{ + startDate: new Date(2000), + scte35Out: '0xFC30200000FFF00F0500D4DF747FFFFE0034BC00C00000E4612424', + id: 'testId1', + class: 'TestClass', + endOnNext: true + }, { + startDate: new Date(4000), + scte35Out: '0xFC30200000FFF00F0500D4DF747FFFFE0034BC00C00000E46', + id: 'testId2', + class: 'TestClass' + }], + dateTimeObject: 1000 + }, + timestampOffset: 9 + }); + + assert.ok(inbandTextTracks.metadataTrack_, 'metadataTrack exists'); + assert.equal(inbandTextTracks.metadataTrack_.cues[0].endTime, 12, 'startTime of the next dateRange with the same class is used as endTime'); +}); + test('adds cues for each metadata frame seen', function(assert) { const tech = new MockTech(); const inbandTextTracks = {};