From 986071b7688536a1906cb3abdc17489d9cdd0eb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Velad=20Galv=C3=A1n?= Date: Wed, 10 Apr 2024 12:22:58 +0200 Subject: [PATCH] feat: Make getPlayheadTimeAsDate and getPresentationStartTimeAsDate available for VOD (#6417) Closes https://github.com/shaka-project/shaka-player/issues/2959 --- lib/hls/hls_parser.js | 32 +++++++++++++------------- lib/player.js | 21 ++++------------- test/player_unit.js | 53 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 33 deletions(-) diff --git a/lib/hls/hls_parser.js b/lib/hls/hls_parser.js index 8a167f01b3..b1b462f344 100644 --- a/lib/hls/hls_parser.js +++ b/lib/hls/hls_parser.js @@ -1023,27 +1023,27 @@ shaka.hls.HlsParser = class { this.presentationTimeline_.setSegmentAvailabilityDuration( segmentAvailabilityDuration); } - - if (!this.presentationTimeline_.isStartTimeLocked()) { - for (const streamInfo of this.uriToStreamInfosMap_.values()) { - if (!streamInfo.stream.segmentIndex) { - continue; // Not active. - } - if (streamInfo.type != 'audio' && streamInfo.type != 'video') { - continue; - } - const firstReference = streamInfo.stream.segmentIndex.get(0); - if (firstReference && firstReference.syncTime) { - const syncTime = firstReference.syncTime; - this.presentationTimeline_.setInitialProgramDateTime(syncTime); - } - } - } } else { // Use the minimum duration as the presentation duration. this.presentationTimeline_.setDuration(this.getMinDuration_()); } + if (!this.presentationTimeline_.isStartTimeLocked()) { + for (const streamInfo of this.uriToStreamInfosMap_.values()) { + if (!streamInfo.stream.segmentIndex) { + continue; // Not active. + } + if (streamInfo.type != 'audio' && streamInfo.type != 'video') { + continue; + } + const firstReference = streamInfo.stream.segmentIndex.get(0); + if (firstReference && firstReference.syncTime) { + const syncTime = firstReference.syncTime; + this.presentationTimeline_.setInitialProgramDateTime(syncTime); + } + } + } + // This is the first point where we have a meaningful presentation start // time, and we need to tell PresentationTimeline that so that it can // maintain consistency from here on. diff --git a/lib/player.js b/lib/player.js index f50faa4070..4b36eaed8b 100644 --- a/lib/player.js +++ b/lib/player.js @@ -4697,19 +4697,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget { } /** - * Get the current playhead position as a date. This should only be called - * when the player has loaded a live stream. If the player has not loaded a - * live stream, this will return null. + * Get the current playhead position as a date. * * @return {Date} * @export */ getPlayheadTimeAsDate() { - if (!this.isLive()) { - shaka.log.warning('getPlayheadTimeAsDate is for live streams!'); - return null; - } - let presentationTime = 0; if (this.playhead_) { presentationTime = this.playhead_.getTime(); @@ -4746,22 +4739,16 @@ shaka.Player = class extends shaka.util.FakeEventTarget { } /** - * Get the presentation start time as a date. This should only be called when - * the player has loaded a live stream. If the player has not loaded a live - * stream, this will return null. + * Get the presentation start time as a date. * * @return {Date} * @export */ getPresentationStartTimeAsDate() { - if (!this.isLive()) { - shaka.log.warning('getPresentationStartTimeAsDate is for live streams!'); - return null; - } - if (this.manifest_) { const timeline = this.manifest_.presentationTimeline; - const startTime = timeline.getPresentationStartTime(); + const startTime = timeline.getInitialProgramDateTime() || + timeline.getPresentationStartTime(); goog.asserts.assert(startTime != null, 'Presentation start time should not be null!'); return new Date(/* ms= */ startTime * 1000); diff --git a/test/player_unit.js b/test/player_unit.js index 0d6892001b..725ec1f6f0 100644 --- a/test/player_unit.js +++ b/test/player_unit.js @@ -3839,6 +3839,59 @@ describe('Player', () => { // (100 (program date time) + 20 (playhead time)) * 1000 (ms/sec) expect(liveTimeUtc).toEqual(new Date(120 * 1000)); }); + + it('uses program date time for VoD', () => { + timeline.setStatic(true); + timeline.setInitialProgramDateTime(100); + playhead.getTime.and.returnValue(20); + + const liveTimeUtc = player.getPlayheadTimeAsDate(); + // (100 (program date time) + 20 (playhead time)) * 1000 (ms/sec) + expect(liveTimeUtc).toEqual(new Date(120 * 1000)); + }); + }); + + describe('getPresentationStartTimeAsDate()', () => { + /** @type {?shaka.media.PresentationTimeline} */ + let timeline; + beforeEach(async () => { + timeline = new shaka.media.PresentationTimeline(300, 0); + timeline.setStatic(false); + + manifest = shaka.test.ManifestGenerator.generate((manifest) => { + goog.asserts.assert(timeline, 'timeline must be non-null'); + manifest.presentationTimeline = timeline; + manifest.addVariant(0, (variant) => { + variant.addVideo(1); + }); + }); + + goog.asserts.assert(manifest, 'manifest must be non-null'); + await player.load(fakeManifestUri, 0, fakeMimeType); + }); + + it('gets current wall clock time in UTC', () => { + const liveTimeUtc = player.getPresentationStartTimeAsDate(); + // 300 (presentation start time) * 1000 (ms/sec) + expect(liveTimeUtc).toEqual(new Date(300 * 1000)); + }); + + it('uses program date time', () => { + timeline.setInitialProgramDateTime(100); + + const liveTimeUtc = player.getPresentationStartTimeAsDate(); + // 100 (program date time) * 1000 (ms/sec) + expect(liveTimeUtc).toEqual(new Date(100 * 1000)); + }); + + it('uses program date time for VoD', () => { + timeline.setStatic(true); + timeline.setInitialProgramDateTime(100); + + const liveTimeUtc = player.getPlayheadTimeAsDate(); + // 100 (program date time) * 1000 (ms/sec) + expect(liveTimeUtc).toEqual(new Date(100 * 1000)); + }); }); describe('getSegmentAvailabilityDuration()', () => {