Skip to content

Commit 5f8e958

Browse files
zangueavelad
andauthored
fix: Fix video progress events accuracy (#7654)
Due do rounding errors the progress events were fired way too early (especially the complete event on longer videos). This changes use float comparison to mitigate the issue. Further improvements (video with start time, seek handling) will be added in follow up PRs. Co-authored-by: Álvaro Velad Galván <ladvan91@hotmail.com>
1 parent d09cd7e commit 5f8e958

File tree

4 files changed

+99
-35
lines changed

4 files changed

+99
-35
lines changed

build/types/core

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@
105105
+../../lib/util/multi_map.js
106106
+../../lib/util/mutex.js
107107
+../../lib/util/networking.js
108+
+../../lib/util/number_utils.js
108109
+../../lib/util/object_utils.js
109110
+../../lib/util/operation_manager.js
110111
+../../lib/util/periods.js

lib/player.js

Lines changed: 41 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ goog.require('shaka.util.ManifestParserUtils');
5858
goog.require('shaka.util.MediaReadyState');
5959
goog.require('shaka.util.MimeUtils');
6060
goog.require('shaka.util.Mutex');
61+
goog.require('shaka.util.NumberUtils');
6162
goog.require('shaka.util.ObjectUtils');
6263
goog.require('shaka.util.Platform');
6364
goog.require('shaka.util.PlayerConfiguration');
@@ -763,7 +764,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
763764
this.externalSrcEqualsThumbnailsStreams_ = [];
764765

765766
/** @private {number} */
766-
this.completionPercent_ = NaN;
767+
this.completionPercent_ = -1;
767768

768769
/** @private {?shaka.extern.PlayerConfiguration} */
769770
this.config_ = this.defaultConfig_();
@@ -1525,7 +1526,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
15251526

15261527
this.externalSrcEqualsThumbnailsStreams_ = [];
15271528

1528-
this.completionPercent_ = NaN;
1529+
this.completionPercent_ = -1;
15291530

15301531
if (this.networkingEngine_) {
15311532
this.networkingEngine_.clearCommonAccessTokenMap();
@@ -7036,41 +7037,46 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
70367037
if (!this.video_) {
70377038
return;
70387039
}
7039-
let hasNewCompletionPercent = false;
7040-
const completionRatio = this.video_.currentTime / this.video_.duration;
7041-
if (!isNaN(completionRatio)) {
7042-
const percent = Math.round(100 * completionRatio);
7043-
if (isNaN(this.completionPercent_)) {
7044-
this.completionPercent_ = percent;
7045-
hasNewCompletionPercent = true;
7046-
} else {
7047-
const newCompletionPercent = Math.max(this.completionPercent_, percent);
7048-
if (this.completionPercent_ != newCompletionPercent) {
7049-
this.completionPercent_ = newCompletionPercent;
7050-
hasNewCompletionPercent = true;
7051-
}
7040+
7041+
const isQuartile = (quartilePercent, currentPercent) => {
7042+
const NumberUtils = shaka.util.NumberUtils;
7043+
7044+
if ((NumberUtils.isFloatEqual(quartilePercent, currentPercent) ||
7045+
currentPercent > quartilePercent) &&
7046+
this.completionPercent_ < quartilePercent) {
7047+
this.completionPercent_ = quartilePercent;
7048+
return true;
70527049
}
7050+
return false;
7051+
};
7052+
7053+
const completionRatio = this.video_.currentTime / this.video_.duration;
7054+
7055+
if (isNaN(completionRatio)) {
7056+
return;
70537057
}
7054-
if (hasNewCompletionPercent) {
7055-
let event;
7056-
if (this.completionPercent_ == 0) {
7057-
event = shaka.Player.makeEvent_(shaka.util.FakeEvent.EventName.Started);
7058-
} else if (this.completionPercent_ == 25) {
7059-
event = shaka.Player.makeEvent_(
7060-
shaka.util.FakeEvent.EventName.FirstQuartile);
7061-
} else if (this.completionPercent_ == 50) {
7062-
event = shaka.Player.makeEvent_(
7063-
shaka.util.FakeEvent.EventName.Midpoint);
7064-
} else if (this.completionPercent_ == 75) {
7065-
event = shaka.Player.makeEvent_(
7066-
shaka.util.FakeEvent.EventName.ThirdQuartile);
7067-
} else if (this.completionPercent_ == 100) {
7068-
event = shaka.Player.makeEvent_(
7069-
shaka.util.FakeEvent.EventName.Complete);
7070-
}
7071-
if (event) {
7072-
this.dispatchEvent(event);
7073-
}
7058+
7059+
const percent = completionRatio * 100;
7060+
7061+
let event;
7062+
if (isQuartile(0, percent)) {
7063+
event = shaka.Player.makeEvent_(shaka.util.FakeEvent.EventName.Started);
7064+
} else if (isQuartile(25, percent)) {
7065+
event = shaka.Player.makeEvent_(
7066+
shaka.util.FakeEvent.EventName.FirstQuartile);
7067+
} else if (isQuartile(50, percent)) {
7068+
event = shaka.Player.makeEvent_(
7069+
shaka.util.FakeEvent.EventName.Midpoint);
7070+
} else if (isQuartile(75, percent)) {
7071+
event = shaka.Player.makeEvent_(
7072+
shaka.util.FakeEvent.EventName.ThirdQuartile);
7073+
} else if (isQuartile(100, percent)) {
7074+
event = shaka.Player.makeEvent_(
7075+
shaka.util.FakeEvent.EventName.Complete);
7076+
}
7077+
7078+
if (event) {
7079+
this.dispatchEvent(event);
70747080
}
70757081
}
70767082

lib/util/number_utils.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*! @license
2+
* Shaka Player
3+
* Copyright 2016 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
goog.provide('shaka.util.NumberUtils');
8+
9+
10+
shaka.util.NumberUtils = class {
11+
/**
12+
* Compare two float numbers, taking a configurable tolerance margin into
13+
* account.
14+
*
15+
* @param {number} a
16+
* @param {number} b
17+
* @param {number=} tolerance
18+
* @return {boolean}
19+
*/
20+
static isFloatEqual(a, b, tolerance = Number.EPSILON) {
21+
if (a === b) {
22+
return true;
23+
}
24+
25+
const error = Math.abs(a - b);
26+
27+
if (error <= tolerance) {
28+
return true;
29+
}
30+
31+
if (tolerance !== Number.EPSILON) {
32+
return Math.abs(error - tolerance) <= Number.EPSILON;
33+
}
34+
35+
return false;
36+
}
37+
};

test/util/number_utils_unit.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*! @license
2+
* Shaka Player
3+
* Copyright 2016 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
describe('NumberUtils', () => {
8+
const NumberUtils = shaka.util.NumberUtils;
9+
10+
it('compares float', () => {
11+
expect(NumberUtils.isFloatEqual(0.1 + 0.2, 0.3)).toBe(true);
12+
expect(NumberUtils.isFloatEqual(0.4 - 0.1, 0.3)).toBe(true);
13+
expect(NumberUtils.isFloatEqual(0.0004, 0.0003)).toBe(false);
14+
});
15+
16+
it('respects provided tolerance margin', () => {
17+
expect(NumberUtils.isFloatEqual(1.5, 1.4)).toBe(false);
18+
expect(NumberUtils.isFloatEqual(1.5, 1.4, 0.1)).toBe(true);
19+
});
20+
});

0 commit comments

Comments
 (0)