rx-player 3.29.0-dev.2022091600 → 3.29.0-dev.2022103100
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 +22 -10
- package/MAINTAINERS.md +2 -1
- package/VERSION +1 -1
- package/dist/_esm5.processed/compat/browser_detection.d.ts +4 -1
- package/dist/_esm5.processed/compat/browser_detection.js +7 -1
- package/dist/_esm5.processed/compat/can_reuse_media_keys.d.ts +12 -0
- package/dist/_esm5.processed/compat/can_reuse_media_keys.js +15 -0
- package/dist/_esm5.processed/compat/enable_audio_track.d.ts +11 -0
- package/dist/_esm5.processed/compat/enable_audio_track.js +26 -0
- package/dist/_esm5.processed/compat/index.d.ts +4 -2
- package/dist/_esm5.processed/compat/index.js +4 -2
- package/dist/_esm5.processed/compat/{should_renew_media_keys.d.ts → should_renew_media_key_system_access.d.ts} +3 -3
- package/dist/_esm5.processed/compat/{should_renew_media_keys.js → should_renew_media_key_system_access.js} +3 -3
- package/dist/_esm5.processed/config.d.ts +1 -0
- package/dist/_esm5.processed/core/api/playback_observer.d.ts +14 -5
- package/dist/_esm5.processed/core/api/playback_observer.js +54 -22
- package/dist/_esm5.processed/core/api/public_api.d.ts +7 -4
- package/dist/_esm5.processed/core/api/public_api.js +79 -61
- package/dist/_esm5.processed/core/api/tracks_management/media_element_track_choice_manager.js +9 -10
- package/dist/_esm5.processed/core/decrypt/__tests__/__global__/utils.js +1 -1
- package/dist/_esm5.processed/core/decrypt/find_key_system.js +3 -3
- package/dist/_esm5.processed/core/decrypt/get_media_keys.js +4 -1
- package/dist/_esm5.processed/core/fetchers/cdn_prioritizer.d.ts +98 -0
- package/dist/_esm5.processed/core/fetchers/cdn_prioritizer.js +173 -0
- package/dist/_esm5.processed/core/fetchers/manifest/manifest_fetcher.js +4 -4
- package/dist/_esm5.processed/core/fetchers/segment/segment_fetcher.d.ts +2 -1
- package/dist/_esm5.processed/core/fetchers/segment/segment_fetcher.js +15 -16
- package/dist/_esm5.processed/core/fetchers/segment/segment_fetcher_creator.d.ts +3 -1
- package/dist/_esm5.processed/core/fetchers/segment/segment_fetcher_creator.js +5 -2
- package/dist/_esm5.processed/core/fetchers/utils/schedule_request.d.ts +103 -0
- package/dist/_esm5.processed/core/fetchers/utils/schedule_request.js +414 -0
- package/dist/_esm5.processed/core/init/content_time_boundaries_observer.d.ts +4 -3
- package/dist/_esm5.processed/core/init/content_time_boundaries_observer.js +21 -22
- package/dist/_esm5.processed/core/init/initialize_media_source.d.ts +18 -4
- package/dist/_esm5.processed/core/init/initialize_media_source.js +10 -2
- package/dist/_esm5.processed/core/init/load_on_media_source.js +7 -1
- package/dist/_esm5.processed/core/init/stall_avoider.js +3 -1
- package/dist/_esm5.processed/default_config.d.ts +11 -0
- package/dist/_esm5.processed/default_config.js +11 -0
- package/dist/_esm5.processed/experimental/tools/VideoThumbnailLoader/video_thumbnail_loader.js +1 -1
- package/dist/_esm5.processed/manifest/manifest.js +11 -2
- package/dist/_esm5.processed/manifest/representation.d.ts +12 -1
- package/dist/_esm5.processed/manifest/representation.js +1 -0
- package/dist/_esm5.processed/manifest/representation_index/static.d.ts +1 -1
- package/dist/_esm5.processed/manifest/representation_index/static.js +2 -2
- package/dist/_esm5.processed/manifest/representation_index/types.d.ts +32 -15
- package/dist/_esm5.processed/manifest/update_period_in_place.js +1 -0
- package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/base.d.ts +10 -9
- package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/base.js +11 -11
- package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/get_init_segment.d.ts +1 -1
- package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/get_init_segment.js +3 -3
- package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/get_segments_from_timeline.d.ts +1 -1
- package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/get_segments_from_timeline.js +4 -4
- package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/list.d.ts +7 -7
- package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/list.js +12 -8
- package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/template.d.ts +5 -6
- package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/template.js +16 -16
- package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/timeline/timeline_representation_index.d.ts +11 -10
- package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/timeline/timeline_representation_index.js +14 -13
- package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/tokens.d.ts +3 -4
- package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/tokens.js +4 -12
- package/dist/_esm5.processed/parsers/manifest/dash/common/parse_mpd.js +2 -4
- package/dist/_esm5.processed/parsers/manifest/dash/common/parse_representation_index.js +1 -3
- package/dist/_esm5.processed/parsers/manifest/dash/common/parse_representations.js +6 -2
- package/dist/_esm5.processed/parsers/manifest/dash/common/resolve_base_urls.d.ts +1 -2
- package/dist/_esm5.processed/parsers/manifest/dash/common/resolve_base_urls.js +3 -8
- package/dist/_esm5.processed/parsers/manifest/dash/js-parser/node_parsers/BaseURL.d.ts +1 -1
- package/dist/_esm5.processed/parsers/manifest/dash/js-parser/node_parsers/BaseURL.js +2 -20
- package/dist/_esm5.processed/parsers/manifest/dash/node_parser_types.d.ts +0 -6
- package/dist/_esm5.processed/parsers/manifest/dash/wasm-parser/ts/generators/BaseURL.js +0 -11
- package/dist/_esm5.processed/parsers/manifest/dash/wasm-parser/ts/types.d.ts +5 -1
- package/dist/_esm5.processed/parsers/manifest/local/parse_local_manifest.js +2 -1
- package/dist/_esm5.processed/parsers/manifest/local/representation_index.js +2 -2
- package/dist/_esm5.processed/parsers/manifest/metaplaylist/metaplaylist_parser.js +7 -1
- package/dist/_esm5.processed/parsers/manifest/smooth/create_parser.js +17 -11
- package/dist/_esm5.processed/parsers/manifest/smooth/representation_index.js +2 -2
- package/dist/_esm5.processed/parsers/manifest/types.d.ts +24 -0
- package/dist/_esm5.processed/parsers/texttracks/ttml/html/apply_extent.js +8 -1
- package/dist/_esm5.processed/parsers/texttracks/ttml/html/apply_origin.js +9 -1
- package/dist/_esm5.processed/parsers/texttracks/ttml/html/create_element.js +2 -1
- package/dist/_esm5.processed/transports/dash/construct_segment_url.d.ts +18 -0
- package/dist/_esm5.processed/transports/dash/construct_segment_url.js +21 -0
- package/dist/_esm5.processed/transports/dash/image_pipelines.d.ts +3 -2
- package/dist/_esm5.processed/transports/dash/image_pipelines.js +5 -3
- package/dist/_esm5.processed/transports/dash/segment_loader.js +4 -2
- package/dist/_esm5.processed/transports/dash/text_loader.js +4 -2
- package/dist/_esm5.processed/transports/dash/text_parser.js +3 -1
- package/dist/_esm5.processed/transports/local/segment_loader.d.ts +3 -2
- package/dist/_esm5.processed/transports/local/segment_loader.js +2 -2
- package/dist/_esm5.processed/transports/metaplaylist/pipelines.js +8 -8
- package/dist/_esm5.processed/transports/smooth/pipelines.js +11 -7
- package/dist/_esm5.processed/transports/smooth/utils.d.ts +4 -2
- package/dist/_esm5.processed/transports/smooth/utils.js +7 -1
- package/dist/_esm5.processed/transports/types.d.ts +5 -3
- package/dist/_esm5.processed/utils/event_emitter.d.ts +1 -2
- package/dist/_esm5.processed/utils/resolve_url.d.ts +7 -4
- package/dist/_esm5.processed/utils/resolve_url.js +11 -8
- package/dist/mpd-parser.wasm +0 -0
- package/dist/rx-player.js +5656 -13236
- package/dist/rx-player.min.js +1 -1
- package/jest.config.js +28 -23
- package/package.json +29 -29
- package/sonar-project.properties +1 -1
- package/src/compat/__tests__/can_reuse_media_keys.test.ts +54 -0
- package/src/compat/__tests__/enable_audio_track.test.ts +341 -0
- package/src/compat/__tests__/{should_renew_media_keys.test.ts → should_renew_media_key_system_access.test.ts} +7 -5
- package/src/compat/browser_detection.ts +10 -0
- package/src/compat/can_reuse_media_keys.ts +19 -0
- package/src/compat/enable_audio_track.ts +33 -0
- package/src/compat/index.ts +6 -2
- package/src/compat/{should_renew_media_keys.ts → should_renew_media_key_system_access.ts} +3 -3
- package/src/core/api/playback_observer.ts +97 -33
- package/src/core/api/public_api.ts +126 -74
- package/src/core/api/tracks_management/media_element_track_choice_manager.ts +3 -11
- package/src/core/decrypt/__tests__/__global__/media_keys.test.ts +182 -0
- package/src/core/decrypt/__tests__/__global__/utils.ts +2 -0
- package/src/core/decrypt/find_key_system.ts +3 -3
- package/src/core/decrypt/get_media_keys.ts +5 -1
- package/src/core/fetchers/cdn_prioritizer.ts +198 -0
- package/src/core/fetchers/manifest/manifest_fetcher.ts +7 -11
- package/src/core/fetchers/segment/segment_fetcher.ts +12 -10
- package/src/core/fetchers/segment/segment_fetcher_creator.ts +10 -1
- package/src/core/fetchers/utils/schedule_request.ts +482 -0
- package/src/core/init/content_time_boundaries_observer.ts +12 -12
- package/src/core/init/initialize_media_source.ts +31 -4
- package/src/core/init/load_on_media_source.ts +11 -2
- package/src/core/init/stall_avoider.ts +4 -1
- package/src/core/stream/orchestrator/stream_orchestrator.ts +0 -1
- package/src/default_config.ts +12 -0
- package/src/experimental/tools/VideoThumbnailLoader/video_thumbnail_loader.ts +1 -0
- package/src/manifest/manifest.ts +11 -2
- package/src/manifest/representation.ts +15 -0
- package/src/manifest/representation_index/__tests__/static.test.ts +1 -1
- package/src/manifest/representation_index/static.ts +3 -3
- package/src/manifest/representation_index/types.ts +33 -15
- package/src/manifest/update_period_in_place.ts +1 -0
- package/src/parsers/manifest/dash/common/indexes/base.ts +22 -23
- package/src/parsers/manifest/dash/common/indexes/get_init_segment.ts +6 -4
- package/src/parsers/manifest/dash/common/indexes/get_segments_from_timeline.ts +5 -5
- package/src/parsers/manifest/dash/common/indexes/list.ts +19 -20
- package/src/parsers/manifest/dash/common/indexes/template.ts +23 -27
- package/src/parsers/manifest/dash/common/indexes/timeline/timeline_representation_index.ts +25 -23
- package/src/parsers/manifest/dash/common/indexes/tokens.ts +7 -18
- package/src/parsers/manifest/dash/common/parse_mpd.ts +2 -4
- package/src/parsers/manifest/dash/common/parse_representation_index.ts +1 -6
- package/src/parsers/manifest/dash/common/parse_representations.ts +9 -0
- package/src/parsers/manifest/dash/common/resolve_base_urls.ts +5 -9
- package/src/parsers/manifest/dash/js-parser/node_parsers/BaseURL.ts +2 -29
- package/src/parsers/manifest/dash/js-parser/node_parsers/__tests__/AdaptationSet.test.ts +7 -11
- package/src/parsers/manifest/dash/node_parser_types.ts +0 -7
- package/src/parsers/manifest/dash/wasm-parser/rs/events.rs +3 -1
- package/src/parsers/manifest/dash/wasm-parser/rs/processor/attributes.rs +1 -8
- package/src/parsers/manifest/dash/wasm-parser/ts/generators/BaseURL.ts +0 -13
- package/src/parsers/manifest/dash/wasm-parser/ts/types.ts +8 -0
- package/src/parsers/manifest/local/parse_local_manifest.ts +1 -0
- package/src/parsers/manifest/local/representation_index.ts +2 -2
- package/src/parsers/manifest/metaplaylist/metaplaylist_parser.ts +7 -1
- package/src/parsers/manifest/smooth/create_parser.ts +17 -13
- package/src/parsers/manifest/smooth/representation_index.ts +2 -2
- package/src/parsers/manifest/types.ts +26 -1
- package/src/parsers/manifest/utils/__tests__/get_first_time_from_adaptations.test.ts +15 -0
- package/src/parsers/manifest/utils/__tests__/get_last_time_from_adaptation.test.ts +15 -0
- package/src/parsers/texttracks/ttml/html/apply_extent.ts +8 -1
- package/src/parsers/texttracks/ttml/html/apply_origin.ts +9 -1
- package/src/parsers/texttracks/ttml/html/create_element.ts +2 -2
- package/src/transports/dash/construct_segment_url.ts +28 -0
- package/src/transports/dash/image_pipelines.ts +5 -2
- package/src/transports/dash/segment_loader.ts +5 -2
- package/src/transports/dash/text_loader.ts +5 -2
- package/src/transports/dash/text_parser.ts +3 -1
- package/src/transports/local/segment_loader.ts +3 -2
- package/src/transports/metaplaylist/pipelines.ts +28 -9
- package/src/transports/smooth/pipelines.ts +12 -6
- package/src/transports/smooth/utils.ts +13 -1
- package/src/transports/types.ts +6 -3
- package/src/utils/__tests__/event_emitter.test.ts +72 -67
- package/src/utils/__tests__/initialization_segment_cache.test.ts +7 -7
- package/src/utils/__tests__/resolve_url.test.ts +27 -23
- package/src/utils/event_emitter.ts +1 -3
- package/src/utils/resolve_url.ts +11 -8
- package/dist/_esm5.processed/core/fetchers/utils/try_urls_with_backoff.d.ts +0 -85
- package/dist/_esm5.processed/core/fetchers/utils/try_urls_with_backoff.js +0 -264
- package/dummy +0 -1
- package/src/core/fetchers/utils/try_urls_with_backoff.ts +0 -287
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2015 CANAL+ Group
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { isOffline } from "../../../compat";
|
|
18
|
+
import {
|
|
19
|
+
CustomLoaderError,
|
|
20
|
+
isKnownError,
|
|
21
|
+
NetworkErrorTypes,
|
|
22
|
+
RequestError,
|
|
23
|
+
} from "../../../errors";
|
|
24
|
+
import log from "../../../log";
|
|
25
|
+
import { ICdnMetadata } from "../../../parsers/manifest";
|
|
26
|
+
import cancellableSleep from "../../../utils/cancellable_sleep";
|
|
27
|
+
import getFuzzedDelay from "../../../utils/get_fuzzed_delay";
|
|
28
|
+
import noop from "../../../utils/noop";
|
|
29
|
+
import TaskCanceller, {
|
|
30
|
+
CancellationSignal,
|
|
31
|
+
} from "../../../utils/task_canceller";
|
|
32
|
+
import CdnPrioritizer from "../cdn_prioritizer";
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Called on a loader error.
|
|
36
|
+
* Returns whether the loader request should be retried.
|
|
37
|
+
*
|
|
38
|
+
* TODO the notion of retrying or not could be transport-specific (e.g. 412 are
|
|
39
|
+
* mainly used for Smooth contents) and thus as part of the transport code (e.g.
|
|
40
|
+
* by rejecting with an error always having a `canRetry` property?).
|
|
41
|
+
* Or not, to ponder.
|
|
42
|
+
*
|
|
43
|
+
* @param {Error} error
|
|
44
|
+
* @returns {Boolean} - If true, the request can be retried.
|
|
45
|
+
*/
|
|
46
|
+
function shouldRetry(error : unknown) : boolean {
|
|
47
|
+
if (error instanceof RequestError) {
|
|
48
|
+
if (error.type === NetworkErrorTypes.ERROR_HTTP_CODE) {
|
|
49
|
+
return error.status >= 500 ||
|
|
50
|
+
error.status === 404 ||
|
|
51
|
+
error.status === 415 || // some CDN seems to use that code when
|
|
52
|
+
// requesting low-latency segments too much
|
|
53
|
+
// in advance
|
|
54
|
+
error.status === 412;
|
|
55
|
+
}
|
|
56
|
+
return error.type === NetworkErrorTypes.TIMEOUT ||
|
|
57
|
+
error.type === NetworkErrorTypes.ERROR_EVENT;
|
|
58
|
+
} else if (error instanceof CustomLoaderError) {
|
|
59
|
+
if (typeof error.canRetry === "boolean") {
|
|
60
|
+
return error.canRetry;
|
|
61
|
+
}
|
|
62
|
+
if (error.xhr !== undefined) {
|
|
63
|
+
return error.xhr.status >= 500 ||
|
|
64
|
+
error.xhr.status === 404 ||
|
|
65
|
+
error.xhr.status === 415 || // some CDN seems to use that code when
|
|
66
|
+
// requesting low-latency segments too much
|
|
67
|
+
// in advance
|
|
68
|
+
error.xhr.status === 412;
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
return isKnownError(error) && error.code === "INTEGRITY_ERROR";
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Returns true if we're pretty sure that the current error is due to the
|
|
77
|
+
* user being offline.
|
|
78
|
+
* @param {Error} error
|
|
79
|
+
* @returns {Boolean}
|
|
80
|
+
*/
|
|
81
|
+
function isOfflineRequestError(error : unknown) : boolean {
|
|
82
|
+
if (error instanceof RequestError) {
|
|
83
|
+
return error.type === NetworkErrorTypes.ERROR_EVENT &&
|
|
84
|
+
isOffline();
|
|
85
|
+
} else if (error instanceof CustomLoaderError) {
|
|
86
|
+
return error.isOfflineError;
|
|
87
|
+
}
|
|
88
|
+
return false; // under doubt, return false
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Settings to give to the backoff functions to configure their behavior. */
|
|
92
|
+
export interface IBackoffSettings {
|
|
93
|
+
/**
|
|
94
|
+
* Initial delay to wait if a request fails before making a new request, in
|
|
95
|
+
* milliseconds.
|
|
96
|
+
*/
|
|
97
|
+
baseDelay : number;
|
|
98
|
+
/**
|
|
99
|
+
* Maximum delay to wait if a request fails before making a new request, in
|
|
100
|
+
* milliseconds.
|
|
101
|
+
*/
|
|
102
|
+
maxDelay : number;
|
|
103
|
+
/**
|
|
104
|
+
* Maximum number of retries to perform on "regular" errors (e.g. due to HTTP
|
|
105
|
+
* status, integrity errors, timeouts...).
|
|
106
|
+
*/
|
|
107
|
+
maxRetryRegular : number;
|
|
108
|
+
/**
|
|
109
|
+
* Maximum number of retries to perform when it appears that the user is
|
|
110
|
+
* currently offline.
|
|
111
|
+
*/
|
|
112
|
+
maxRetryOffline : number;
|
|
113
|
+
/** Callback called when a request is retried. */
|
|
114
|
+
onRetry : (err : unknown) => void;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const enum REQUEST_ERROR_TYPES { None,
|
|
118
|
+
Regular,
|
|
119
|
+
Offline }
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Guess the type of error obtained.
|
|
123
|
+
* @param {*} error
|
|
124
|
+
* @returns {number}
|
|
125
|
+
*/
|
|
126
|
+
function getRequestErrorType(error : unknown) : REQUEST_ERROR_TYPES {
|
|
127
|
+
return isOfflineRequestError(error) ? REQUEST_ERROR_TYPES.Offline :
|
|
128
|
+
REQUEST_ERROR_TYPES.Regular;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Specific algorithm used to perform segment and manifest requests.
|
|
133
|
+
*
|
|
134
|
+
* Here how it works:
|
|
135
|
+
*
|
|
136
|
+
* 1. You give it one or multiple of the CDN available for the resource you
|
|
137
|
+
* want to request (from the most important one to the least important),
|
|
138
|
+
* a callback doing the request with the chosen CDN in argument, and some
|
|
139
|
+
* options.
|
|
140
|
+
*
|
|
141
|
+
* 2. it tries to call the request callback with the most prioritized CDN
|
|
142
|
+
* first:
|
|
143
|
+
* - if it works as expected, it resolves the returned Promise with that
|
|
144
|
+
* request's response.
|
|
145
|
+
* - if it fails, it calls ther `onRetry` callback given with the
|
|
146
|
+
* corresponding error, un-prioritize that CDN and try with the new
|
|
147
|
+
* most prioritized CDN.
|
|
148
|
+
*
|
|
149
|
+
* Each CDN might be retried multiple times, depending on the nature of the
|
|
150
|
+
* error and the Configuration given.
|
|
151
|
+
*
|
|
152
|
+
* Multiple retries of the same CDN are done after a delay to avoid
|
|
153
|
+
* overwhelming it, this is what we call a "backoff". That delay raises
|
|
154
|
+
* exponentially as multiple consecutive errors are encountered on this
|
|
155
|
+
* CDN.
|
|
156
|
+
*
|
|
157
|
+
* @param {Array.<string>|null} cdns - The different CDN on which the
|
|
158
|
+
* wanted resource is available. `scheduleRequestWithCdns` will call the
|
|
159
|
+
* `performRequest` callback with the right element from that array if different
|
|
160
|
+
* from `null`.
|
|
161
|
+
*
|
|
162
|
+
* Can be set to `null` when that resource is not reachable through a CDN, in
|
|
163
|
+
* which case the `performRequest` callback may be called with `null`.
|
|
164
|
+
* @param {Object|null} cdnPrioritizer - Interface allowing to give the priority
|
|
165
|
+
* between multiple CDNs.
|
|
166
|
+
* @param {Function} performRequest - Callback implementing the request in
|
|
167
|
+
* itself. Resolving when the resource request succeed and rejecting with the
|
|
168
|
+
* corresponding error when the request failed.
|
|
169
|
+
* @param {Object} options - Configuration allowing to tweak the number on which
|
|
170
|
+
* the algorithm behind `scheduleRequestWithCdns` bases itself.
|
|
171
|
+
* @param {Object} cancellationSignal - CancellationSignal allowing to cancel
|
|
172
|
+
* the logic of `scheduleRequestWithCdns`.
|
|
173
|
+
* To trigger if the resource is not needed anymore.
|
|
174
|
+
* @returns {Promise} - Promise resolving, with the corresponding
|
|
175
|
+
* `performRequest`'s data, when the resource request succeed and rejecting in
|
|
176
|
+
* the following scenarios:
|
|
177
|
+
* - `scheduleRequestWithCdns` has been cancelled due to `cancellationSignal`
|
|
178
|
+
* being triggered. In that case a `CancellationError` is thrown.
|
|
179
|
+
*
|
|
180
|
+
* - The resource request(s) failed and will not be retried anymore.
|
|
181
|
+
*/
|
|
182
|
+
export async function scheduleRequestWithCdns<T>(
|
|
183
|
+
cdns : ICdnMetadata[] | null,
|
|
184
|
+
cdnPrioritizer : CdnPrioritizer | null,
|
|
185
|
+
performRequest : (
|
|
186
|
+
cdn : ICdnMetadata | null,
|
|
187
|
+
cancellationSignal : CancellationSignal
|
|
188
|
+
) => Promise<T>,
|
|
189
|
+
options : IBackoffSettings,
|
|
190
|
+
cancellationSignal : CancellationSignal
|
|
191
|
+
) : Promise<T> {
|
|
192
|
+
if (cancellationSignal.cancellationError !== null) {
|
|
193
|
+
return Promise.reject(cancellationSignal.cancellationError);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const { baseDelay,
|
|
197
|
+
maxDelay,
|
|
198
|
+
maxRetryRegular,
|
|
199
|
+
maxRetryOffline,
|
|
200
|
+
onRetry } = options;
|
|
201
|
+
|
|
202
|
+
if (cdns !== null && cdns.length === 0) {
|
|
203
|
+
log.warn("Fetchers: no CDN given to `scheduleRequestWithCdns`.");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const missedAttempts : Map<ICdnMetadata | null, ICdnAttemptMetadata> = new Map();
|
|
207
|
+
const initialCdnToRequest = getCdnToRequest();
|
|
208
|
+
if (initialCdnToRequest === undefined) {
|
|
209
|
+
throw new Error("No CDN to request");
|
|
210
|
+
}
|
|
211
|
+
return requestCdn(initialCdnToRequest);
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Returns what is now the most prioritary CDN to request the wanted resource.
|
|
215
|
+
*
|
|
216
|
+
* A return value of `null` indicates that the resource can be requested
|
|
217
|
+
* through another mean than by doing an HTTP request.
|
|
218
|
+
*
|
|
219
|
+
* A return value of `undefined` indicates that there's no CDN left to request
|
|
220
|
+
* the resource.
|
|
221
|
+
* @returns {Object|null|undefined}
|
|
222
|
+
*/
|
|
223
|
+
function getCdnToRequest() : ICdnMetadata | null | undefined {
|
|
224
|
+
if (cdns === null) {
|
|
225
|
+
const nullAttemptObject = missedAttempts.get(null);
|
|
226
|
+
if (nullAttemptObject !== undefined && nullAttemptObject.isBlacklisted) {
|
|
227
|
+
return undefined;
|
|
228
|
+
}
|
|
229
|
+
return null;
|
|
230
|
+
} else if (cdnPrioritizer === null) {
|
|
231
|
+
return getPrioritaryRequestableCdnFromSortedList(cdns);
|
|
232
|
+
} else {
|
|
233
|
+
const prioritized = cdnPrioritizer.getCdnPreferenceForResource(cdns);
|
|
234
|
+
return getPrioritaryRequestableCdnFromSortedList(prioritized);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Perform immediately the request for the given CDN.
|
|
240
|
+
*
|
|
241
|
+
* If it fails, forbid the CDN from being used - optionally and in some
|
|
242
|
+
* conditions, only temporarily, then try the next CDN according to
|
|
243
|
+
* previously-set delays (with a potential sleep before to respect them).
|
|
244
|
+
*
|
|
245
|
+
* Reject if both the request fails and there's no CDN left to use.
|
|
246
|
+
* @param {string|null} cdn
|
|
247
|
+
* @returns {Promise}
|
|
248
|
+
*/
|
|
249
|
+
async function requestCdn(cdn : ICdnMetadata | null) : Promise<T> {
|
|
250
|
+
try {
|
|
251
|
+
const res = await performRequest(cdn, cancellationSignal);
|
|
252
|
+
return res;
|
|
253
|
+
} catch (error : unknown) {
|
|
254
|
+
if (TaskCanceller.isCancellationError(error)) {
|
|
255
|
+
throw error;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (cdn !== null && cdnPrioritizer !== null) {
|
|
259
|
+
// We failed requesting the resource on this CDN.
|
|
260
|
+
// Globally give priority to the next CDN through the CdnPrioritizer.
|
|
261
|
+
cdnPrioritizer.downgradeCdn(cdn);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const currentErrorType = getRequestErrorType(error);
|
|
265
|
+
|
|
266
|
+
let missedAttemptsObj = missedAttempts.get(cdn);
|
|
267
|
+
if (missedAttemptsObj === undefined) {
|
|
268
|
+
missedAttemptsObj = { errorCounter: 1,
|
|
269
|
+
lastErrorType: currentErrorType,
|
|
270
|
+
blockedUntil: undefined,
|
|
271
|
+
isBlacklisted: false };
|
|
272
|
+
missedAttempts.set(cdn, missedAttemptsObj);
|
|
273
|
+
} else {
|
|
274
|
+
if (currentErrorType !== missedAttemptsObj.lastErrorType) {
|
|
275
|
+
missedAttemptsObj.errorCounter = 1;
|
|
276
|
+
missedAttemptsObj.lastErrorType = currentErrorType;
|
|
277
|
+
} else {
|
|
278
|
+
missedAttemptsObj.errorCounter++;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (!shouldRetry(error)) {
|
|
283
|
+
missedAttemptsObj.blockedUntil = undefined;
|
|
284
|
+
missedAttemptsObj.isBlacklisted = true;
|
|
285
|
+
return retryWithNextCdn(error);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const maxRetry = currentErrorType === REQUEST_ERROR_TYPES.Offline ?
|
|
289
|
+
maxRetryOffline :
|
|
290
|
+
maxRetryRegular;
|
|
291
|
+
|
|
292
|
+
if (missedAttemptsObj.errorCounter > maxRetry) {
|
|
293
|
+
missedAttemptsObj.blockedUntil = undefined;
|
|
294
|
+
missedAttemptsObj.isBlacklisted = true;
|
|
295
|
+
} else {
|
|
296
|
+
const errorCounter = missedAttemptsObj.errorCounter;
|
|
297
|
+
const delay = Math.min(baseDelay * Math.pow(2, errorCounter - 1),
|
|
298
|
+
maxDelay);
|
|
299
|
+
const fuzzedDelay = getFuzzedDelay(delay);
|
|
300
|
+
missedAttemptsObj.blockedUntil = performance.now() + fuzzedDelay;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return retryWithNextCdn(error);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* After a request error, find the new most prioritary CDN and perform the
|
|
309
|
+
* request with it, optionally after a delay.
|
|
310
|
+
*
|
|
311
|
+
* If there's no CDN left to test, reject the original request error.
|
|
312
|
+
* @param {*} prevRequestError
|
|
313
|
+
* @returns {Promise}
|
|
314
|
+
*/
|
|
315
|
+
async function retryWithNextCdn(prevRequestError : unknown) : Promise<T> {
|
|
316
|
+
const nextCdn = getCdnToRequest();
|
|
317
|
+
|
|
318
|
+
if (cancellationSignal.isCancelled) {
|
|
319
|
+
throw cancellationSignal.cancellationError;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (nextCdn === undefined) {
|
|
323
|
+
throw prevRequestError;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
onRetry(prevRequestError);
|
|
327
|
+
if (cancellationSignal.isCancelled) {
|
|
328
|
+
throw cancellationSignal.cancellationError;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return waitPotentialBackoffAndRequest(nextCdn, prevRequestError);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Request the corresponding CDN after the optional backoff needed before
|
|
336
|
+
* requesting it.
|
|
337
|
+
*
|
|
338
|
+
* If a new CDN become prioritary in the meantime, request it instead, again
|
|
339
|
+
* awaiting its optional backoff delay if it exists.
|
|
340
|
+
* @param {string|null} nextWantedCdn
|
|
341
|
+
* @param {*} prevRequestError
|
|
342
|
+
* @returns {Promise}
|
|
343
|
+
*/
|
|
344
|
+
function waitPotentialBackoffAndRequest(
|
|
345
|
+
nextWantedCdn: ICdnMetadata | null,
|
|
346
|
+
prevRequestError : unknown
|
|
347
|
+
) : Promise<T> {
|
|
348
|
+
const nextCdnAttemptObj = missedAttempts.get(nextWantedCdn);
|
|
349
|
+
if (nextCdnAttemptObj === undefined ||
|
|
350
|
+
nextCdnAttemptObj.blockedUntil === undefined)
|
|
351
|
+
{
|
|
352
|
+
return requestCdn(nextWantedCdn);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const now = performance.now();
|
|
356
|
+
const blockedFor = nextCdnAttemptObj.blockedUntil - now;
|
|
357
|
+
if (blockedFor <= 0) {
|
|
358
|
+
return requestCdn(nextWantedCdn);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const canceller = new TaskCanceller({ cancelOn: cancellationSignal });
|
|
362
|
+
return new Promise<T>((res, rej) => {
|
|
363
|
+
/* eslint-disable-next-line @typescript-eslint/no-misused-promises */
|
|
364
|
+
cdnPrioritizer?.addEventListener("priorityChange", () => {
|
|
365
|
+
const updatedPrioritaryCdn = getCdnToRequest();
|
|
366
|
+
if (cancellationSignal.isCancelled) {
|
|
367
|
+
throw cancellationSignal.cancellationError;
|
|
368
|
+
}
|
|
369
|
+
if (updatedPrioritaryCdn === undefined) {
|
|
370
|
+
return rej(prevRequestError);
|
|
371
|
+
}
|
|
372
|
+
if (updatedPrioritaryCdn !== nextWantedCdn) {
|
|
373
|
+
canceller.cancel();
|
|
374
|
+
waitPotentialBackoffAndRequest(updatedPrioritaryCdn, prevRequestError)
|
|
375
|
+
.then(res, rej);
|
|
376
|
+
}
|
|
377
|
+
}, canceller.signal);
|
|
378
|
+
|
|
379
|
+
cancellableSleep(blockedFor, canceller.signal)
|
|
380
|
+
.then(() => requestCdn(nextWantedCdn).then(res, rej), noop);
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Takes in input the list of CDN that can be used to request the resource, in
|
|
386
|
+
* a general preference order.
|
|
387
|
+
*
|
|
388
|
+
* Returns the actual most prioritary Cdn to request, based on the current
|
|
389
|
+
* attempts already done for that resource.
|
|
390
|
+
*
|
|
391
|
+
* Returns `undefined` if there's no Cdn left to request the resource.
|
|
392
|
+
* @param {Array.<Object>}
|
|
393
|
+
* @returns {Object|undefined}
|
|
394
|
+
*/
|
|
395
|
+
function getPrioritaryRequestableCdnFromSortedList(
|
|
396
|
+
sortedCdns : ICdnMetadata[]
|
|
397
|
+
) : ICdnMetadata | undefined {
|
|
398
|
+
if (missedAttempts.size === 0) {
|
|
399
|
+
return sortedCdns[0];
|
|
400
|
+
}
|
|
401
|
+
const now = performance.now();
|
|
402
|
+
return sortedCdns
|
|
403
|
+
.filter(c => missedAttempts.get(c)?.isBlacklisted !== true)
|
|
404
|
+
.reduce((
|
|
405
|
+
acc : [ICdnMetadata, number | undefined] | undefined,
|
|
406
|
+
x : ICdnMetadata
|
|
407
|
+
) : [ICdnMetadata, number | undefined] => {
|
|
408
|
+
let blockedUntil = missedAttempts.get(x)?.blockedUntil;
|
|
409
|
+
if (blockedUntil !== undefined && blockedUntil <= now) {
|
|
410
|
+
blockedUntil = undefined;
|
|
411
|
+
}
|
|
412
|
+
if (acc === undefined) {
|
|
413
|
+
return [x, blockedUntil];
|
|
414
|
+
}
|
|
415
|
+
if (blockedUntil === undefined) {
|
|
416
|
+
if (acc[1] === undefined) {
|
|
417
|
+
return acc;
|
|
418
|
+
}
|
|
419
|
+
return [x, undefined];
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return acc[1] === undefined ? acc :
|
|
423
|
+
blockedUntil < acc[1] ? [x, blockedUntil] :
|
|
424
|
+
acc;
|
|
425
|
+
}, undefined)?.[0];
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Lightweight version of the request algorithm, this time with only a simple
|
|
431
|
+
* Promise given.
|
|
432
|
+
* @param {Function} performRequest
|
|
433
|
+
* @param {Object} options
|
|
434
|
+
* @returns {Promise}
|
|
435
|
+
*/
|
|
436
|
+
export function scheduleRequestPromise<T>(
|
|
437
|
+
performRequest : () => Promise<T>,
|
|
438
|
+
options : IBackoffSettings,
|
|
439
|
+
cancellationSignal : CancellationSignal
|
|
440
|
+
) : Promise<T> {
|
|
441
|
+
// same than for a single unknown CDN
|
|
442
|
+
return scheduleRequestWithCdns(null,
|
|
443
|
+
null,
|
|
444
|
+
performRequest,
|
|
445
|
+
options,
|
|
446
|
+
cancellationSignal);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Metadata associated to attempt(s) of requesting a resource through the same
|
|
451
|
+
* CDN.
|
|
452
|
+
*
|
|
453
|
+
* Each `ICdnAttemptMetadata` object should concern only one CDN.
|
|
454
|
+
*/
|
|
455
|
+
interface ICdnAttemptMetadata {
|
|
456
|
+
/**
|
|
457
|
+
* Count the amount of consecutive times an error of type `lastErrorType` has
|
|
458
|
+
* been encountered while requesting this resource though the concerned CDN.
|
|
459
|
+
*
|
|
460
|
+
* For example `1` means that the request through this CDN either failed for
|
|
461
|
+
* the first consecutive time or that it already failed just before but for an
|
|
462
|
+
* error with a different `lastErrorType` value.
|
|
463
|
+
* `2` means that after requesting this CDN two consecutive times, the request
|
|
464
|
+
* failed with an error with the same `lastErrorType` value.
|
|
465
|
+
* etc.
|
|
466
|
+
*/
|
|
467
|
+
errorCounter : number;
|
|
468
|
+
/** The last type of error encountered when requesting through that CDN. */
|
|
469
|
+
lastErrorType : REQUEST_ERROR_TYPES;
|
|
470
|
+
/**
|
|
471
|
+
* Timestamp, in terms of `performance.now()`, until which it should be
|
|
472
|
+
* forbidden to request this CDN.
|
|
473
|
+
* Enforcing this delay allows to prevent making too much requests to a given
|
|
474
|
+
* CDN.
|
|
475
|
+
*
|
|
476
|
+
* `undefined` when either there is no enforced delay or when the CDN is
|
|
477
|
+
* blacklisted anyway (@see isBlacklisted)
|
|
478
|
+
*/
|
|
479
|
+
blockedUntil : number | undefined;
|
|
480
|
+
/** If `true`, that request should not be requested at all anymore. */
|
|
481
|
+
isBlacklisted : boolean;
|
|
482
|
+
}
|
|
@@ -32,10 +32,12 @@ import Manifest, {
|
|
|
32
32
|
import { fromEvent } from "../../utils/event_emitter";
|
|
33
33
|
import filterMap from "../../utils/filter_map";
|
|
34
34
|
import isNullOrUndefined from "../../utils/is_null_or_undefined";
|
|
35
|
-
import createSharedReference
|
|
35
|
+
import createSharedReference, {
|
|
36
|
+
IReadOnlySharedReference,
|
|
37
|
+
} from "../../utils/reference";
|
|
36
38
|
import { IReadOnlyPlaybackObserver } from "../api";
|
|
37
39
|
import {
|
|
38
|
-
|
|
40
|
+
IAdaptationChangeEvent,
|
|
39
41
|
IStreamOrchestratorPlaybackObservation,
|
|
40
42
|
} from "../stream";
|
|
41
43
|
import EVENTS from "./events_generators";
|
|
@@ -56,13 +58,13 @@ import { IWarningEvent } from "./types";
|
|
|
56
58
|
* theoretically playable.
|
|
57
59
|
*
|
|
58
60
|
* @param {Object} manifest
|
|
59
|
-
* @param {
|
|
61
|
+
* @param {Object} lastAdaptationChange
|
|
60
62
|
* @param {Object} playbackObserver
|
|
61
63
|
* @returns {Observable}
|
|
62
64
|
*/
|
|
63
65
|
export default function ContentTimeBoundariesObserver(
|
|
64
66
|
manifest : Manifest,
|
|
65
|
-
|
|
67
|
+
lastAdaptationChange : IReadOnlySharedReference<IAdaptationChangeEvent | null>,
|
|
66
68
|
playbackObserver : IReadOnlyPlaybackObserver<IContentTimeObserverPlaybackObservation>
|
|
67
69
|
|
|
68
70
|
) : Observable<IContentDurationUpdateEvent | IWarningEvent> {
|
|
@@ -115,10 +117,10 @@ export default function ContentTimeBoundariesObserver(
|
|
|
115
117
|
ignoreElements()
|
|
116
118
|
);
|
|
117
119
|
|
|
118
|
-
const updateDurationAndTimeBoundsOnTrackChange$ =
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if (!manifest.isLastPeriodKnown) {
|
|
120
|
+
const updateDurationAndTimeBoundsOnTrackChange$ = lastAdaptationChange
|
|
121
|
+
.asObservable().pipe(
|
|
122
|
+
tap((message) => {
|
|
123
|
+
if (message === null || !manifest.isLastPeriodKnown) {
|
|
122
124
|
return;
|
|
123
125
|
}
|
|
124
126
|
const lastPeriod = manifest.periods[manifest.periods.length - 1];
|
|
@@ -137,10 +139,8 @@ export default function ContentTimeBoundariesObserver(
|
|
|
137
139
|
contentDuration.setValue(newDuration);
|
|
138
140
|
}
|
|
139
141
|
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
ignoreElements()
|
|
143
|
-
);
|
|
142
|
+
}),
|
|
143
|
+
ignoreElements());
|
|
144
144
|
|
|
145
145
|
return observableMerge(
|
|
146
146
|
updateDurationOnManifestUpdate$,
|
|
@@ -36,11 +36,13 @@ import { shouldReloadMediaSourceOnDecipherabilityUpdate } from "../../compat";
|
|
|
36
36
|
import config from "../../config";
|
|
37
37
|
import log from "../../log";
|
|
38
38
|
import { IKeySystemOption } from "../../public_types";
|
|
39
|
+
import { ITransportPipelines } from "../../transports";
|
|
39
40
|
import deferSubscriptions from "../../utils/defer_subscriptions";
|
|
40
41
|
import { fromEvent } from "../../utils/event_emitter";
|
|
41
42
|
import filterMap from "../../utils/filter_map";
|
|
42
43
|
import objectAssign from "../../utils/object_assign";
|
|
43
44
|
import { IReadOnlySharedReference } from "../../utils/reference";
|
|
45
|
+
import TaskCanceller from "../../utils/task_canceller";
|
|
44
46
|
import AdaptiveRepresentationSelector, {
|
|
45
47
|
IAdaptiveRepresentationSelectorArguments,
|
|
46
48
|
} from "../adaptive";
|
|
@@ -125,8 +127,21 @@ export interface IInitializeArguments {
|
|
|
125
127
|
mediaElement : HTMLMediaElement;
|
|
126
128
|
/** Limit the frequency of Manifest updates. */
|
|
127
129
|
minimumManifestUpdateInterval : number;
|
|
128
|
-
/** Interface allowing to
|
|
129
|
-
|
|
130
|
+
/** Interface allowing to interact with the transport protocol */
|
|
131
|
+
transport : ITransportPipelines;
|
|
132
|
+
/** Configuration for the segment requesting logic. */
|
|
133
|
+
segmentRequestOptions : {
|
|
134
|
+
/** Maximum number of time a request on error will be retried. */
|
|
135
|
+
regularError : number | undefined;
|
|
136
|
+
/** Maximum number of time a request be retried when the user is offline. */
|
|
137
|
+
offlineError : number | undefined;
|
|
138
|
+
/**
|
|
139
|
+
* Amount of time after which a request should be aborted.
|
|
140
|
+
* `undefined` indicates that a default value is wanted.
|
|
141
|
+
* `-1` indicates no timeout.
|
|
142
|
+
*/
|
|
143
|
+
requestTimeout : number | undefined;
|
|
144
|
+
};
|
|
130
145
|
/** Emit the playback rate (speed) set by the user. */
|
|
131
146
|
speed : IReadOnlySharedReference<number>;
|
|
132
147
|
/** The configured starting position. */
|
|
@@ -177,14 +192,17 @@ export default function InitializeOnMediaSource(
|
|
|
177
192
|
mediaElement,
|
|
178
193
|
minimumManifestUpdateInterval,
|
|
179
194
|
playbackObserver,
|
|
180
|
-
|
|
195
|
+
segmentRequestOptions,
|
|
181
196
|
speed,
|
|
182
197
|
startAt,
|
|
198
|
+
transport,
|
|
183
199
|
textTrackOptions } : IInitializeArguments
|
|
184
200
|
) : Observable<IInitEvent> {
|
|
185
201
|
/** Choose the right "Representation" for a given "Adaptation". */
|
|
186
202
|
const representationEstimator = AdaptiveRepresentationSelector(adaptiveOptions);
|
|
187
203
|
|
|
204
|
+
const playbackCanceller = new TaskCanceller();
|
|
205
|
+
|
|
188
206
|
/**
|
|
189
207
|
* Create and open a new MediaSource object on the given media element on
|
|
190
208
|
* subscription.
|
|
@@ -244,6 +262,14 @@ export default function InitializeOnMediaSource(
|
|
|
244
262
|
const initialTime = getInitialTime(manifest, lowLatencyMode, startAt);
|
|
245
263
|
log.debug("Init: Initial time calculated:", initialTime);
|
|
246
264
|
|
|
265
|
+
const requestOptions = { lowLatencyMode,
|
|
266
|
+
requestTimeout: segmentRequestOptions.requestTimeout,
|
|
267
|
+
maxRetryRegular: segmentRequestOptions.regularError,
|
|
268
|
+
maxRetryOffline: segmentRequestOptions.offlineError };
|
|
269
|
+
const segmentFetcherCreator = new SegmentFetcherCreator(transport,
|
|
270
|
+
requestOptions,
|
|
271
|
+
playbackCanceller.signal);
|
|
272
|
+
|
|
247
273
|
const mediaSourceLoader = createMediaSourceLoader({
|
|
248
274
|
bufferOptions: objectAssign({ textTrackOptions, drmSystemId },
|
|
249
275
|
bufferOptions),
|
|
@@ -358,5 +384,6 @@ export default function InitializeOnMediaSource(
|
|
|
358
384
|
}
|
|
359
385
|
}));
|
|
360
386
|
|
|
361
|
-
return observableMerge(loadContent$, mediaError$, drmEvents$.pipe(ignoreElements()))
|
|
387
|
+
return observableMerge(loadContent$, mediaError$, drmEvents$.pipe(ignoreElements()))
|
|
388
|
+
.pipe(finalize(() => { playbackCanceller.cancel(); }));
|
|
362
389
|
}
|
|
@@ -31,12 +31,13 @@ import {
|
|
|
31
31
|
import { MediaError } from "../../errors";
|
|
32
32
|
import log from "../../log";
|
|
33
33
|
import Manifest from "../../manifest";
|
|
34
|
-
import { IReadOnlySharedReference } from "../../utils/reference";
|
|
34
|
+
import createSharedReference, { IReadOnlySharedReference } from "../../utils/reference";
|
|
35
35
|
import { IRepresentationEstimator } from "../adaptive";
|
|
36
36
|
import { PlaybackObserver } from "../api";
|
|
37
37
|
import { SegmentFetcherCreator } from "../fetchers";
|
|
38
38
|
import SegmentBuffersStore from "../segment_buffers";
|
|
39
39
|
import StreamOrchestrator, {
|
|
40
|
+
IAdaptationChangeEvent,
|
|
40
41
|
IStreamOrchestratorOptions,
|
|
41
42
|
} from "../stream";
|
|
42
43
|
import ContentTimeBoundariesObserver from "./content_time_boundaries_observer";
|
|
@@ -147,6 +148,11 @@ export default function createMediaSourceLoader(
|
|
|
147
148
|
/** Emits event when streams are "locked", meaning they cannot load segments. */
|
|
148
149
|
const lockedStream$ = new Subject<ILockedStreamEvent>();
|
|
149
150
|
|
|
151
|
+
/** Emit each time a new Adaptation is considered by the `StreamOrchestrator`. */
|
|
152
|
+
const lastAdaptationChange = createSharedReference<
|
|
153
|
+
IAdaptationChangeEvent | null
|
|
154
|
+
>(null);
|
|
155
|
+
|
|
150
156
|
// Creates Observable which will manage every Stream for the given Content.
|
|
151
157
|
const streams$ = StreamOrchestrator({ manifest, initialPeriod },
|
|
152
158
|
streamObserver,
|
|
@@ -176,6 +182,9 @@ export default function createMediaSourceLoader(
|
|
|
176
182
|
case "locked-stream":
|
|
177
183
|
lockedStream$.next(evt.value);
|
|
178
184
|
return EMPTY;
|
|
185
|
+
case "adaptationChange":
|
|
186
|
+
lastAdaptationChange.setValue(evt);
|
|
187
|
+
return observableOf(evt);
|
|
179
188
|
default:
|
|
180
189
|
return observableOf(evt);
|
|
181
190
|
}
|
|
@@ -183,7 +192,7 @@ export default function createMediaSourceLoader(
|
|
|
183
192
|
);
|
|
184
193
|
|
|
185
194
|
const contentTimeObserver = ContentTimeBoundariesObserver(manifest,
|
|
186
|
-
|
|
195
|
+
lastAdaptationChange,
|
|
187
196
|
streamObserver)
|
|
188
197
|
.pipe(
|
|
189
198
|
mergeMap((evt) => {
|
|
@@ -314,7 +314,10 @@ export default function StallAvoider(
|
|
|
314
314
|
/** Position at which data is awaited. */
|
|
315
315
|
const { position: stalledPosition } = rebuffering;
|
|
316
316
|
|
|
317
|
-
if (stalledPosition !== null &&
|
|
317
|
+
if (stalledPosition !== null &&
|
|
318
|
+
stalledPosition !== undefined &&
|
|
319
|
+
speed.getValue() > 0)
|
|
320
|
+
{
|
|
318
321
|
const skippableDiscontinuity = findSeekableDiscontinuity(discontinuitiesStore,
|
|
319
322
|
manifest,
|
|
320
323
|
stalledPosition);
|
|
@@ -363,7 +363,6 @@ export default function StreamOrchestrator(
|
|
|
363
363
|
if (start >= end) {
|
|
364
364
|
return EMPTY;
|
|
365
365
|
}
|
|
366
|
-
|
|
367
366
|
const canceller = new TaskCanceller();
|
|
368
367
|
return fromCancellablePromise(canceller, () => {
|
|
369
368
|
return segmentBuffer.removeBuffer(start, end, canceller.signal);
|