From 0de4b8d13e3e22e6c92603b673d5e28d7c857d10 Mon Sep 17 00:00:00 2001 From: Armand Zangue Date: Mon, 23 Sep 2024 01:37:39 +0200 Subject: [PATCH 1/8] wip: video progress events --- lib/player.js | 111 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 78 insertions(+), 33 deletions(-) diff --git a/lib/player.js b/lib/player.js index 64c3013596..ebade87980 100644 --- a/lib/player.js +++ b/lib/player.js @@ -763,7 +763,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget { this.externalSrcEqualsThumbnailsStreams_ = []; /** @private {number} */ - this.completionPercent_ = NaN; + this.completionPercent_ = -1; /** @private {?shaka.extern.PlayerConfiguration} */ this.config_ = this.defaultConfig_(); @@ -7032,42 +7032,87 @@ shaka.Player = class extends shaka.util.FakeEventTarget { if (!this.video_) { return; } + + const isCloseEnough = (n1, n2) => { + const epsilon = 0.01 || Number.EPSILON || 2.220446049250313e-16; + return Math.abs(n1 - n2) < epsilon; + } + + const isQuartile = (quartilePercent, currentPercent) => { + if ( + isCloseEnough(quartilePercent, currentPercent) && + this.completionPercent_ < quartilePercent + ) { + this.completionPercent_ = quartilePercent; + return true; + } + return false; + } + let hasNewCompletionPercent = false; const completionRatio = this.video_.currentTime / this.video_.duration; - if (!isNaN(completionRatio)) { - const percent = Math.round(100 * completionRatio); - if (isNaN(this.completionPercent_)) { - this.completionPercent_ = percent; - hasNewCompletionPercent = true; - } else { - const newCompletionPercent = Math.max(this.completionPercent_, percent); - if (this.completionPercent_ != newCompletionPercent) { - this.completionPercent_ = newCompletionPercent; - hasNewCompletionPercent = true; - } - } + + if (isNaN(completionRatio)) { + return; } - if (hasNewCompletionPercent) { - let event; - if (this.completionPercent_ == 0) { - event = shaka.Player.makeEvent_(shaka.util.FakeEvent.EventName.Started); - } else if (this.completionPercent_ == 25) { - event = shaka.Player.makeEvent_( - shaka.util.FakeEvent.EventName.FirstQuartile); - } else if (this.completionPercent_ == 50) { - event = shaka.Player.makeEvent_( - shaka.util.FakeEvent.EventName.Midpoint); - } else if (this.completionPercent_ == 75) { - event = shaka.Player.makeEvent_( - shaka.util.FakeEvent.EventName.ThirdQuartile); - } else if (this.completionPercent_ == 100) { - event = shaka.Player.makeEvent_( - shaka.util.FakeEvent.EventName.Complete); - } - if (event) { - this.dispatchEvent(event); - } + + let event; + const percent = completionRatio * 100; + + if (isQuartile(0, percent)) { + event = shaka.Player.makeEvent_(shaka.util.FakeEvent.EventName.Started); + } else if (isQuartile(25, percent)) { + event = shaka.Player.makeEvent_( + shaka.util.FakeEvent.EventName.FirstQuartile); + } else if (isQuartile(50, percent)) { + event = shaka.Player.makeEvent_( + shaka.util.FakeEvent.EventName.Midpoint); + } else if (isQuartile(75, percent)) { + event = shaka.Player.makeEvent_( + shaka.util.FakeEvent.EventName.ThirdQuartile); + } else if (isQuartile(100, percent)) { + event = shaka.Player.makeEvent_( + shaka.util.FakeEvent.EventName.Complete); } + + if (event) { + this.dispatchEvent(event); + } + + // if (!isNaN(completionRatio)) { + // const percent = Math.round(100 * completionRatio); + // if (isNaN(this.completionPercent_)) { + // this.completionPercent_ = percent; + // hasNewCompletionPercent = true; + // } else { + // const newCompletionPercent = Math.max(this.completionPercent_, percent); + // if (this.completionPercent_ != newCompletionPercent) { + // this.completionPercent_ = newCompletionPercent; + // hasNewCompletionPercent = true; + // } + // } + // } + // if (hasNewCompletionPercent) { + // let event; + // if (this.completionPercent_ == 0) { + // event = shaka.Player.makeEvent_(shaka.util.FakeEvent.EventName.Started); + // } else if (this.completionPercent_ == 25) { + // event = shaka.Player.makeEvent_( + // shaka.util.FakeEvent.EventName.FirstQuartile); + // } else if (this.completionPercent_ == 50) { + // event = shaka.Player.makeEvent_( + // shaka.util.FakeEvent.EventName.Midpoint); + // } else if (this.completionPercent_ == 75) { + // event = shaka.Player.makeEvent_( + // shaka.util.FakeEvent.EventName.ThirdQuartile); + // } else if (this.completionPercent_ == 100) { + // event = shaka.Player.makeEvent_( + // shaka.util.FakeEvent.EventName.Complete); + // } + // if (event) { + // this.dispatchEvent(event); + // } + // } } /** From 201be2302822331dd06ad60a9b065ac2f01fd1b6 Mon Sep 17 00:00:00 2001 From: Armand Zangue Date: Tue, 24 Sep 2024 23:48:58 +0200 Subject: [PATCH 2/8] good empirical value for epsilon --- lib/player.js | 40 ++-------------------------------------- 1 file changed, 2 insertions(+), 38 deletions(-) diff --git a/lib/player.js b/lib/player.js index ebade87980..4e6f782e1b 100644 --- a/lib/player.js +++ b/lib/player.js @@ -7034,7 +7034,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget { } const isCloseEnough = (n1, n2) => { - const epsilon = 0.01 || Number.EPSILON || 2.220446049250313e-16; + const epsilon = 0.1; return Math.abs(n1 - n2) < epsilon; } @@ -7049,16 +7049,15 @@ shaka.Player = class extends shaka.util.FakeEventTarget { return false; } - let hasNewCompletionPercent = false; const completionRatio = this.video_.currentTime / this.video_.duration; if (isNaN(completionRatio)) { return; } - let event; const percent = completionRatio * 100; + let event; if (isQuartile(0, percent)) { event = shaka.Player.makeEvent_(shaka.util.FakeEvent.EventName.Started); } else if (isQuartile(25, percent)) { @@ -7078,41 +7077,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget { if (event) { this.dispatchEvent(event); } - - // if (!isNaN(completionRatio)) { - // const percent = Math.round(100 * completionRatio); - // if (isNaN(this.completionPercent_)) { - // this.completionPercent_ = percent; - // hasNewCompletionPercent = true; - // } else { - // const newCompletionPercent = Math.max(this.completionPercent_, percent); - // if (this.completionPercent_ != newCompletionPercent) { - // this.completionPercent_ = newCompletionPercent; - // hasNewCompletionPercent = true; - // } - // } - // } - // if (hasNewCompletionPercent) { - // let event; - // if (this.completionPercent_ == 0) { - // event = shaka.Player.makeEvent_(shaka.util.FakeEvent.EventName.Started); - // } else if (this.completionPercent_ == 25) { - // event = shaka.Player.makeEvent_( - // shaka.util.FakeEvent.EventName.FirstQuartile); - // } else if (this.completionPercent_ == 50) { - // event = shaka.Player.makeEvent_( - // shaka.util.FakeEvent.EventName.Midpoint); - // } else if (this.completionPercent_ == 75) { - // event = shaka.Player.makeEvent_( - // shaka.util.FakeEvent.EventName.ThirdQuartile); - // } else if (this.completionPercent_ == 100) { - // event = shaka.Player.makeEvent_( - // shaka.util.FakeEvent.EventName.Complete); - // } - // if (event) { - // this.dispatchEvent(event); - // } - // } } /** From d06831c94a9e14137cc5624c3965e55f28aaf7f6 Mon Sep 17 00:00:00 2001 From: Armand Zangue Date: Wed, 13 Nov 2024 01:53:34 +0100 Subject: [PATCH 3/8] add number utils --- build/types/core | 1 + lib/player.js | 22 +++++++++++++------- lib/util/number_utils.js | 37 ++++++++++++++++++++++++++++++++++ test/util/number_utils_unit.js | 20 ++++++++++++++++++ 4 files changed, 73 insertions(+), 7 deletions(-) create mode 100644 lib/util/number_utils.js create mode 100644 test/util/number_utils_unit.js diff --git a/build/types/core b/build/types/core index 23d04a5c8f..5f322c0e7d 100644 --- a/build/types/core +++ b/build/types/core @@ -105,6 +105,7 @@ +../../lib/util/multi_map.js +../../lib/util/mutex.js +../../lib/util/networking.js ++../../lib/util/number_utils.js +../../lib/util/object_utils.js +../../lib/util/operation_manager.js +../../lib/util/periods.js diff --git a/lib/player.js b/lib/player.js index 4e6f782e1b..7bc4294c77 100644 --- a/lib/player.js +++ b/lib/player.js @@ -58,6 +58,7 @@ goog.require('shaka.util.ManifestParserUtils'); goog.require('shaka.util.MediaReadyState'); goog.require('shaka.util.MimeUtils'); goog.require('shaka.util.Mutex'); +goog.require('shaka.util.NumberUtils'); goog.require('shaka.util.ObjectUtils'); goog.require('shaka.util.Platform'); goog.require('shaka.util.PlayerConfiguration'); @@ -2760,6 +2761,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget { const onVideoProgress = () => this.onVideoProgress_(); this.loadEventManager_.listen( mediaElement, 'timeupdate', onVideoProgress); + this.loadEventManager_.listen( + mediaElement, 'ended', onVideoProgress); this.onVideoProgress_(); if (this.manifest_.nextUrl) { if (this.config_.streaming.preloadNextUrlWindow > 0) { @@ -3156,6 +3159,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget { const onVideoProgress = () => this.onVideoProgress_(); this.loadEventManager_.listen( mediaElement, 'timeupdate', onVideoProgress); + this.loadEventManager_.listen( + mediaElement, 'ended', onVideoProgress); this.onVideoProgress_(); } @@ -7033,21 +7038,24 @@ shaka.Player = class extends shaka.util.FakeEventTarget { return; } - const isCloseEnough = (n1, n2) => { - const epsilon = 0.1; - return Math.abs(n1 - n2) < epsilon; - } - const isQuartile = (quartilePercent, currentPercent) => { + const NumberUtils = shaka.util.NumberUtils; + /** + * According to MDN, depending on system load, the |timeupdate| event will + * be thrown with a frequency between about 4Hz and 66Hz. A tolerance + * margin of 0.3 should be good enough to not miss any quartile point. + */ + const tolerance = 0.3; + if ( - isCloseEnough(quartilePercent, currentPercent) && + NumberUtils.isFloatEqual(quartilePercent, currentPercent, tolerance) && this.completionPercent_ < quartilePercent ) { this.completionPercent_ = quartilePercent; return true; } return false; - } + }; const completionRatio = this.video_.currentTime / this.video_.duration; diff --git a/lib/util/number_utils.js b/lib/util/number_utils.js new file mode 100644 index 0000000000..f3aa3e37c7 --- /dev/null +++ b/lib/util/number_utils.js @@ -0,0 +1,37 @@ +/*! @license + * Shaka Player + * Copyright 2016 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +goog.provide('shaka.util.NumberUtils'); + + +shaka.util.NumberUtils = class { + /** + * Compare two float numbers, taking a configurable tolerance margin into + * account. + * + * @param {number} a + * @param {number} b + * @param {number=} tolerance + * @return {boolean} + */ + static isFloatEqual(a, b, tolerance = Number.EPSILON) { + if (a === b) { + return true; + } + + const error = Math.abs(a - b); + + if (error <= tolerance) { + return true; + } + + if (tolerance !== Number.EPSILON) { + return Math.abs(error - tolerance) <= Number.EPSILON; + } + + return false; + } +}; diff --git a/test/util/number_utils_unit.js b/test/util/number_utils_unit.js new file mode 100644 index 0000000000..647dfa3383 --- /dev/null +++ b/test/util/number_utils_unit.js @@ -0,0 +1,20 @@ +/*! @license + * Shaka Player + * Copyright 2016 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +describe('NumberUtils', () => { + const NumberUtils = shaka.util.NumberUtils; + + it('compares float', () => { + expect(NumberUtils.isFloatEqual(0.1 + 0.2, 0.3)).toBe(true); + expect(NumberUtils.isFloatEqual(0.4 - 0.1, 0.3)).toBe(true); + expect(NumberUtils.isFloatEqual(0.0004, 0.0003)).toBe(false); + }); + + it('respects provided tolerance margin', () => { + expect(NumberUtils.isFloatEqual(1.5, 1.4)).toBe(false); + expect(NumberUtils.isFloatEqual(1.5, 1.4, 0.1)).toBe(true); + }); +}); From 63372da9f88d97796241b0d86b0f6f9ead408a81 Mon Sep 17 00:00:00 2001 From: Armand Zangue Date: Fri, 22 Nov 2024 20:05:00 +0100 Subject: [PATCH 4/8] use default epsilon tolerance --- lib/player.js | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/lib/player.js b/lib/player.js index 7bc4294c77..c2508e32ee 100644 --- a/lib/player.js +++ b/lib/player.js @@ -1526,7 +1526,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget { this.externalSrcEqualsThumbnailsStreams_ = []; - this.completionPercent_ = NaN; + this.completionPercent_ = -1; // Make sure that the app knows of the new buffering state. this.updateBufferState_(); @@ -7040,15 +7040,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget { const isQuartile = (quartilePercent, currentPercent) => { const NumberUtils = shaka.util.NumberUtils; - /** - * According to MDN, depending on system load, the |timeupdate| event will - * be thrown with a frequency between about 4Hz and 66Hz. A tolerance - * margin of 0.3 should be good enough to not miss any quartile point. - */ - const tolerance = 0.3; if ( - NumberUtils.isFloatEqual(quartilePercent, currentPercent, tolerance) && + (NumberUtils.isFloatEqual(quartilePercent, currentPercent) || + currentPercent > quartilePercent) && this.completionPercent_ < quartilePercent ) { this.completionPercent_ = quartilePercent; From ef5755f6e67ee689ec73354c1a32a93d7d7b3a43 Mon Sep 17 00:00:00 2001 From: Armand Zangue Date: Fri, 22 Nov 2024 20:12:35 +0100 Subject: [PATCH 5/8] add more event of interest --- lib/player.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/player.js b/lib/player.js index c2508e32ee..a7d8c0e989 100644 --- a/lib/player.js +++ b/lib/player.js @@ -2761,6 +2761,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget { const onVideoProgress = () => this.onVideoProgress_(); this.loadEventManager_.listen( mediaElement, 'timeupdate', onVideoProgress); + this.loadEventManager_.listen( + mediaElement, 'playing', onVideoProgress); this.loadEventManager_.listen( mediaElement, 'ended', onVideoProgress); this.onVideoProgress_(); @@ -3159,6 +3161,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget { const onVideoProgress = () => this.onVideoProgress_(); this.loadEventManager_.listen( mediaElement, 'timeupdate', onVideoProgress); + this.loadEventManager_.listen( + mediaElement, 'playing', onVideoProgress); this.loadEventManager_.listen( mediaElement, 'ended', onVideoProgress); this.onVideoProgress_(); From a897e3d576ce73fe45fc50e20fb3ad2ce20361f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?/z=C9=92=CC=83ge/?= Date: Mon, 25 Nov 2024 17:48:57 +0100 Subject: [PATCH 6/8] code styling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Álvaro Velad Galván --- lib/player.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/player.js b/lib/player.js index a7d8c0e989..13d643dadb 100644 --- a/lib/player.js +++ b/lib/player.js @@ -7045,11 +7045,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget { const isQuartile = (quartilePercent, currentPercent) => { const NumberUtils = shaka.util.NumberUtils; - if ( - (NumberUtils.isFloatEqual(quartilePercent, currentPercent) || + if ((NumberUtils.isFloatEqual(quartilePercent, currentPercent) || currentPercent > quartilePercent) && - this.completionPercent_ < quartilePercent - ) { + this.completionPercent_ < quartilePercent) { this.completionPercent_ = quartilePercent; return true; } From 55b6994d6826b9ac9379c1f724df9e1017d0223c Mon Sep 17 00:00:00 2001 From: Armand Zangue Date: Mon, 25 Nov 2024 17:54:22 +0100 Subject: [PATCH 7/8] disregard playing event for video progress --- lib/player.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/player.js b/lib/player.js index 13d643dadb..a9658f6e2b 100644 --- a/lib/player.js +++ b/lib/player.js @@ -2761,8 +2761,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget { const onVideoProgress = () => this.onVideoProgress_(); this.loadEventManager_.listen( mediaElement, 'timeupdate', onVideoProgress); - this.loadEventManager_.listen( - mediaElement, 'playing', onVideoProgress); this.loadEventManager_.listen( mediaElement, 'ended', onVideoProgress); this.onVideoProgress_(); @@ -3161,8 +3159,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget { const onVideoProgress = () => this.onVideoProgress_(); this.loadEventManager_.listen( mediaElement, 'timeupdate', onVideoProgress); - this.loadEventManager_.listen( - mediaElement, 'playing', onVideoProgress); this.loadEventManager_.listen( mediaElement, 'ended', onVideoProgress); this.onVideoProgress_(); From 65314d124e5b10ad6d506dd84f5a22b85098966e Mon Sep 17 00:00:00 2001 From: Armand Zangue Date: Mon, 25 Nov 2024 17:59:27 +0100 Subject: [PATCH 8/8] disregard ended event for video progress --- lib/player.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/player.js b/lib/player.js index a9658f6e2b..17dcf2e2e8 100644 --- a/lib/player.js +++ b/lib/player.js @@ -2761,8 +2761,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget { const onVideoProgress = () => this.onVideoProgress_(); this.loadEventManager_.listen( mediaElement, 'timeupdate', onVideoProgress); - this.loadEventManager_.listen( - mediaElement, 'ended', onVideoProgress); this.onVideoProgress_(); if (this.manifest_.nextUrl) { if (this.config_.streaming.preloadNextUrlWindow > 0) { @@ -3159,8 +3157,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget { const onVideoProgress = () => this.onVideoProgress_(); this.loadEventManager_.listen( mediaElement, 'timeupdate', onVideoProgress); - this.loadEventManager_.listen( - mediaElement, 'ended', onVideoProgress); this.onVideoProgress_(); }