rx-player 3.30.1-dev.2023032800 → 3.31.0-dev.2023052200
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -2
- package/CONTRIBUTING.md +48 -168
- package/FILES.md +40 -92
- package/VERSION +1 -1
- package/dist/_esm5.processed/compat/browser_detection.d.ts +3 -1
- package/dist/_esm5.processed/compat/browser_detection.js +7 -2
- package/dist/_esm5.processed/compat/has_issues_with_high_media_source_duration.d.ts +21 -0
- package/dist/_esm5.processed/compat/has_issues_with_high_media_source_duration.js +26 -0
- package/dist/_esm5.processed/core/adaptive/adaptive_representation_selector.js +1 -2
- package/dist/_esm5.processed/core/adaptive/buffer_based_chooser.d.ts +18 -1
- package/dist/_esm5.processed/core/adaptive/buffer_based_chooser.js +105 -24
- package/dist/_esm5.processed/core/adaptive/guess_based_chooser.js +6 -6
- package/dist/_esm5.processed/core/adaptive/utils/representation_score_calculator.d.ts +19 -1
- package/dist/_esm5.processed/core/adaptive/utils/representation_score_calculator.js +1 -1
- package/dist/_esm5.processed/core/api/debug/render.js +1 -1
- package/dist/_esm5.processed/core/api/public_api.js +2 -2
- package/dist/_esm5.processed/core/api/tracks_management/media_element_track_choice_manager.js +10 -1
- package/dist/_esm5.processed/core/api/tracks_management/track_choice_manager.js +20 -117
- package/dist/_esm5.processed/core/init/media_source_content_initializer.js +28 -3
- package/dist/_esm5.processed/core/init/utils/media_source_duration_updater.d.ts +2 -2
- package/dist/_esm5.processed/core/init/utils/media_source_duration_updater.js +29 -18
- package/dist/_esm5.processed/core/segment_buffers/index.d.ts +2 -2
- package/dist/_esm5.processed/core/stream/adaptation/utils/create_representation_estimator.js +1 -1
- package/dist/_esm5.processed/core/stream/period/period_stream.js +1 -1
- package/dist/_esm5.processed/core/stream/representation/utils/append_segment_to_buffer.d.ts +4 -2
- package/dist/_esm5.processed/core/stream/representation/utils/append_segment_to_buffer.js +2 -2
- package/dist/_esm5.processed/core/stream/representation/utils/push_init_segment.js +6 -2
- package/dist/_esm5.processed/errors/index.d.ts +2 -2
- package/dist/_esm5.processed/errors/media_error.d.ts +23 -1
- package/dist/_esm5.processed/errors/media_error.js +18 -5
- package/dist/_esm5.processed/manifest/adaptation.d.ts +17 -2
- package/dist/_esm5.processed/manifest/adaptation.js +67 -1
- package/dist/_esm5.processed/manifest/manifest.d.ts +1 -1
- package/dist/_esm5.processed/manifest/manifest.js +3 -3
- package/dist/_esm5.processed/manifest/period.js +1 -1
- package/dist/_esm5.processed/manifest/representation.d.ts +11 -1
- package/dist/_esm5.processed/manifest/representation.js +16 -0
- package/dist/_esm5.processed/parsers/manifest/dash/js-parser/parse_from_document.d.ts +1 -1
- package/dist/_esm5.processed/parsers/manifest/dash/js-parser/parse_from_document.js +1 -1
- package/dist/_esm5.processed/parsers/manifest/dash/wasm-parser/ts/dash-wasm-parser.js +1 -0
- package/dist/_esm5.processed/public_types.d.ts +6 -5
- package/dist/_esm5.processed/transports/smooth/isobmff/create_boxes.d.ts +4 -6
- package/dist/_esm5.processed/transports/smooth/isobmff/create_boxes.js +4 -6
- package/dist/rx-player.js +3086 -3024
- package/dist/rx-player.min.js +1 -1
- package/package.json +25 -26
- package/sonar-project.properties +1 -1
- package/src/compat/browser_detection.ts +7 -1
- package/src/compat/has_issues_with_high_media_source_duration.ts +27 -0
- package/src/core/adaptive/__tests__/buffer_based_chooser.test.ts +147 -48
- package/src/core/adaptive/adaptive_representation_selector.ts +1 -2
- package/src/core/adaptive/buffer_based_chooser.ts +143 -25
- package/src/core/adaptive/guess_based_chooser.ts +9 -8
- package/src/core/adaptive/utils/representation_score_calculator.ts +22 -2
- package/src/core/api/debug/render.ts +1 -1
- package/src/core/api/public_api.ts +2 -2
- package/src/core/api/tracks_management/media_element_track_choice_manager.ts +17 -8
- package/src/core/api/tracks_management/track_choice_manager.ts +20 -132
- package/src/core/init/media_source_content_initializer.ts +32 -4
- package/src/core/init/utils/media_source_duration_updater.ts +31 -19
- package/src/core/segment_buffers/index.ts +2 -0
- package/src/core/stream/adaptation/utils/create_representation_estimator.ts +2 -1
- package/src/core/stream/period/period_stream.ts +2 -1
- package/src/core/stream/representation/utils/append_segment_to_buffer.ts +8 -3
- package/src/core/stream/representation/utils/push_init_segment.ts +7 -1
- package/src/errors/__tests__/media_error.test.ts +6 -6
- package/src/errors/index.ts +4 -1
- package/src/errors/media_error.ts +67 -1
- package/src/manifest/__tests__/period.test.ts +90 -45
- package/src/manifest/adaptation.ts +77 -2
- package/src/manifest/manifest.ts +5 -3
- package/src/manifest/period.ts +2 -1
- package/src/manifest/representation.ts +23 -1
- package/src/parsers/manifest/dash/js-parser/parse_from_document.ts +1 -1
- package/src/parsers/manifest/dash/wasm-parser/ts/dash-wasm-parser.ts +1 -0
- package/src/parsers/texttracks/ttml/parse_ttml.ts +1 -1
- package/src/public_types.ts +8 -3
- package/src/transports/smooth/isobmff/create_boxes.ts +4 -6
- package/src/typings/globals.d.ts +21 -21
- package/scripts/doc-generator/construct_table_of_contents.js +0 -76
- package/scripts/doc-generator/convert_MD_to_HMTL.js +0 -26
- package/scripts/doc-generator/create_documentation.js +0 -331
- package/scripts/doc-generator/create_documentation_page.js +0 -209
- package/scripts/doc-generator/create_page.js +0 -210
- package/scripts/doc-generator/generate_header_html.js +0 -147
- package/scripts/doc-generator/generate_page_html.js +0 -115
- package/scripts/doc-generator/generate_page_list_html.js +0 -92
- package/scripts/doc-generator/generate_sidebar_html.js +0 -85
- package/scripts/doc-generator/get_search_data_for_content.js +0 -137
- package/scripts/doc-generator/index.js +0 -34
- package/scripts/doc-generator/parse_doc_configs.js +0 -327
- package/scripts/doc-generator/scripts/lunr.js +0 -10
- package/scripts/doc-generator/scripts/script.js +0 -451
- package/scripts/doc-generator/styles/code.css +0 -99
- package/scripts/doc-generator/styles/style.css +0 -835
- package/scripts/doc-generator/utils.js +0 -74
|
@@ -17,6 +17,54 @@
|
|
|
17
17
|
import log from "../../log";
|
|
18
18
|
import arrayFindIndex from "../../utils/array_find_index";
|
|
19
19
|
import getBufferLevels from "./utils/get_buffer_levels";
|
|
20
|
+
import {
|
|
21
|
+
IRepresentationMaintainabilityScore,
|
|
22
|
+
ScoreConfidenceLevel,
|
|
23
|
+
} from "./utils/representation_score_calculator";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Minimum amount of time, in milliseconds, during which we are blocked from
|
|
27
|
+
* raising in quality after it had been considered as too high.
|
|
28
|
+
*/
|
|
29
|
+
const MINIMUM_BLOCK_RAISE_DELAY = 6000;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Maximum amount of time, in milliseconds, during which we are blocked from
|
|
33
|
+
* raising in quality after it had been considered as too high.
|
|
34
|
+
*/
|
|
35
|
+
const MAXIMUM_BLOCK_RAISE_DELAY = 15000;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Amount of time, in milliseconds, with which the blocking time in raising
|
|
39
|
+
* the quality will be incremented if the current quality estimate is seen
|
|
40
|
+
* as too unstable.
|
|
41
|
+
*/
|
|
42
|
+
const RAISE_BLOCKING_DELAY_INCREMENT = 3000;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Amount of time, in milliseconds, with which the blocking time in raising
|
|
46
|
+
* the quality will be dcremented if the current quality estimate is seen
|
|
47
|
+
* as relatively stable, until `MINIMUM_BLOCK_RAISE_DELAY` is reached.
|
|
48
|
+
*/
|
|
49
|
+
const RAISE_BLOCKING_DELAY_DECREMENT = 1000;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Amount of time, in milliseconds, after the "raise blocking delay" currently
|
|
53
|
+
* in place (during which it is forbidden to raise up in quality), during which
|
|
54
|
+
* we might want to raise the "raise blocking delay" if the last chosen quality
|
|
55
|
+
* seems unsuitable.
|
|
56
|
+
*
|
|
57
|
+
* For example, let's consider that the current raise blocking delay is at
|
|
58
|
+
* `4000`, or 4 seconds, and that this `STABILITY_CHECK_DELAY` is at `5000`, or
|
|
59
|
+
* 5 seconds.
|
|
60
|
+
* Here it means that if the estimated quality is found to be unsuitable less
|
|
61
|
+
* than 4+5 = 9 seconds after it last was, we will increment the raise blocking
|
|
62
|
+
* delay by `RAISE_BLOCKING_DELAY_INCREMENT` (unless `MAXIMUM_BLOCK_RAISE_DELAY`
|
|
63
|
+
* is reached).
|
|
64
|
+
* Else, if takes more than 9 seconds, the raise blocking delay might be
|
|
65
|
+
* decremented.
|
|
66
|
+
*/
|
|
67
|
+
const STABILITY_CHECK_DELAY = 9000;
|
|
20
68
|
|
|
21
69
|
/**
|
|
22
70
|
* Choose a bitrate based on the currently available buffer.
|
|
@@ -29,18 +77,41 @@ import getBufferLevels from "./utils/get_buffer_levels";
|
|
|
29
77
|
* "maintanable" or not.
|
|
30
78
|
* If so, we may switch to a better quality, or conversely to a worse quality.
|
|
31
79
|
*
|
|
80
|
+
* It also rely on mechanisms to avoid fluctuating too much between qualities.
|
|
81
|
+
*
|
|
32
82
|
* @class BufferBasedChooser
|
|
33
83
|
*/
|
|
34
84
|
export default class BufferBasedChooser {
|
|
35
85
|
private _levelsMap : number[];
|
|
36
86
|
private _bitrates : number[];
|
|
37
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Laast timestamp, in terms of `performance.now`, at which the current
|
|
90
|
+
* quality was seen as too high by this algorithm.
|
|
91
|
+
* Begins at `undefined`.
|
|
92
|
+
*/
|
|
93
|
+
private _lastUnsuitableQualityTimestamp: number | undefined;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* After lowering in quality, we forbid raising during a set amount of time.
|
|
97
|
+
* This amount is adaptive may continue to raise if it seems that quality
|
|
98
|
+
* is switching too much between low and high qualities.
|
|
99
|
+
*
|
|
100
|
+
* `_blockRaiseDelay` represents this time in milliseconds.
|
|
101
|
+
*/
|
|
102
|
+
private _blockRaiseDelay: number;
|
|
103
|
+
|
|
38
104
|
/**
|
|
39
105
|
* @param {Array.<number>} bitrates
|
|
40
106
|
*/
|
|
41
107
|
constructor(bitrates : number[]) {
|
|
42
|
-
this._levelsMap = getBufferLevels(bitrates)
|
|
108
|
+
this._levelsMap = getBufferLevels(bitrates).map(bl => {
|
|
109
|
+
return bl + 4; // Add some buffer security as it will be used conjointly with
|
|
110
|
+
// other algorithms anyway
|
|
111
|
+
});
|
|
43
112
|
this._bitrates = bitrates;
|
|
113
|
+
this._lastUnsuitableQualityTimestamp = undefined;
|
|
114
|
+
this._blockRaiseDelay = MINIMUM_BLOCK_RAISE_DELAY;
|
|
44
115
|
log.debug("ABR: Steps for buffer based chooser.",
|
|
45
116
|
this._levelsMap.map((l, i) => `bufferLevel: ${l}, bitrate: ${bitrates[i]}`)
|
|
46
117
|
.join(" ,"));
|
|
@@ -59,7 +130,18 @@ export default class BufferBasedChooser {
|
|
|
59
130
|
if (currentBitrate == null) {
|
|
60
131
|
return bitrates[0];
|
|
61
132
|
}
|
|
62
|
-
|
|
133
|
+
|
|
134
|
+
let currentBitrateIndex = -1;
|
|
135
|
+
for (let i = 0; i < bitrates.length; i++) {
|
|
136
|
+
// There could be bitrate duplicates. Only take the last one to simplify
|
|
137
|
+
const bitrate = bitrates[i];
|
|
138
|
+
if (bitrate === currentBitrate) {
|
|
139
|
+
currentBitrateIndex = i;
|
|
140
|
+
} else if (bitrate > currentBitrate) {
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
63
145
|
if (currentBitrateIndex < 0 || bitrates.length !== bufferLevels.length) {
|
|
64
146
|
log.error("ABR: Current Bitrate not found in the calculated levels");
|
|
65
147
|
return bitrates[0];
|
|
@@ -67,35 +149,71 @@ export default class BufferBasedChooser {
|
|
|
67
149
|
|
|
68
150
|
let scaledScore : number|undefined;
|
|
69
151
|
if (currentScore !== undefined) {
|
|
70
|
-
scaledScore = speed === 0 ? currentScore : (currentScore / speed);
|
|
152
|
+
scaledScore = speed === 0 ? currentScore.score : (currentScore.score / speed);
|
|
71
153
|
}
|
|
72
154
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
155
|
+
const actualBufferGap = isFinite(bufferGap) ?
|
|
156
|
+
bufferGap :
|
|
157
|
+
0;
|
|
158
|
+
|
|
159
|
+
const now = performance.now();
|
|
160
|
+
|
|
161
|
+
if (
|
|
162
|
+
actualBufferGap < bufferLevels[currentBitrateIndex] ||
|
|
163
|
+
(
|
|
164
|
+
scaledScore !== undefined && scaledScore < 1 &&
|
|
165
|
+
currentScore?.confidenceLevel === ScoreConfidenceLevel.HIGH
|
|
166
|
+
)
|
|
167
|
+
) {
|
|
168
|
+
const timeSincePrev = this._lastUnsuitableQualityTimestamp === undefined ?
|
|
169
|
+
-1 :
|
|
170
|
+
now - this._lastUnsuitableQualityTimestamp;
|
|
171
|
+
if (timeSincePrev < this._blockRaiseDelay + STABILITY_CHECK_DELAY) {
|
|
172
|
+
const newDelay = this._blockRaiseDelay + RAISE_BLOCKING_DELAY_INCREMENT;
|
|
173
|
+
this._blockRaiseDelay = Math.min(newDelay, MAXIMUM_BLOCK_RAISE_DELAY);
|
|
174
|
+
log.debug("ABR: Incrementing blocking raise in BufferBasedChooser due " +
|
|
175
|
+
"to unstable quality",
|
|
176
|
+
this._blockRaiseDelay);
|
|
177
|
+
} else {
|
|
178
|
+
const newDelay = this._blockRaiseDelay - RAISE_BLOCKING_DELAY_DECREMENT;
|
|
179
|
+
this._blockRaiseDelay = Math.max(MINIMUM_BLOCK_RAISE_DELAY, newDelay);
|
|
180
|
+
log.debug("ABR: Lowering quality in BufferBasedChooser", this._blockRaiseDelay);
|
|
181
|
+
}
|
|
182
|
+
this._lastUnsuitableQualityTimestamp = now;
|
|
183
|
+
// Security if multiple bitrates are equal, we now take the first one
|
|
184
|
+
const baseIndex = arrayFindIndex(bitrates, (b) => b === currentBitrate);
|
|
185
|
+
for (let i = baseIndex - 1; i >= 0; i--) {
|
|
186
|
+
if (actualBufferGap >= bufferLevels[i]) {
|
|
187
|
+
return bitrates[i];
|
|
86
188
|
}
|
|
87
189
|
}
|
|
190
|
+
return bitrates[0];
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (
|
|
194
|
+
(
|
|
195
|
+
this._lastUnsuitableQualityTimestamp !== undefined &&
|
|
196
|
+
now - this._lastUnsuitableQualityTimestamp < this._blockRaiseDelay
|
|
197
|
+
) ||
|
|
198
|
+
scaledScore === undefined || scaledScore < 1.15 ||
|
|
199
|
+
currentScore?.confidenceLevel !== ScoreConfidenceLevel.HIGH
|
|
200
|
+
) {
|
|
201
|
+
return currentBitrate;
|
|
88
202
|
}
|
|
89
203
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
return bitrates[i];
|
|
96
|
-
}
|
|
204
|
+
const currentBufferLevel = bufferLevels[currentBitrateIndex];
|
|
205
|
+
const nextIndex = (() => {
|
|
206
|
+
for (let i = currentBitrateIndex + 1; i < bufferLevels.length; i++) {
|
|
207
|
+
if (bufferLevels[i] > currentBufferLevel) {
|
|
208
|
+
return i;
|
|
97
209
|
}
|
|
98
|
-
|
|
210
|
+
}
|
|
211
|
+
})();
|
|
212
|
+
if (nextIndex !== undefined) {
|
|
213
|
+
const nextBufferLevel = bufferLevels[nextIndex];
|
|
214
|
+
if (bufferGap >= nextBufferLevel) {
|
|
215
|
+
log.debug("ABR: Raising quality in BufferBasedChooser", bitrates[nextIndex]);
|
|
216
|
+
return bitrates[nextIndex];
|
|
99
217
|
}
|
|
100
218
|
}
|
|
101
219
|
return currentBitrate;
|
|
@@ -113,7 +231,7 @@ export interface IBufferBasedChooserPlaybackObservation {
|
|
|
113
231
|
/** The bitrate of the currently downloaded segments, in bps. */
|
|
114
232
|
currentBitrate? : number | undefined;
|
|
115
233
|
/** The "maintainability score" of the currently downloaded segments. */
|
|
116
|
-
currentScore? :
|
|
234
|
+
currentScore? : IRepresentationMaintainabilityScore | undefined;
|
|
117
235
|
/** Playback rate wanted (e.g. `1` is regular playback, `2` is double speed etc.). */
|
|
118
236
|
speed : number;
|
|
119
237
|
}
|
|
@@ -23,6 +23,7 @@ import LastEstimateStorage, {
|
|
|
23
23
|
} from "./utils/last_estimate_storage";
|
|
24
24
|
import { IRequestInfo } from "./utils/pending_requests_store";
|
|
25
25
|
import RepresentationScoreCalculator, {
|
|
26
|
+
IRepresentationMaintainabilityScore,
|
|
26
27
|
ScoreConfidenceLevel,
|
|
27
28
|
} from "./utils/representation_score_calculator";
|
|
28
29
|
|
|
@@ -178,11 +179,11 @@ export default class GuessBasedChooser {
|
|
|
178
179
|
private _canGuessHigher(
|
|
179
180
|
bufferGap : number,
|
|
180
181
|
speed : number,
|
|
181
|
-
|
|
182
|
+
{ score, confidenceLevel } : IRepresentationMaintainabilityScore
|
|
182
183
|
) : boolean {
|
|
183
184
|
return isFinite(bufferGap) && bufferGap >= 2.5 &&
|
|
184
185
|
performance.now() > this._blockGuessesUntil &&
|
|
185
|
-
|
|
186
|
+
confidenceLevel === ScoreConfidenceLevel.HIGH &&
|
|
186
187
|
score / speed > 1.01;
|
|
187
188
|
}
|
|
188
189
|
|
|
@@ -197,13 +198,13 @@ export default class GuessBasedChooser {
|
|
|
197
198
|
*/
|
|
198
199
|
private _shouldStopGuess(
|
|
199
200
|
lastGuess : Representation,
|
|
200
|
-
scoreData :
|
|
201
|
+
scoreData : IRepresentationMaintainabilityScore | undefined,
|
|
201
202
|
bufferGap : number,
|
|
202
203
|
requests : IRequestInfo[]
|
|
203
204
|
) : boolean {
|
|
204
|
-
if (scoreData !== undefined && scoreData
|
|
205
|
+
if (scoreData !== undefined && scoreData.score < 1.01) {
|
|
205
206
|
return true;
|
|
206
|
-
} else if ((scoreData === undefined || scoreData
|
|
207
|
+
} else if ((scoreData === undefined || scoreData.score < 1.2) && bufferGap < 0.6) {
|
|
207
208
|
return true;
|
|
208
209
|
}
|
|
209
210
|
|
|
@@ -233,11 +234,11 @@ export default class GuessBasedChooser {
|
|
|
233
234
|
private _isLastGuessValidated(
|
|
234
235
|
lastGuess : Representation,
|
|
235
236
|
incomingBestBitrate : number,
|
|
236
|
-
scoreData :
|
|
237
|
+
scoreData : IRepresentationMaintainabilityScore | undefined
|
|
237
238
|
) : boolean {
|
|
238
239
|
if (scoreData !== undefined &&
|
|
239
|
-
scoreData
|
|
240
|
-
scoreData
|
|
240
|
+
scoreData.confidenceLevel === ScoreConfidenceLevel.HIGH &&
|
|
241
|
+
scoreData.score > 1.5)
|
|
241
242
|
{
|
|
242
243
|
return true;
|
|
243
244
|
}
|
|
@@ -18,6 +18,26 @@ import log from "../../../log";
|
|
|
18
18
|
import { Representation } from "../../../manifest";
|
|
19
19
|
import EWMA from "./ewma";
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Object representing a maintainability score as calculated by the
|
|
23
|
+
* `RepresentationScoreCalculator`.
|
|
24
|
+
*/
|
|
25
|
+
export interface IRepresentationMaintainabilityScore {
|
|
26
|
+
/**
|
|
27
|
+
* Weighted mean of dividing the loaded segment's duration by the time to make
|
|
28
|
+
* their request.
|
|
29
|
+
*/
|
|
30
|
+
score : number;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The confidence we have on the calculated `score` in reflecting a useful
|
|
34
|
+
* maintainability hint for the concerned Representation.
|
|
35
|
+
*
|
|
36
|
+
* Basically, the more segments have been loaded, the higher the confidence.
|
|
37
|
+
*/
|
|
38
|
+
confidenceLevel: ScoreConfidenceLevel;
|
|
39
|
+
}
|
|
40
|
+
|
|
21
41
|
/**
|
|
22
42
|
* Calculate the "maintainability score" of a given Representation:
|
|
23
43
|
* - A score higher than 1 means that the Representation can theorically
|
|
@@ -107,7 +127,7 @@ export default class RepresentationScoreCalculator {
|
|
|
107
127
|
*/
|
|
108
128
|
public getEstimate(
|
|
109
129
|
representation : Representation
|
|
110
|
-
) :
|
|
130
|
+
) : IRepresentationMaintainabilityScore | undefined {
|
|
111
131
|
if (this._currentRepresentationData === null ||
|
|
112
132
|
this._currentRepresentationData.representation.id !== representation.id)
|
|
113
133
|
{
|
|
@@ -119,7 +139,7 @@ export default class RepresentationScoreCalculator {
|
|
|
119
139
|
loadedDuration >= 10 ? ScoreConfidenceLevel.HIGH :
|
|
120
140
|
ScoreConfidenceLevel.LOW;
|
|
121
141
|
|
|
122
|
-
return
|
|
142
|
+
return { score: estimate, confidenceLevel };
|
|
123
143
|
}
|
|
124
144
|
|
|
125
145
|
/**
|
|
@@ -29,7 +29,7 @@ export default function renderDebugElement(
|
|
|
29
29
|
debugWrapperElt.style.backgroundColor = "#00000099";
|
|
30
30
|
debugWrapperElt.style.padding = "7px";
|
|
31
31
|
debugWrapperElt.style.fontSize = "13px";
|
|
32
|
-
debugWrapperElt.style.fontFamily = "mono";
|
|
32
|
+
debugWrapperElt.style.fontFamily = "mono, monospace";
|
|
33
33
|
debugWrapperElt.style.color = "white";
|
|
34
34
|
debugWrapperElt.style.display = "inline-block";
|
|
35
35
|
debugWrapperElt.style.bottom = "0px";
|
|
@@ -364,7 +364,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
|
|
|
364
364
|
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1194624
|
|
365
365
|
videoElement.preload = "auto";
|
|
366
366
|
|
|
367
|
-
this.version = /* PLAYER_VERSION */"3.
|
|
367
|
+
this.version = /* PLAYER_VERSION */"3.31.0-dev.2023052200";
|
|
368
368
|
this.log = log;
|
|
369
369
|
this.state = "STOPPED";
|
|
370
370
|
this.videoElement = videoElement;
|
|
@@ -2986,7 +2986,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
|
|
|
2986
2986
|
return mediaElementTrackChoiceManager;
|
|
2987
2987
|
}
|
|
2988
2988
|
}
|
|
2989
|
-
Player.version = /* PLAYER_VERSION */"3.
|
|
2989
|
+
Player.version = /* PLAYER_VERSION */"3.31.0-dev.2023052200";
|
|
2990
2990
|
|
|
2991
2991
|
/** Every events sent by the RxPlayer's public API. */
|
|
2992
2992
|
interface IPublicAPIEvent {
|
|
@@ -120,10 +120,7 @@ function createAudioTracks(
|
|
|
120
120
|
*/
|
|
121
121
|
function createTextTracks(
|
|
122
122
|
textTracks: ICompatTextTrackList
|
|
123
|
-
): Array<{ track:
|
|
124
|
-
normalized: string;
|
|
125
|
-
language: string;
|
|
126
|
-
closedCaption: boolean; };
|
|
123
|
+
): Array<{ track: ITextTrack;
|
|
127
124
|
nativeTrack: TextTrack; }> {
|
|
128
125
|
const newTextTracks = [];
|
|
129
126
|
const languagesOccurences: Partial<Record<string, number>> = {};
|
|
@@ -137,10 +134,20 @@ function createTextTracks(
|
|
|
137
134
|
"_" +
|
|
138
135
|
occurences.toString();
|
|
139
136
|
languagesOccurences[language] = occurences + 1;
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
137
|
+
|
|
138
|
+
// Safari seems to be indicating that the subtitles track is a forced
|
|
139
|
+
// subtitles track by setting the `kind` attribute to `"forced"`.
|
|
140
|
+
// As of now (2023-04-04), this is not standard.
|
|
141
|
+
// @see https://github.com/whatwg/html/issues/4472
|
|
142
|
+
const forced = (textTrack.kind as string) === "forced" ?
|
|
143
|
+
true :
|
|
144
|
+
undefined;
|
|
145
|
+
const track = { language: textTrack.language,
|
|
146
|
+
forced,
|
|
147
|
+
label: textTrack.label,
|
|
148
|
+
id,
|
|
149
|
+
normalized: normalizeLanguage(textTrack.language),
|
|
150
|
+
closedCaption: textTrack.kind === "captions" };
|
|
144
151
|
newTextTracks.push({ track,
|
|
145
152
|
nativeTrack: textTrack });
|
|
146
153
|
}
|
|
@@ -471,6 +478,8 @@ export default class MediaElementTrackChoiceManager
|
|
|
471
478
|
public getAvailableTextTracks(): IAvailableTextTrack[] {
|
|
472
479
|
return this._textTracks.map(({ track, nativeTrack }) => {
|
|
473
480
|
return { id: track.id,
|
|
481
|
+
label: track.label,
|
|
482
|
+
forced: track.forced,
|
|
474
483
|
language: track.language,
|
|
475
484
|
normalized: track.normalized,
|
|
476
485
|
closedCaption: track.closedCaption,
|
|
@@ -26,7 +26,6 @@ import {
|
|
|
26
26
|
Representation,
|
|
27
27
|
} from "../../../manifest";
|
|
28
28
|
import {
|
|
29
|
-
IAudioRepresentation,
|
|
30
29
|
IAudioTrack,
|
|
31
30
|
IAudioTrackPreference,
|
|
32
31
|
IAvailableAudioTrack,
|
|
@@ -34,7 +33,6 @@ import {
|
|
|
34
33
|
IAvailableVideoTrack,
|
|
35
34
|
ITextTrack,
|
|
36
35
|
ITextTrackPreference,
|
|
37
|
-
IVideoRepresentation,
|
|
38
36
|
IVideoTrack,
|
|
39
37
|
IVideoTrackPreference,
|
|
40
38
|
} from "../../../public_types";
|
|
@@ -42,6 +40,7 @@ import arrayFind from "../../../utils/array_find";
|
|
|
42
40
|
import arrayIncludes from "../../../utils/array_includes";
|
|
43
41
|
import isNullOrUndefined from "../../../utils/is_null_or_undefined";
|
|
44
42
|
import normalizeLanguage from "../../../utils/languages";
|
|
43
|
+
import objectAssign from "../../../utils/object_assign";
|
|
45
44
|
import { ISharedReference } from "../../../utils/reference";
|
|
46
45
|
import SortedList from "../../../utils/sorted_list";
|
|
47
46
|
import takeFirstSet from "../../../utils/take_first_set";
|
|
@@ -618,19 +617,7 @@ export default class TrackChoiceManager {
|
|
|
618
617
|
if (isNullOrUndefined(chosenTrack)) {
|
|
619
618
|
return null;
|
|
620
619
|
}
|
|
621
|
-
|
|
622
|
-
const audioTrack : IAudioTrack = {
|
|
623
|
-
language: takeFirstSet<string>(chosenTrack.language, ""),
|
|
624
|
-
normalized: takeFirstSet<string>(chosenTrack.normalizedLanguage, ""),
|
|
625
|
-
audioDescription: chosenTrack.isAudioDescription === true,
|
|
626
|
-
id: chosenTrack.id,
|
|
627
|
-
representations: chosenTrack.representations.map(parseAudioRepresentation),
|
|
628
|
-
label: chosenTrack.label,
|
|
629
|
-
};
|
|
630
|
-
if (chosenTrack.isDub === true) {
|
|
631
|
-
audioTrack.dub = true;
|
|
632
|
-
}
|
|
633
|
-
return audioTrack;
|
|
620
|
+
return chosenTrack.toAudioTrack();
|
|
634
621
|
}
|
|
635
622
|
|
|
636
623
|
/**
|
|
@@ -655,18 +642,7 @@ export default class TrackChoiceManager {
|
|
|
655
642
|
if (isNullOrUndefined(chosenTextAdaptation)) {
|
|
656
643
|
return null;
|
|
657
644
|
}
|
|
658
|
-
|
|
659
|
-
const formatted : ITextTrack = {
|
|
660
|
-
language: takeFirstSet<string>(chosenTextAdaptation.language, ""),
|
|
661
|
-
normalized: takeFirstSet<string>(chosenTextAdaptation.normalizedLanguage, ""),
|
|
662
|
-
closedCaption: chosenTextAdaptation.isClosedCaption === true,
|
|
663
|
-
id: chosenTextAdaptation.id,
|
|
664
|
-
label: chosenTextAdaptation.label,
|
|
665
|
-
};
|
|
666
|
-
if (chosenTextAdaptation.isForcedSubtitles !== undefined) {
|
|
667
|
-
formatted.forced = chosenTextAdaptation.isForcedSubtitles;
|
|
668
|
-
}
|
|
669
|
-
return formatted;
|
|
645
|
+
return chosenTextAdaptation.toTextTrack();
|
|
670
646
|
}
|
|
671
647
|
|
|
672
648
|
/**
|
|
@@ -692,36 +668,7 @@ export default class TrackChoiceManager {
|
|
|
692
668
|
return null;
|
|
693
669
|
}
|
|
694
670
|
const currAdaptation = chosenVideoAdaptation.adaptation;
|
|
695
|
-
|
|
696
|
-
const trickModeTracks = currAdaptation.trickModeTracks !== undefined ?
|
|
697
|
-
currAdaptation.trickModeTracks.map((trickModeAdaptation) => {
|
|
698
|
-
const representations = trickModeAdaptation.representations
|
|
699
|
-
.map(parseVideoRepresentation);
|
|
700
|
-
const trickMode : IVideoTrack = { id: trickModeAdaptation.id,
|
|
701
|
-
representations,
|
|
702
|
-
isTrickModeTrack: true };
|
|
703
|
-
if (trickModeAdaptation.isSignInterpreted === true) {
|
|
704
|
-
trickMode.signInterpreted = true;
|
|
705
|
-
}
|
|
706
|
-
return trickMode;
|
|
707
|
-
}) :
|
|
708
|
-
undefined;
|
|
709
|
-
|
|
710
|
-
const videoTrack: IVideoTrack = {
|
|
711
|
-
id: currAdaptation.id,
|
|
712
|
-
representations: currAdaptation.representations.map(parseVideoRepresentation),
|
|
713
|
-
label: currAdaptation.label,
|
|
714
|
-
};
|
|
715
|
-
if (currAdaptation.isSignInterpreted === true) {
|
|
716
|
-
videoTrack.signInterpreted = true;
|
|
717
|
-
}
|
|
718
|
-
if (currAdaptation.isTrickModeTrack === true) {
|
|
719
|
-
videoTrack.isTrickModeTrack = true;
|
|
720
|
-
}
|
|
721
|
-
if (trickModeTracks !== undefined) {
|
|
722
|
-
videoTrack.trickModeTracks = trickModeTracks;
|
|
723
|
-
}
|
|
724
|
-
return videoTrack;
|
|
671
|
+
return currAdaptation.toVideoTrack();
|
|
725
672
|
}
|
|
726
673
|
|
|
727
674
|
/**
|
|
@@ -745,20 +692,9 @@ export default class TrackChoiceManager {
|
|
|
745
692
|
|
|
746
693
|
return audioInfos.adaptations
|
|
747
694
|
.map((adaptation) => {
|
|
748
|
-
const
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
audioDescription: adaptation.isAudioDescription === true,
|
|
752
|
-
id: adaptation.id,
|
|
753
|
-
active: currentId === null ? false :
|
|
754
|
-
currentId === adaptation.id,
|
|
755
|
-
representations: adaptation.representations.map(parseAudioRepresentation),
|
|
756
|
-
label: adaptation.label,
|
|
757
|
-
};
|
|
758
|
-
if (adaptation.isDub === true) {
|
|
759
|
-
formatted.dub = true;
|
|
760
|
-
}
|
|
761
|
-
return formatted;
|
|
695
|
+
const active = currentId === null ? false :
|
|
696
|
+
currentId === adaptation.id;
|
|
697
|
+
return objectAssign(adaptation.toAudioTrack(), { active });
|
|
762
698
|
});
|
|
763
699
|
}
|
|
764
700
|
|
|
@@ -784,19 +720,9 @@ export default class TrackChoiceManager {
|
|
|
784
720
|
|
|
785
721
|
return textInfos.adaptations
|
|
786
722
|
.map((adaptation) => {
|
|
787
|
-
const
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
closedCaption: adaptation.isClosedCaption === true,
|
|
791
|
-
id: adaptation.id,
|
|
792
|
-
active: currentId === null ? false :
|
|
793
|
-
currentId === adaptation.id,
|
|
794
|
-
label: adaptation.label,
|
|
795
|
-
};
|
|
796
|
-
if (adaptation.isForcedSubtitles !== undefined) {
|
|
797
|
-
formatted.forced = adaptation.isForcedSubtitles;
|
|
798
|
-
}
|
|
799
|
-
return formatted;
|
|
723
|
+
const active = currentId === null ? false :
|
|
724
|
+
currentId === adaptation.id;
|
|
725
|
+
return objectAssign(adaptation.toTextTrack(), { active });
|
|
800
726
|
});
|
|
801
727
|
}
|
|
802
728
|
|
|
@@ -821,37 +747,21 @@ export default class TrackChoiceManager {
|
|
|
821
747
|
|
|
822
748
|
return videoInfos.adaptations
|
|
823
749
|
.map((adaptation) => {
|
|
824
|
-
const
|
|
825
|
-
|
|
750
|
+
const active = currentId === null ? false :
|
|
751
|
+
currentId === adaptation.id;
|
|
752
|
+
const track = adaptation.toVideoTrack();
|
|
753
|
+
const trickModeTracks = track.trickModeTracks !== undefined ?
|
|
754
|
+
track.trickModeTracks.map((trickModeAdaptation) => {
|
|
826
755
|
const isActive = currentId === null ? false :
|
|
827
756
|
currentId === trickModeAdaptation.id;
|
|
828
|
-
|
|
829
|
-
.map(parseVideoRepresentation);
|
|
830
|
-
const trickMode : IAvailableVideoTrack = { id: trickModeAdaptation.id,
|
|
831
|
-
representations,
|
|
832
|
-
isTrickModeTrack: true,
|
|
833
|
-
active: isActive };
|
|
834
|
-
if (trickModeAdaptation.isSignInterpreted === true) {
|
|
835
|
-
trickMode.signInterpreted = true;
|
|
836
|
-
}
|
|
837
|
-
return trickMode;
|
|
757
|
+
return objectAssign(trickModeAdaptation, { active: isActive });
|
|
838
758
|
}) :
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
const formatted: IAvailableVideoTrack = {
|
|
842
|
-
id: adaptation.id,
|
|
843
|
-
active: currentId === null ? false :
|
|
844
|
-
currentId === adaptation.id,
|
|
845
|
-
representations: adaptation.representations.map(parseVideoRepresentation),
|
|
846
|
-
label: adaptation.label,
|
|
847
|
-
};
|
|
848
|
-
if (adaptation.isSignInterpreted === true) {
|
|
849
|
-
formatted.signInterpreted = true;
|
|
850
|
-
}
|
|
759
|
+
[];
|
|
760
|
+
const availableTrack = objectAssign(track, { active });
|
|
851
761
|
if (trickModeTracks !== undefined) {
|
|
852
|
-
|
|
762
|
+
availableTrack.trickModeTracks = trickModeTracks;
|
|
853
763
|
}
|
|
854
|
-
return
|
|
764
|
+
return availableTrack;
|
|
855
765
|
});
|
|
856
766
|
}
|
|
857
767
|
|
|
@@ -1359,28 +1269,6 @@ function getPeriodItem(
|
|
|
1359
1269
|
}
|
|
1360
1270
|
}
|
|
1361
1271
|
|
|
1362
|
-
/**
|
|
1363
|
-
* Parse video Representation into a IVideoRepresentation.
|
|
1364
|
-
* @param {Object} representation
|
|
1365
|
-
* @returns {Object}
|
|
1366
|
-
*/
|
|
1367
|
-
function parseVideoRepresentation(
|
|
1368
|
-
{ id, bitrate, frameRate, width, height, codec, hdrInfo } : Representation
|
|
1369
|
-
) : IVideoRepresentation {
|
|
1370
|
-
return { id, bitrate, frameRate, width, height, codec, hdrInfo };
|
|
1371
|
-
}
|
|
1372
|
-
|
|
1373
|
-
/**
|
|
1374
|
-
* Parse audio Representation into a ITMAudioRepresentation.
|
|
1375
|
-
* @param {Object} representation
|
|
1376
|
-
* @returns {Object}
|
|
1377
|
-
*/
|
|
1378
|
-
function parseAudioRepresentation(
|
|
1379
|
-
{ id, bitrate, codec } : Representation
|
|
1380
|
-
) : IAudioRepresentation {
|
|
1381
|
-
return { id, bitrate, codec };
|
|
1382
|
-
}
|
|
1383
|
-
|
|
1384
1272
|
function getRightVideoTrack(
|
|
1385
1273
|
adaptation : Adaptation,
|
|
1386
1274
|
isTrickModeEnabled : boolean
|
|
@@ -502,8 +502,36 @@ export default class MediaSourceContentInitializer extends ContentInitializer {
|
|
|
502
502
|
*/
|
|
503
503
|
function handleStreamOrchestratorCallbacks() : IStreamOrchestratorCallbacks {
|
|
504
504
|
return {
|
|
505
|
-
needsBufferFlush: () =>
|
|
506
|
-
|
|
505
|
+
needsBufferFlush: () => {
|
|
506
|
+
const seekedTime = mediaElement.currentTime + 0.001;
|
|
507
|
+
playbackObserver.setCurrentTime(seekedTime);
|
|
508
|
+
|
|
509
|
+
// Seek again once data begins to be buffered.
|
|
510
|
+
// This is sadly necessary on some browsers to avoid decoding
|
|
511
|
+
// issues after a flush.
|
|
512
|
+
//
|
|
513
|
+
// NOTE: there's in theory a potential race condition in the following
|
|
514
|
+
// logic as the callback could be called when media data is still
|
|
515
|
+
// being removed by the browser - which is an asynchronous process.
|
|
516
|
+
// The following condition checking for buffered data could thus lead
|
|
517
|
+
// to a false positive where we're actually checking previous data.
|
|
518
|
+
// For now, such scenario is avoided by setting the
|
|
519
|
+
// `includeLastObservation` option to `false` and calling
|
|
520
|
+
// `needsBufferFlush` once MSE media removal operations have been
|
|
521
|
+
// explicitely validated by the browser, but that's a complex and easy
|
|
522
|
+
// to break system.
|
|
523
|
+
playbackObserver.listen((obs, stopListening) => {
|
|
524
|
+
if (
|
|
525
|
+
// Data is buffered around the current position
|
|
526
|
+
obs.currentRange !== null ||
|
|
527
|
+
// Or, for whatever reason, playback is already advancing
|
|
528
|
+
obs.position > seekedTime + 0.1
|
|
529
|
+
) {
|
|
530
|
+
stopListening();
|
|
531
|
+
playbackObserver.setCurrentTime(obs.position + 0.001);
|
|
532
|
+
}
|
|
533
|
+
}, { includeLastObservation: false, clearSignal: cancelSignal });
|
|
534
|
+
},
|
|
507
535
|
|
|
508
536
|
streamStatusUpdate(value) {
|
|
509
537
|
// Announce discontinuities if found
|
|
@@ -664,7 +692,7 @@ export default class MediaSourceContentInitializer extends ContentInitializer {
|
|
|
664
692
|
this.trigger("activePeriodChanged", { period });
|
|
665
693
|
});
|
|
666
694
|
contentTimeBoundariesObserver.addEventListener("durationUpdate", (newDuration) => {
|
|
667
|
-
mediaSourceDurationUpdater.updateDuration(newDuration.duration,
|
|
695
|
+
mediaSourceDurationUpdater.updateDuration(newDuration.duration, newDuration.isEnd);
|
|
668
696
|
});
|
|
669
697
|
contentTimeBoundariesObserver.addEventListener("endOfStream", () => {
|
|
670
698
|
if (endOfStreamCanceller === null) {
|
|
@@ -683,7 +711,7 @@ export default class MediaSourceContentInitializer extends ContentInitializer {
|
|
|
683
711
|
});
|
|
684
712
|
const currentDuration = contentTimeBoundariesObserver.getCurrentDuration();
|
|
685
713
|
mediaSourceDurationUpdater.updateDuration(currentDuration.duration,
|
|
686
|
-
|
|
714
|
+
currentDuration.isEnd);
|
|
687
715
|
return contentTimeBoundariesObserver;
|
|
688
716
|
}
|
|
689
717
|
|