rx-player 4.0.0-beta.1 → 4.0.0-beta.2
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 +40 -0
- 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/eme/load_session.js +1 -1
- 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/config.d.ts +2 -0
- package/dist/_esm5.processed/core/adaptive/adaptive_representation_selector.js +5 -4
- package/dist/_esm5.processed/core/adaptive/buffer_based_chooser.d.ts +18 -1
- package/dist/_esm5.processed/core/adaptive/buffer_based_chooser.js +106 -25
- package/dist/_esm5.processed/core/adaptive/guess_based_chooser.js +6 -6
- package/dist/_esm5.processed/core/adaptive/network_analyzer.js +8 -5
- 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/playback_observer.js +1 -0
- package/dist/_esm5.processed/core/api/public_api.d.ts +54 -1
- package/dist/_esm5.processed/core/api/public_api.js +232 -35
- package/dist/_esm5.processed/core/api/track_management/media_element_tracks_store.js +10 -1
- package/dist/_esm5.processed/core/api/track_management/track_dispatcher.d.ts +13 -1
- package/dist/_esm5.processed/core/api/track_management/track_dispatcher.js +30 -15
- package/dist/_esm5.processed/core/api/track_management/tracks_store.d.ts +3 -1
- package/dist/_esm5.processed/core/api/track_management/tracks_store.js +67 -152
- package/dist/_esm5.processed/core/api/utils.d.ts +10 -0
- package/dist/_esm5.processed/core/api/utils.js +20 -0
- package/dist/_esm5.processed/core/decrypt/session_events_listener.js +7 -1
- package/dist/_esm5.processed/core/decrypt/utils/clean_old_loaded_sessions.js +2 -0
- package/dist/_esm5.processed/core/decrypt/utils/loaded_sessions_store.js +5 -1
- package/dist/_esm5.processed/core/init/directfile_content_initializer.js +1 -1
- package/dist/_esm5.processed/core/init/media_source_content_initializer.js +47 -10
- package/dist/_esm5.processed/core/init/types.d.ts +9 -1
- package/dist/_esm5.processed/core/init/utils/content_time_boundaries_observer.d.ts +28 -1
- package/dist/_esm5.processed/core/init/utils/content_time_boundaries_observer.js +22 -9
- package/dist/_esm5.processed/core/init/utils/media_source_duration_updater.d.ts +58 -0
- package/dist/_esm5.processed/core/init/utils/{media_duration_updater.js → media_source_duration_updater.js} +84 -87
- package/dist/_esm5.processed/core/init/utils/rebuffering_controller.d.ts +36 -2
- package/dist/_esm5.processed/core/init/utils/rebuffering_controller.js +82 -2
- package/dist/_esm5.processed/core/segment_buffers/implementations/audio_video/audio_video_segment_buffer.d.ts +18 -7
- package/dist/_esm5.processed/core/segment_buffers/implementations/audio_video/audio_video_segment_buffer.js +31 -40
- package/dist/_esm5.processed/core/segment_buffers/implementations/text/html/html_text_segment_buffer.d.ts +8 -0
- package/dist/_esm5.processed/core/segment_buffers/implementations/text/html/html_text_segment_buffer.js +12 -0
- package/dist/_esm5.processed/core/segment_buffers/implementations/text/native/native_text_segment_buffer.d.ts +8 -0
- package/dist/_esm5.processed/core/segment_buffers/implementations/text/native/native_text_segment_buffer.js +12 -0
- package/dist/_esm5.processed/core/segment_buffers/implementations/types.d.ts +11 -4
- package/dist/_esm5.processed/core/segment_buffers/index.d.ts +2 -2
- package/dist/_esm5.processed/core/stream/adaptation/utils/create_representation_estimator.d.ts +47 -0
- package/dist/_esm5.processed/core/stream/adaptation/utils/create_representation_estimator.js +70 -0
- package/dist/_esm5.processed/core/stream/orchestrator/stream_orchestrator.js +15 -8
- package/dist/_esm5.processed/core/stream/period/period_stream.js +1 -1
- package/dist/_esm5.processed/core/stream/representation/representation_stream.js +22 -13
- 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.d.ts +3 -2
- package/dist/_esm5.processed/core/stream/representation/utils/push_init_segment.js +8 -8
- package/dist/_esm5.processed/core/stream/representation/utils/push_media_segment.d.ts +2 -2
- package/dist/_esm5.processed/core/stream/representation/utils/push_media_segment.js +2 -3
- package/dist/_esm5.processed/default_config.d.ts +25 -0
- package/dist/_esm5.processed/default_config.js +27 -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/experimental/tools/VideoThumbnailLoader/load_and_push_segment.d.ts +1 -1
- package/dist/_esm5.processed/experimental/tools/VideoThumbnailLoader/load_and_push_segment.js +8 -7
- package/dist/_esm5.processed/experimental/tools/VideoThumbnailLoader/video_thumbnail_loader.js +17 -9
- package/dist/_esm5.processed/experimental/tools/mediaCapabilitiesProber/index.js +0 -2
- package/dist/_esm5.processed/manifest/adaptation.d.ts +21 -2
- package/dist/_esm5.processed/manifest/adaptation.js +76 -1
- package/dist/_esm5.processed/manifest/manifest.js +1 -1
- package/dist/_esm5.processed/manifest/period.js +2 -2
- package/dist/_esm5.processed/manifest/representation.d.ts +33 -2
- package/dist/_esm5.processed/manifest/representation.js +21 -0
- package/dist/_esm5.processed/manifest/utils.js +1 -3
- 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 +13 -3
- package/dist/_esm5.processed/tools/TextTrackRenderer/text_track_renderer.js +1 -1
- 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/_esm5.processed/utils/is_null_or_undefined.d.ts +1 -1
- package/dist/_esm5.processed/utils/is_null_or_undefined.js +1 -1
- package/dist/mpd-parser.wasm +0 -0
- package/dist/rx-player.js +4709 -4218
- package/dist/rx-player.min.js +1 -1
- package/package.json +42 -36
- package/scripts/build/generate_build.js +1 -1
- package/scripts/fast_demo_build.js +4 -3
- package/scripts/generate_full_demo.js +1 -1
- package/sonar-project.properties +1 -1
- package/src/compat/browser_detection.ts +7 -1
- package/src/compat/eme/load_session.ts +1 -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 +7 -4
- package/src/core/adaptive/buffer_based_chooser.ts +144 -26
- package/src/core/adaptive/guess_based_chooser.ts +9 -8
- package/src/core/adaptive/network_analyzer.ts +9 -4
- package/src/core/adaptive/utils/representation_score_calculator.ts +22 -2
- package/src/core/api/debug/render.ts +1 -1
- package/src/core/api/playback_observer.ts +1 -0
- package/src/core/api/public_api.ts +277 -44
- package/src/core/api/track_management/media_element_tracks_store.ts +17 -8
- package/src/core/api/track_management/track_dispatcher.ts +37 -14
- package/src/core/api/track_management/tracks_store.ts +77 -167
- package/src/core/api/utils.ts +26 -0
- package/src/core/decrypt/session_events_listener.ts +6 -1
- package/src/core/decrypt/utils/clean_old_loaded_sessions.ts +2 -1
- package/src/core/decrypt/utils/loaded_sessions_store.ts +8 -1
- package/src/core/init/directfile_content_initializer.ts +1 -0
- package/src/core/init/media_source_content_initializer.ts +52 -9
- package/src/core/init/types.ts +9 -1
- package/src/core/init/utils/content_time_boundaries_observer.ts +46 -10
- package/src/core/init/utils/{media_duration_updater.ts → media_source_duration_updater.ts} +100 -112
- package/src/core/init/utils/rebuffering_controller.ts +114 -3
- package/src/core/segment_buffers/implementations/audio_video/audio_video_segment_buffer.ts +56 -55
- package/src/core/segment_buffers/implementations/text/html/html_text_segment_buffer.ts +16 -0
- package/src/core/segment_buffers/implementations/text/native/native_text_segment_buffer.ts +16 -0
- package/src/core/segment_buffers/implementations/types.ts +16 -4
- package/src/core/segment_buffers/index.ts +2 -0
- package/src/core/stream/adaptation/utils/create_representation_estimator.ts +114 -0
- package/src/core/stream/orchestrator/stream_orchestrator.ts +16 -8
- package/src/core/stream/period/period_stream.ts +2 -1
- package/src/core/stream/representation/representation_stream.ts +34 -22
- package/src/core/stream/representation/utils/append_segment_to_buffer.ts +8 -3
- package/src/core/stream/representation/utils/push_init_segment.ts +11 -6
- package/src/core/stream/representation/utils/push_media_segment.ts +3 -3
- package/src/default_config.ts +29 -2
- 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/experimental/tools/VideoThumbnailLoader/load_and_push_segment.ts +10 -7
- package/src/experimental/tools/VideoThumbnailLoader/video_thumbnail_loader.ts +17 -6
- package/src/experimental/tools/mediaCapabilitiesProber/index.ts +0 -4
- package/src/manifest/__tests__/manifest.test.ts +7 -7
- package/src/manifest/__tests__/period.test.ts +90 -45
- package/src/manifest/adaptation.ts +89 -1
- package/src/manifest/manifest.ts +1 -1
- package/src/manifest/period.ts +4 -2
- package/src/manifest/representation.ts +67 -1
- package/src/manifest/utils.ts +1 -3
- 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 +16 -1
- package/src/tools/TextTrackRenderer/text_track_renderer.ts +1 -1
- package/src/transports/smooth/isobmff/create_boxes.ts +4 -6
- package/src/typings/globals.d.ts +20 -20
- package/src/utils/is_null_or_undefined.ts +1 -1
- package/dist/_esm5.processed/core/init/utils/media_duration_updater.d.ts +0 -56
- 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
|
@@ -111,18 +111,20 @@ export default class ContentTimeBoundariesObserver
|
|
|
111
111
|
}, { includeLastObservation: true, clearSignal: cancelSignal });
|
|
112
112
|
|
|
113
113
|
manifest.addEventListener("manifestUpdate", () => {
|
|
114
|
-
this.trigger("durationUpdate",
|
|
114
|
+
this.trigger("durationUpdate", this._getManifestDuration());
|
|
115
115
|
if (cancelSignal.isCancelled()) {
|
|
116
116
|
return;
|
|
117
117
|
}
|
|
118
118
|
this._checkEndOfStream();
|
|
119
119
|
}, cancelSignal);
|
|
120
|
+
}
|
|
120
121
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
122
|
+
/**
|
|
123
|
+
* Returns an estimate of the current duration of the content.
|
|
124
|
+
* @returns {Object}
|
|
125
|
+
*/
|
|
126
|
+
public getCurrentDuration() : IDurationItem {
|
|
127
|
+
return this._getManifestDuration();
|
|
126
128
|
}
|
|
127
129
|
|
|
128
130
|
/**
|
|
@@ -154,9 +156,12 @@ export default class ContentTimeBoundariesObserver
|
|
|
154
156
|
this._maximumPositionCalculator
|
|
155
157
|
.updateLastVideoAdaptation(adaptation);
|
|
156
158
|
}
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
159
|
+
const endingPosition = this._maximumPositionCalculator.getEndingPosition();
|
|
160
|
+
const newDuration = endingPosition !== undefined ?
|
|
161
|
+
{ isEnd: true,
|
|
162
|
+
duration: endingPosition } :
|
|
163
|
+
{ isEnd: false,
|
|
164
|
+
duration: this._maximumPositionCalculator.getMaximumAvailablePosition() };
|
|
160
165
|
this.trigger("durationUpdate", newDuration);
|
|
161
166
|
}
|
|
162
167
|
}
|
|
@@ -306,6 +311,15 @@ export default class ContentTimeBoundariesObserver
|
|
|
306
311
|
}
|
|
307
312
|
}
|
|
308
313
|
|
|
314
|
+
private _getManifestDuration() : IDurationItem {
|
|
315
|
+
const endingPosition = this._maximumPositionCalculator.getEndingPosition();
|
|
316
|
+
return endingPosition !== undefined ?
|
|
317
|
+
{ isEnd: true,
|
|
318
|
+
duration: endingPosition } :
|
|
319
|
+
{ isEnd: false,
|
|
320
|
+
duration: this._maximumPositionCalculator.getMaximumAvailablePosition() };
|
|
321
|
+
}
|
|
322
|
+
|
|
309
323
|
private _lazilyCreateActiveStreamInfo(bufferType : IBufferType) : IActiveStreamsInfo {
|
|
310
324
|
let streamInfo = this._activeStreams.get(bufferType);
|
|
311
325
|
if (streamInfo === undefined) {
|
|
@@ -334,6 +348,28 @@ export default class ContentTimeBoundariesObserver
|
|
|
334
348
|
}
|
|
335
349
|
}
|
|
336
350
|
|
|
351
|
+
export interface IDurationItem {
|
|
352
|
+
/**
|
|
353
|
+
* The new maximum known position (note that this is the ending position
|
|
354
|
+
* currently known of the current content, it might be superior to the last
|
|
355
|
+
* position at which segments are available and it might also evolve over
|
|
356
|
+
* time), in seconds.
|
|
357
|
+
*/
|
|
358
|
+
duration : number;
|
|
359
|
+
/**
|
|
360
|
+
* If `true`, the communicated `duration` is the actual end of the content.
|
|
361
|
+
* It may still be updated due to a track change or to add precision, but it
|
|
362
|
+
* is still a (rough) estimate of the maximum position that content should
|
|
363
|
+
* have.
|
|
364
|
+
*
|
|
365
|
+
* If `false`, this is the currently known maximum position associated to
|
|
366
|
+
* the content, but the content is still evolving (typically, new media
|
|
367
|
+
* segments are still being generated) and as such it can still have a
|
|
368
|
+
* longer duration in the future.
|
|
369
|
+
*/
|
|
370
|
+
isEnd : boolean;
|
|
371
|
+
}
|
|
372
|
+
|
|
337
373
|
/**
|
|
338
374
|
* Events triggered by a `ContentTimeBoundariesObserver` where the keys are the
|
|
339
375
|
* event names and the value is the payload of those events.
|
|
@@ -347,7 +383,7 @@ export interface IContentTimeBoundariesObserverEvent {
|
|
|
347
383
|
* Triggered when the duration of the currently-playing content became known
|
|
348
384
|
* or changed.
|
|
349
385
|
*/
|
|
350
|
-
durationUpdate :
|
|
386
|
+
durationUpdate : IDurationItem;
|
|
351
387
|
/**
|
|
352
388
|
* Triggered when the last possible chronological segment for all types of
|
|
353
389
|
* buffers has either been pushed or is being pushed to the corresponding
|
|
@@ -19,11 +19,11 @@ import {
|
|
|
19
19
|
onSourceEnded,
|
|
20
20
|
onSourceClose,
|
|
21
21
|
} from "../../../compat/event_listeners";
|
|
22
|
+
/* eslint-disable-next-line max-len */
|
|
23
|
+
import hasIssuesWithHighMediaSourceDuration from "../../../compat/has_issues_with_high_media_source_duration";
|
|
22
24
|
import log from "../../../log";
|
|
23
|
-
import Manifest from "../../../manifest";
|
|
24
25
|
import createSharedReference, {
|
|
25
26
|
IReadOnlySharedReference,
|
|
26
|
-
ISharedReference,
|
|
27
27
|
} from "../../../utils/reference";
|
|
28
28
|
import TaskCanceller, {
|
|
29
29
|
CancellationSignal,
|
|
@@ -33,124 +33,108 @@ import TaskCanceller, {
|
|
|
33
33
|
const YEAR_IN_SECONDS = 365 * 24 * 3600;
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
|
-
* Keep the MediaSource's duration up-to-date with
|
|
37
|
-
*
|
|
36
|
+
* Keep the MediaSource's `duration` attribute up-to-date with the duration of
|
|
37
|
+
* the content played on it.
|
|
38
|
+
* @class MediaSourceDurationUpdater
|
|
38
39
|
*/
|
|
39
|
-
export default class
|
|
40
|
-
|
|
40
|
+
export default class MediaSourceDurationUpdater {
|
|
41
|
+
/**
|
|
42
|
+
* `MediaSource` on which we're going to update the `duration` attribute.
|
|
43
|
+
*/
|
|
44
|
+
private _mediaSource : MediaSource;
|
|
41
45
|
|
|
42
46
|
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* `undefined` if the audio track for the last Period has never been known yet.
|
|
46
|
-
* `null` if there are no chosen audio Adaptation.
|
|
47
|
+
* Abort the current duration-setting logic.
|
|
48
|
+
* `null` if no such logic is pending.
|
|
47
49
|
*/
|
|
48
|
-
private
|
|
50
|
+
private _currentMediaSourceDurationUpdateCanceller : TaskCanceller | null;
|
|
49
51
|
|
|
50
52
|
/**
|
|
51
|
-
* Create a new `
|
|
52
|
-
* duration as soon as possible.
|
|
53
|
-
* This duration will be updated until the `stop` method is called.
|
|
54
|
-
* @param {Object} manifest - The Manifest currently played.
|
|
55
|
-
* For another content, you will have to create another `MediaDurationUpdater`.
|
|
53
|
+
* Create a new `MediaSourceDurationUpdater`,
|
|
56
54
|
* @param {MediaSource} mediaSource - The MediaSource on which the content is
|
|
57
|
-
*
|
|
55
|
+
* played.
|
|
58
56
|
*/
|
|
59
|
-
constructor(
|
|
60
|
-
|
|
61
|
-
|
|
57
|
+
constructor(mediaSource : MediaSource) {
|
|
58
|
+
this._mediaSource = mediaSource;
|
|
59
|
+
this._currentMediaSourceDurationUpdateCanceller = null;
|
|
60
|
+
}
|
|
62
61
|
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
/**
|
|
63
|
+
* Indicate to the `MediaSourceDurationUpdater` the currently known duration
|
|
64
|
+
* of the content.
|
|
65
|
+
*
|
|
66
|
+
* The `MediaSourceDurationUpdater` will then use that value to determine
|
|
67
|
+
* which `duration` attribute should be set on the `MediaSource` associated
|
|
68
|
+
*
|
|
69
|
+
* @param {number} newDuration
|
|
70
|
+
* @param {boolean} isRealEndKnown - If set to `false`, the current content is
|
|
71
|
+
* a dynamic content (it might evolve in the future) and the `newDuration`
|
|
72
|
+
* communicated might be greater still. In effect the
|
|
73
|
+
* `MediaSourceDurationUpdater` will actually set a much higher value to the
|
|
74
|
+
* `MediaSource`'s duration to prevent being annoyed by the HTML-related
|
|
75
|
+
* side-effects of having a too low duration (such as the impossibility to
|
|
76
|
+
* seek over that value).
|
|
77
|
+
*/
|
|
78
|
+
public updateDuration(
|
|
79
|
+
newDuration : number,
|
|
80
|
+
isRealEndKnown : boolean
|
|
81
|
+
) : void {
|
|
82
|
+
if (this._currentMediaSourceDurationUpdateCanceller !== null) {
|
|
83
|
+
this._currentMediaSourceDurationUpdateCanceller.cancel();
|
|
84
|
+
}
|
|
85
|
+
this._currentMediaSourceDurationUpdateCanceller = new TaskCanceller();
|
|
65
86
|
|
|
87
|
+
const mediaSource = this._mediaSource;
|
|
88
|
+
const currentSignal = this._currentMediaSourceDurationUpdateCanceller.signal;
|
|
66
89
|
const isMediaSourceOpened = createMediaSourceOpenReference(mediaSource,
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
/** TaskCanceller triggered each time the MediaSource open status changes. */
|
|
70
|
-
let msUpdateCanceller = new TaskCanceller();
|
|
71
|
-
msUpdateCanceller.linkToSignal(this._canceller.signal);
|
|
90
|
+
currentSignal);
|
|
72
91
|
|
|
92
|
+
/** TaskCanceller triggered each time the MediaSource switches to and from "open". */
|
|
93
|
+
let msOpenStatusCanceller = new TaskCanceller();
|
|
94
|
+
msOpenStatusCanceller.linkToSignal(currentSignal);
|
|
73
95
|
isMediaSourceOpened.onUpdate(onMediaSourceOpenedStatusChanged,
|
|
74
96
|
{ emitCurrentValue: true,
|
|
75
|
-
clearSignal:
|
|
76
|
-
|
|
97
|
+
clearSignal: currentSignal });
|
|
77
98
|
|
|
78
99
|
function onMediaSourceOpenedStatusChanged() {
|
|
79
|
-
|
|
100
|
+
msOpenStatusCanceller.cancel();
|
|
80
101
|
if (!isMediaSourceOpened.getValue()) {
|
|
81
102
|
return;
|
|
82
103
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
/** TaskCanceller triggered each time the content's duration may have changed */
|
|
87
|
-
let durationChangeCanceller = new TaskCanceller();
|
|
88
|
-
durationChangeCanceller.linkToSignal(msUpdateCanceller.signal);
|
|
89
|
-
|
|
90
|
-
const reSetDuration = () => {
|
|
91
|
-
durationChangeCanceller.cancel();
|
|
92
|
-
durationChangeCanceller = new TaskCanceller();
|
|
93
|
-
durationChangeCanceller.linkToSignal(msUpdateCanceller.signal);
|
|
94
|
-
onDurationMayHaveChanged(durationChangeCanceller.signal);
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
currentKnownDuration.onUpdate(reSetDuration,
|
|
98
|
-
{ emitCurrentValue: false,
|
|
99
|
-
clearSignal: msUpdateCanceller.signal });
|
|
100
|
-
|
|
101
|
-
manifest.addEventListener("manifestUpdate",
|
|
102
|
-
reSetDuration,
|
|
103
|
-
msUpdateCanceller.signal);
|
|
104
|
-
|
|
105
|
-
onDurationMayHaveChanged(durationChangeCanceller.signal);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function onDurationMayHaveChanged(cancelSignal : CancellationSignal) {
|
|
104
|
+
msOpenStatusCanceller = new TaskCanceller();
|
|
105
|
+
msOpenStatusCanceller.linkToSignal(currentSignal);
|
|
109
106
|
const areSourceBuffersUpdating = createSourceBuffersUpdatingReference(
|
|
110
107
|
mediaSource.sourceBuffers,
|
|
111
|
-
|
|
108
|
+
msOpenStatusCanceller.signal
|
|
112
109
|
);
|
|
113
|
-
|
|
114
110
|
/** TaskCanceller triggered each time SourceBuffers' updating status changes */
|
|
115
111
|
let sourceBuffersUpdatingCanceller = new TaskCanceller();
|
|
116
|
-
sourceBuffersUpdatingCanceller.linkToSignal(
|
|
112
|
+
sourceBuffersUpdatingCanceller.linkToSignal(msOpenStatusCanceller.signal);
|
|
113
|
+
|
|
117
114
|
return areSourceBuffersUpdating.onUpdate((areUpdating) => {
|
|
118
115
|
sourceBuffersUpdatingCanceller.cancel();
|
|
119
116
|
sourceBuffersUpdatingCanceller = new TaskCanceller();
|
|
120
|
-
sourceBuffersUpdatingCanceller.linkToSignal(
|
|
117
|
+
sourceBuffersUpdatingCanceller.linkToSignal(msOpenStatusCanceller.signal);
|
|
121
118
|
if (areUpdating) {
|
|
122
119
|
return;
|
|
123
120
|
}
|
|
121
|
+
|
|
124
122
|
recursivelyForceDurationUpdate(mediaSource,
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}, { clearSignal:
|
|
123
|
+
newDuration,
|
|
124
|
+
isRealEndKnown,
|
|
125
|
+
sourceBuffersUpdatingCanceller.signal);
|
|
126
|
+
}, { clearSignal: msOpenStatusCanceller.signal, emitCurrentValue: true });
|
|
129
127
|
}
|
|
130
128
|
}
|
|
131
129
|
|
|
132
130
|
/**
|
|
133
|
-
*
|
|
134
|
-
* MediaSource's duration.
|
|
135
|
-
* A more precize duration can be set by communicating to it a more precize
|
|
136
|
-
* media duration through `updateKnownDuration`.
|
|
137
|
-
* If the duration becomes unknown, `undefined` can be given to it so the
|
|
138
|
-
* `MediaDurationUpdater` goes back to a safe estimate.
|
|
139
|
-
* @param {number | undefined} newDuration
|
|
140
|
-
*/
|
|
141
|
-
public updateKnownDuration(
|
|
142
|
-
newDuration : number | undefined
|
|
143
|
-
) : void {
|
|
144
|
-
this._currentKnownDuration.setValueIfChanged(newDuration);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Stop the `MediaDurationUpdater` from updating and free its resources.
|
|
149
|
-
* Once stopped, it is not possible to start it again, beside creating another
|
|
150
|
-
* `MediaDurationUpdater`.
|
|
131
|
+
* Abort the last duration-setting operation and free its resources.
|
|
151
132
|
*/
|
|
152
|
-
public
|
|
153
|
-
this.
|
|
133
|
+
public stopUpdating() {
|
|
134
|
+
if (this._currentMediaSourceDurationUpdateCanceller !== null) {
|
|
135
|
+
this._currentMediaSourceDurationUpdateCanceller.cancel();
|
|
136
|
+
this._currentMediaSourceDurationUpdateCanceller = null;
|
|
137
|
+
}
|
|
154
138
|
}
|
|
155
139
|
}
|
|
156
140
|
|
|
@@ -163,33 +147,21 @@ export default class MediaDurationUpdater {
|
|
|
163
147
|
* - `null` if it hasn'nt been updated
|
|
164
148
|
*
|
|
165
149
|
* @param {MediaSource} mediaSource
|
|
166
|
-
* @param {
|
|
150
|
+
* @param {number} duration
|
|
151
|
+
* @param {boolean} isRealEndKnown
|
|
167
152
|
* @returns {string}
|
|
168
153
|
*/
|
|
169
154
|
function setMediaSourceDuration(
|
|
170
155
|
mediaSource: MediaSource,
|
|
171
|
-
|
|
172
|
-
|
|
156
|
+
duration : number,
|
|
157
|
+
isRealEndKnown : boolean
|
|
173
158
|
) : MediaSourceDurationUpdateStatus {
|
|
174
|
-
let newDuration
|
|
175
|
-
|
|
176
|
-
if (newDuration === undefined) {
|
|
177
|
-
if (manifest.isDynamic) {
|
|
178
|
-
newDuration = manifest.getLivePosition() ??
|
|
179
|
-
manifest.getMaximumSafePosition();
|
|
180
|
-
} else {
|
|
181
|
-
newDuration = manifest.getMaximumSafePosition();
|
|
182
|
-
}
|
|
183
|
-
}
|
|
159
|
+
let newDuration = duration;
|
|
184
160
|
|
|
185
|
-
if (
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
// we want to). As such, we put it at a safe default value of 2^32 excepted
|
|
190
|
-
// when the maximum position is already relatively close to that value, where
|
|
191
|
-
// we authorize exceptionally going over it.
|
|
192
|
-
newDuration = Math.max(Math.pow(2, 32), newDuration + YEAR_IN_SECONDS);
|
|
161
|
+
if (!isRealEndKnown) {
|
|
162
|
+
newDuration = hasIssuesWithHighMediaSourceDuration() ?
|
|
163
|
+
Infinity :
|
|
164
|
+
getMaximumLiveSeekablePosition(duration);
|
|
193
165
|
}
|
|
194
166
|
|
|
195
167
|
let maxBufferedEnd : number = 0;
|
|
@@ -222,6 +194,10 @@ function setMediaSourceDuration(
|
|
|
222
194
|
try {
|
|
223
195
|
log.info("Init: Updating duration", newDuration);
|
|
224
196
|
mediaSource.duration = newDuration;
|
|
197
|
+
if (mediaSource.readyState === "open" && !isFinite(newDuration)) {
|
|
198
|
+
mediaSource.setLiveSeekableRange(0,
|
|
199
|
+
getMaximumLiveSeekablePosition(duration));
|
|
200
|
+
}
|
|
225
201
|
} catch (err) {
|
|
226
202
|
log.warn("Duration Updater: Can't update duration on the MediaSource.",
|
|
227
203
|
err instanceof Error ? err : "");
|
|
@@ -328,30 +304,42 @@ function createMediaSourceOpenReference(
|
|
|
328
304
|
|
|
329
305
|
/**
|
|
330
306
|
* Immediately tries to set the MediaSource's duration to the most appropriate
|
|
331
|
-
* one
|
|
307
|
+
* one.
|
|
332
308
|
*
|
|
333
309
|
* If it fails, wait 2 seconds and retries.
|
|
334
310
|
*
|
|
335
311
|
* @param {MediaSource} mediaSource
|
|
336
|
-
* @param {
|
|
337
|
-
* @param {
|
|
312
|
+
* @param {number} duration
|
|
313
|
+
* @param {boolean} isRealEndKnown
|
|
338
314
|
* @param {Object} cancelSignal
|
|
339
315
|
*/
|
|
340
316
|
function recursivelyForceDurationUpdate(
|
|
341
317
|
mediaSource : MediaSource,
|
|
342
|
-
|
|
343
|
-
|
|
318
|
+
duration : number,
|
|
319
|
+
isRealEndKnown : boolean,
|
|
344
320
|
cancelSignal : CancellationSignal
|
|
345
321
|
) : void {
|
|
346
|
-
const res = setMediaSourceDuration(mediaSource,
|
|
322
|
+
const res = setMediaSourceDuration(mediaSource, duration, isRealEndKnown);
|
|
347
323
|
if (res === MediaSourceDurationUpdateStatus.Success) {
|
|
348
324
|
return ;
|
|
349
325
|
}
|
|
350
326
|
const timeoutId = setTimeout(() => {
|
|
351
327
|
unregisterClear();
|
|
352
|
-
recursivelyForceDurationUpdate(mediaSource,
|
|
328
|
+
recursivelyForceDurationUpdate(mediaSource, duration, isRealEndKnown, cancelSignal);
|
|
353
329
|
}, 2000);
|
|
354
330
|
const unregisterClear = cancelSignal.register(() => {
|
|
355
331
|
clearTimeout(timeoutId);
|
|
356
332
|
});
|
|
357
333
|
}
|
|
334
|
+
|
|
335
|
+
function getMaximumLiveSeekablePosition(contentLastPosition : number) : number {
|
|
336
|
+
// Some targets poorly support setting a very high number for seekable
|
|
337
|
+
// ranges.
|
|
338
|
+
// Yet, in contents whose end is not yet known (e.g. live contents), we
|
|
339
|
+
// would prefer setting a value as high as possible to still be able to
|
|
340
|
+
// seek anywhere we want to (even ahead of the Manifest if we want to).
|
|
341
|
+
// As such, we put it at a safe default value of 2^32 excepted when the
|
|
342
|
+
// maximum position is already relatively close to that value, where we
|
|
343
|
+
// authorize exceptionally going over it.
|
|
344
|
+
return Math.max(Math.pow(2, 32), contentLastPosition + YEAR_IN_SECONDS);
|
|
345
|
+
}
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
IPlaybackObservation,
|
|
31
31
|
PlaybackObserver,
|
|
32
32
|
} from "../../api";
|
|
33
|
-
import { IBufferType } from "../../segment_buffers";
|
|
33
|
+
import SegmentBuffersStore, { IBufferType } from "../../segment_buffers";
|
|
34
34
|
import { IStallingSituation } from "../types";
|
|
35
35
|
|
|
36
36
|
|
|
@@ -54,6 +54,7 @@ export default class RebufferingController
|
|
|
54
54
|
/** Emit the current playback conditions */
|
|
55
55
|
private _playbackObserver : PlaybackObserver;
|
|
56
56
|
private _manifest : Manifest | null;
|
|
57
|
+
private _segmentBuffersStore : SegmentBuffersStore | null;
|
|
57
58
|
private _speed : IReadOnlySharedReference<number>;
|
|
58
59
|
private _isStarted : boolean;
|
|
59
60
|
|
|
@@ -65,6 +66,18 @@ export default class RebufferingController
|
|
|
65
66
|
|
|
66
67
|
private _canceller : TaskCanceller;
|
|
67
68
|
|
|
69
|
+
/**
|
|
70
|
+
* If set to something else than `null`, this is the DOMHighResTimestamp as
|
|
71
|
+
* outputed by `performance.now()` when playback begin to seem to not start
|
|
72
|
+
* despite having decipherable data in the buffer(s).
|
|
73
|
+
*
|
|
74
|
+
* If enough time in that condition is spent, special considerations are
|
|
75
|
+
* taken at which point `_currentFreezeTimestamp` is reset to `null`.
|
|
76
|
+
*
|
|
77
|
+
* It is also reset to `null` when and if there is no such issue anymore.
|
|
78
|
+
*/
|
|
79
|
+
private _currentFreezeTimestamp : number | null;
|
|
80
|
+
|
|
68
81
|
/**
|
|
69
82
|
* @param {object} playbackObserver - emit the current playback conditions.
|
|
70
83
|
* @param {Object} manifest - The Manifest of the currently-played content.
|
|
@@ -72,16 +85,19 @@ export default class RebufferingController
|
|
|
72
85
|
*/
|
|
73
86
|
constructor(
|
|
74
87
|
playbackObserver : PlaybackObserver,
|
|
75
|
-
manifest: Manifest | null,
|
|
88
|
+
manifest : Manifest | null,
|
|
89
|
+
segmentBuffersStore : SegmentBuffersStore | null,
|
|
76
90
|
speed : IReadOnlySharedReference<number>
|
|
77
91
|
) {
|
|
78
92
|
super();
|
|
79
93
|
this._playbackObserver = playbackObserver;
|
|
80
94
|
this._manifest = manifest;
|
|
95
|
+
this._segmentBuffersStore = segmentBuffersStore;
|
|
81
96
|
this._speed = speed;
|
|
82
97
|
this._discontinuitiesStore = [];
|
|
83
98
|
this._isStarted = false;
|
|
84
99
|
this._canceller = new TaskCanceller();
|
|
100
|
+
this._currentFreezeTimestamp = null;
|
|
85
101
|
}
|
|
86
102
|
|
|
87
103
|
public start() : void {
|
|
@@ -154,6 +170,10 @@ export default class RebufferingController
|
|
|
154
170
|
Math.max(observation.pendingInternalSeek ?? 0, observation.position) :
|
|
155
171
|
null;
|
|
156
172
|
|
|
173
|
+
if (this._checkDecipherabilityFreeze(observation)) {
|
|
174
|
+
return ;
|
|
175
|
+
}
|
|
176
|
+
|
|
157
177
|
if (freezing !== null) {
|
|
158
178
|
const now = performance.now();
|
|
159
179
|
|
|
@@ -215,7 +235,7 @@ export default class RebufferingController
|
|
|
215
235
|
this.trigger("stalled", stalledReason);
|
|
216
236
|
return ;
|
|
217
237
|
} else {
|
|
218
|
-
log.warn("Init: ignored stall for too long,
|
|
238
|
+
log.warn("Init: ignored stall for too long, considering it",
|
|
219
239
|
now - ignoredStallTimeStamp);
|
|
220
240
|
}
|
|
221
241
|
}
|
|
@@ -358,6 +378,96 @@ export default class RebufferingController
|
|
|
358
378
|
public destroy() : void {
|
|
359
379
|
this._canceller.cancel();
|
|
360
380
|
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Support of contents with DRM on all the platforms out there is a pain in
|
|
384
|
+
* the *ss considering all the DRM-related bugs there are.
|
|
385
|
+
*
|
|
386
|
+
* We found out a frequent issue which is to be unable to play despite having
|
|
387
|
+
* all the decryption keys to play what is currently buffered.
|
|
388
|
+
* When this happens, re-creating the buffers from scratch, with a reload, is
|
|
389
|
+
* usually sufficient to unlock the situation.
|
|
390
|
+
*
|
|
391
|
+
* Although we prefer providing more targeted fixes or telling to platform
|
|
392
|
+
* developpers to fix their implementation, it's not always possible.
|
|
393
|
+
* We thus resorted to developping an heuristic which detects such situation
|
|
394
|
+
* and reload in that case.
|
|
395
|
+
*
|
|
396
|
+
* @param {Object} observation - The last playback observation produced, it
|
|
397
|
+
* has to be recent (just triggered for example).
|
|
398
|
+
* @returns {boolean} - Returns `true` if it seems to be such kind of
|
|
399
|
+
* decipherability freeze, in which case this method already performed the
|
|
400
|
+
* right handling steps.
|
|
401
|
+
*/
|
|
402
|
+
private _checkDecipherabilityFreeze(
|
|
403
|
+
observation : IPlaybackObservation
|
|
404
|
+
): boolean {
|
|
405
|
+
const { readyState,
|
|
406
|
+
rebuffering,
|
|
407
|
+
freezing } = observation;
|
|
408
|
+
const bufferGap = observation.bufferGap !== undefined &&
|
|
409
|
+
isFinite(observation.bufferGap) ? observation.bufferGap :
|
|
410
|
+
0;
|
|
411
|
+
if (
|
|
412
|
+
this._segmentBuffersStore === null ||
|
|
413
|
+
bufferGap < 6 ||
|
|
414
|
+
(rebuffering === null && freezing === null) ||
|
|
415
|
+
readyState > 1
|
|
416
|
+
) {
|
|
417
|
+
this._currentFreezeTimestamp = null;
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const now = performance.now();
|
|
422
|
+
if (this._currentFreezeTimestamp === null) {
|
|
423
|
+
this._currentFreezeTimestamp = now;
|
|
424
|
+
}
|
|
425
|
+
const rebufferingForTooLong =
|
|
426
|
+
rebuffering !== null && now - rebuffering.timestamp > 4000;
|
|
427
|
+
const frozenForTooLong =
|
|
428
|
+
freezing !== null && now - freezing.timestamp > 4000;
|
|
429
|
+
|
|
430
|
+
if (
|
|
431
|
+
(rebufferingForTooLong || frozenForTooLong) &&
|
|
432
|
+
performance.now() - this._currentFreezeTimestamp > 4000
|
|
433
|
+
) {
|
|
434
|
+
const statusAudio = this._segmentBuffersStore.getStatus("audio");
|
|
435
|
+
const statusVideo = this._segmentBuffersStore.getStatus("video");
|
|
436
|
+
let hasOnlyDecipherableSegments = true;
|
|
437
|
+
let isClear = true;
|
|
438
|
+
for (const status of [statusAudio, statusVideo]) {
|
|
439
|
+
if (status.type === "initialized") {
|
|
440
|
+
for (const segment of status.value.getInventory()) {
|
|
441
|
+
const { representation } = segment.infos;
|
|
442
|
+
if (representation.decipherable === false) {
|
|
443
|
+
log.warn(
|
|
444
|
+
"Init: we have undecipherable segments left in the buffer, reloading"
|
|
445
|
+
);
|
|
446
|
+
this._currentFreezeTimestamp = null;
|
|
447
|
+
this.trigger("needsReload", null);
|
|
448
|
+
return true;
|
|
449
|
+
} else if (representation.contentProtections !== undefined) {
|
|
450
|
+
isClear = false;
|
|
451
|
+
if (representation.decipherable !== true) {
|
|
452
|
+
hasOnlyDecipherableSegments = false;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (!isClear && hasOnlyDecipherableSegments) {
|
|
460
|
+
log.warn(
|
|
461
|
+
"Init: we are frozen despite only having decipherable " +
|
|
462
|
+
"segments left in the buffer, reloading"
|
|
463
|
+
);
|
|
464
|
+
this._currentFreezeTimestamp = null;
|
|
465
|
+
this.trigger("needsReload", null);
|
|
466
|
+
return true;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return false;
|
|
470
|
+
}
|
|
361
471
|
}
|
|
362
472
|
|
|
363
473
|
/**
|
|
@@ -581,6 +691,7 @@ class PlaybackRateUpdater {
|
|
|
581
691
|
export interface IRebufferingControllerEvent {
|
|
582
692
|
stalled : IStallingSituation;
|
|
583
693
|
unstalled : null;
|
|
694
|
+
needsReload : null;
|
|
584
695
|
warning : IPlayerError;
|
|
585
696
|
}
|
|
586
697
|
|