rx-player 3.27.0-dev.2022032800 → 3.27.1-dev.2022040500

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.
Files changed (41) hide show
  1. package/CHANGELOG.md +10 -2
  2. package/VERSION +1 -1
  3. package/dist/_esm5.processed/compat/get_start_date.d.ts +30 -0
  4. package/dist/_esm5.processed/compat/get_start_date.js +44 -0
  5. package/dist/_esm5.processed/compat/index.d.ts +2 -1
  6. package/dist/_esm5.processed/compat/index.js +2 -1
  7. package/dist/_esm5.processed/config.d.ts +1 -2
  8. package/dist/_esm5.processed/core/api/public_api.js +25 -25
  9. package/dist/_esm5.processed/core/segment_buffers/garbage_collector.js +4 -1
  10. package/dist/_esm5.processed/core/stream/orchestrator/stream_orchestrator.js +2 -1
  11. package/dist/_esm5.processed/core/stream/period/period_stream.js +15 -7
  12. package/dist/_esm5.processed/core/stream/representation/force_garbage_collection.js +3 -2
  13. package/dist/_esm5.processed/core/stream/representation/get_buffer_status.d.ts +2 -2
  14. package/dist/_esm5.processed/core/stream/representation/get_buffer_status.js +9 -3
  15. package/dist/_esm5.processed/core/stream/representation/get_needed_segments.d.ts +11 -1
  16. package/dist/_esm5.processed/core/stream/representation/get_needed_segments.js +27 -45
  17. package/dist/_esm5.processed/core/stream/representation/representation_stream.js +6 -4
  18. package/dist/_esm5.processed/default_config.d.ts +2 -8
  19. package/dist/_esm5.processed/default_config.js +2 -8
  20. package/dist/_esm5.processed/transports/dash/add_segment_integrity_checks_to_loader.js +39 -38
  21. package/dist/_esm5.processed/utils/reference.js +0 -2
  22. package/dist/_esm5.processed/utils/task_canceller.d.ts +8 -1
  23. package/dist/_esm5.processed/utils/task_canceller.js +9 -1
  24. package/dist/rx-player.js +202 -148
  25. package/dist/rx-player.min.js +1 -1
  26. package/package.json +1 -1
  27. package/sonar-project.properties +1 -1
  28. package/src/compat/get_start_date.ts +48 -0
  29. package/src/compat/index.ts +2 -0
  30. package/src/core/api/public_api.ts +23 -27
  31. package/src/core/segment_buffers/garbage_collector.ts +4 -0
  32. package/src/core/stream/orchestrator/stream_orchestrator.ts +2 -1
  33. package/src/core/stream/period/period_stream.ts +19 -8
  34. package/src/core/stream/representation/force_garbage_collection.ts +4 -1
  35. package/src/core/stream/representation/get_buffer_status.ts +21 -13
  36. package/src/core/stream/representation/get_needed_segments.ts +40 -55
  37. package/src/core/stream/representation/representation_stream.ts +6 -4
  38. package/src/default_config.ts +20 -27
  39. package/src/transports/dash/add_segment_integrity_checks_to_loader.ts +41 -44
  40. package/src/utils/reference.ts +0 -2
  41. package/src/utils/task_canceller.ts +16 -1
package/CHANGELOG.md CHANGED
@@ -1,6 +1,13 @@
1
1
  # Changelog
2
2
 
3
- ## v3.27.0-dev.2022032800 (2022-03-28)
3
+ ## v3.27.1-dev.2022040500 (2022-04-05)
4
+
5
+ ### Bug fixes
6
+
7
+ - When creating SourceBuffers, use the first _supported_ codec of the chosen `Adaptation`, not the lowest-bitrate one [#1094]
8
+
9
+
10
+ ## v3.27.0 (2022-03-31)
4
11
 
5
12
  ### Features
6
13
 
@@ -16,6 +23,7 @@
16
23
  - avoid performing a small seek when changing the audio track [#1080]
17
24
  - api: allow switching to RELOADING state synchronously after LOADED [#1083]
18
25
  - Safari Mobile: Improve decryption support on Safari mobile by relying on the vendored `WebKitMediaKeys` API [#1072]
26
+ - DASH: Fix issue which prevented the integrity check of most MP4 DASH segments when `transportOptions.checkMediaSegmentIntegrity` was set to `true`
19
27
  - avoid unnecessary warning logs when loading some initialization segments [#1049]
20
28
  - TypeScript: Add forgotten TypeScript types in the exposed segment and manifest loader APIs [#1057]
21
29
  - DRM: Avoid decryption issues when a license is persisted in a `singleLicensePer` `"init-data"` mode but loaded in a `"content"` mode [#1031, #1042]
@@ -26,7 +34,7 @@
26
34
  - DASH: always consider that the non-last Period is finished when it contains SegmentTimeline elements [#1047]
27
35
  - add better buffer cleaning logic on a browser's `QuotaExceededError` to better handle memory limitations [#1065]
28
36
  - DASH: Prioritize selectionPriority attribute over a "main" Role when ordering AdaptationSets [#1082]
29
- - directfile/Safari: use the `getStartDate` method in `getWallClockTime` when available to obtain true offseted times when playing HLS contents on Safari [#1055]
37
+ - directfile/Safari: use the `getStartDate` method in `getWallClockTime`, `seekTo` and the `positionUpdate` event when available to obtain true offseted "wall-clock" times when playing HLS contents on Safari [#1055]
30
38
  - DRM: Improve DRM Session caches performance when `singleLicensePer` is set to `"content"`
31
39
  - DRM: Stop retrying closing MediaKeySessions multiple times when it fails, instead doing it only once when it should work [#1093]
32
40
  - TypeScript: Add IBitrateEstimate, IPositionUpdate and IPlayerState types to the exported types [#1084]
package/VERSION CHANGED
@@ -1 +1 @@
1
- 3.27.0-dev.2022032800
1
+ 3.27.1-dev.2022040500
@@ -0,0 +1,30 @@
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
+ * Calculating a live-offseted media position necessitate to obtain first an
18
+ * offset, and then adding that offset to the wanted position.
19
+ *
20
+ * That offset is in most case present inside the Manifest file, yet in cases
21
+ * without it or without a Manifest, such as the "directfile" mode, the RxPlayer
22
+ * won't know that offset.
23
+ *
24
+ * Thankfully Safari declares a `getStartDate` method allowing to obtain that
25
+ * offset when available. This logic is mainly useful when playing HLS contents
26
+ * in directfile mode on Safari.
27
+ * @param {HTMLMediaElement} mediaElement
28
+ * @returns {number|undefined}
29
+ */
30
+ export default function getStartDate(mediaElement: HTMLMediaElement): number | undefined;
@@ -0,0 +1,44 @@
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
+ * Calculating a live-offseted media position necessitate to obtain first an
18
+ * offset, and then adding that offset to the wanted position.
19
+ *
20
+ * That offset is in most case present inside the Manifest file, yet in cases
21
+ * without it or without a Manifest, such as the "directfile" mode, the RxPlayer
22
+ * won't know that offset.
23
+ *
24
+ * Thankfully Safari declares a `getStartDate` method allowing to obtain that
25
+ * offset when available. This logic is mainly useful when playing HLS contents
26
+ * in directfile mode on Safari.
27
+ * @param {HTMLMediaElement} mediaElement
28
+ * @returns {number|undefined}
29
+ */
30
+ export default function getStartDate(mediaElement) {
31
+ var _mediaElement = mediaElement;
32
+ if (typeof _mediaElement.getStartDate === "function") {
33
+ var startDate = _mediaElement.getStartDate();
34
+ if (typeof startDate === "object" && startDate !== null) {
35
+ var startDateNum = +startDate;
36
+ if (!isNaN(startDateNum)) {
37
+ return startDateNum / 1000;
38
+ }
39
+ }
40
+ else if (typeof startDate === "number" && !isNaN(startDate)) {
41
+ return startDate;
42
+ }
43
+ }
44
+ }
@@ -22,6 +22,7 @@ import clearElementSrc from "./clear_element_src";
22
22
  import { closeSession, CustomMediaKeySystemAccess, generateKeyRequest, getInitData, ICustomMediaKeys, ICustomMediaKeySession, ICustomMediaKeySystemAccess, loadSession, requestMediaKeySystemAccess, setMediaKeys } from "./eme";
23
23
  import * as events from "./event_listeners";
24
24
  import { exitFullscreen, isFullscreen, requestFullscreen } from "./fullscreen";
25
+ import getStartDate from "./get_start_date";
25
26
  import hasEMEAPIs from "./has_eme_apis";
26
27
  import isCodecSupported from "./is_codec_supported";
27
28
  import isNode from "./is_node";
@@ -38,4 +39,4 @@ import shouldValidateMetadata from "./should_validate_metadata";
38
39
  import shouldWaitForDataBeforeLoaded from "./should_wait_for_data_before_loaded";
39
40
  import whenLoadedMetadata$ from "./when_loaded_metadata";
40
41
  import whenMediaSourceOpen$ from "./when_media_source_open";
41
- export { addClassName, addTextTrack, canPatchISOBMFFSegment, clearElementSrc, closeSession, CustomMediaKeySystemAccess, events, exitFullscreen, generateKeyRequest, getInitData, hasEMEAPIs, ICompatTextTrack, ICompatVTTCue, ICustomMediaKeySession, ICustomMediaKeySystemAccess, ICustomMediaKeys, ICompatSourceBuffer, isCodecSupported, isFullscreen, isNode, isOffline, isVTTCue, loadSession, makeVTTCue, MediaSource_, onHeightWidthChange, play, requestFullscreen, requestMediaKeySystemAccess, setElementSrc$, setMediaKeys, shouldReloadMediaSourceOnDecipherabilityUpdate, shouldRenewMediaKeys, shouldUnsetMediaKeys, shouldValidateMetadata, shouldWaitForDataBeforeLoaded, tryToChangeSourceBufferType, whenLoadedMetadata$, whenMediaSourceOpen$, };
42
+ export { addClassName, addTextTrack, canPatchISOBMFFSegment, clearElementSrc, closeSession, CustomMediaKeySystemAccess, events, exitFullscreen, generateKeyRequest, getInitData, getStartDate, hasEMEAPIs, ICompatTextTrack, ICompatVTTCue, ICustomMediaKeySession, ICustomMediaKeySystemAccess, ICustomMediaKeys, ICompatSourceBuffer, isCodecSupported, isFullscreen, isNode, isOffline, isVTTCue, loadSession, makeVTTCue, MediaSource_, onHeightWidthChange, play, requestFullscreen, requestMediaKeySystemAccess, setElementSrc$, setMediaKeys, shouldReloadMediaSourceOnDecipherabilityUpdate, shouldRenewMediaKeys, shouldUnsetMediaKeys, shouldValidateMetadata, shouldWaitForDataBeforeLoaded, tryToChangeSourceBufferType, whenLoadedMetadata$, whenMediaSourceOpen$, };
@@ -22,6 +22,7 @@ import clearElementSrc from "./clear_element_src";
22
22
  import { closeSession, CustomMediaKeySystemAccess, generateKeyRequest, getInitData, loadSession, requestMediaKeySystemAccess, setMediaKeys, } from "./eme";
23
23
  import * as events from "./event_listeners";
24
24
  import { exitFullscreen, isFullscreen, requestFullscreen, } from "./fullscreen";
25
+ import getStartDate from "./get_start_date";
25
26
  import hasEMEAPIs from "./has_eme_apis";
26
27
  import isCodecSupported from "./is_codec_supported";
27
28
  import isNode from "./is_node";
@@ -44,4 +45,4 @@ import whenMediaSourceOpen$ from "./when_media_source_open";
44
45
  // we would prefer to disallow (both for the understandability of the code and
45
46
  // to better exploit tree shaking.
46
47
  patchWebkitSourceBuffer();
47
- export { addClassName, addTextTrack, canPatchISOBMFFSegment, clearElementSrc, closeSession, CustomMediaKeySystemAccess, events, exitFullscreen, generateKeyRequest, getInitData, hasEMEAPIs, isCodecSupported, isFullscreen, isNode, isOffline, isVTTCue, loadSession, makeVTTCue, MediaSource_, onHeightWidthChange, play, requestFullscreen, requestMediaKeySystemAccess, setElementSrc$, setMediaKeys, shouldReloadMediaSourceOnDecipherabilityUpdate, shouldRenewMediaKeys, shouldUnsetMediaKeys, shouldValidateMetadata, shouldWaitForDataBeforeLoaded, tryToChangeSourceBufferType, whenLoadedMetadata$, whenMediaSourceOpen$, };
48
+ export { addClassName, addTextTrack, canPatchISOBMFFSegment, clearElementSrc, closeSession, CustomMediaKeySystemAccess, events, exitFullscreen, generateKeyRequest, getInitData, getStartDate, hasEMEAPIs, isCodecSupported, isFullscreen, isNode, isOffline, isVTTCue, loadSession, makeVTTCue, MediaSource_, onHeightWidthChange, play, requestFullscreen, requestMediaKeySystemAccess, setElementSrc$, setMediaKeys, shouldReloadMediaSourceOnDecipherabilityUpdate, shouldRenewMediaKeys, shouldUnsetMediaKeys, shouldValidateMetadata, shouldWaitForDataBeforeLoaded, tryToChangeSourceBufferType, whenLoadedMetadata$, whenMediaSourceOpen$, };
@@ -175,8 +175,7 @@ declare class ConfigHandler {
175
175
  DEFAULT_MAXIMUM_TIME_ROUNDING_ERROR: number;
176
176
  BUFFERED_HISTORY_RETENTION_TIME: number;
177
177
  BUFFERED_HISTORY_MAXIMUM_ENTRIES: number;
178
- MIN_BUFFER_LENGTH: number;
179
- MIN_BUFFER_DISTANCE_BEFORE_CLEAN_UP: number;
178
+ MIN_BUFFER_AHEAD: number;
180
179
  UPTO_CURRENT_POSITION_CLEANUP: number;
181
180
  };
182
181
  update(config: Partial<IDefaultConfig>): void;
@@ -44,7 +44,7 @@ var __assign = (this && this.__assign) || function () {
44
44
  * It also starts the different sub-parts of the player on various API calls.
45
45
  */
46
46
  import { combineLatest as observableCombineLatest, concat as observableConcat, connectable, distinctUntilChanged, EMPTY, filter, map, merge as observableMerge, mergeMap, of as observableOf, ReplaySubject, share, shareReplay, skipWhile, startWith, Subject, switchMap, take, takeUntil, } from "rxjs";
47
- import { events, exitFullscreen, isFullscreen, requestFullscreen, } from "../../compat";
47
+ import { events, exitFullscreen, getStartDate, isFullscreen, requestFullscreen, } from "../../compat";
48
48
  /* eslint-disable-next-line max-len */
49
49
  import canRelyOnVideoVisibilityAndSize from "../../compat/can_rely_on_video_visibility_and_size";
50
50
  import config from "../../config";
@@ -87,7 +87,7 @@ var Player = /** @class */ (function (_super) {
87
87
  // Workaround to support Firefox autoplay on FF 42.
88
88
  // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1194624
89
89
  videoElement.preload = "auto";
90
- _this.version = /* PLAYER_VERSION */ "3.27.0-dev.2022032800";
90
+ _this.version = /* PLAYER_VERSION */ "3.27.1-dev.2022040500";
91
91
  _this.log = log;
92
92
  _this.state = "STOPPED";
93
93
  _this.videoElement = videoElement;
@@ -805,22 +805,8 @@ var Player = /** @class */ (function (_super) {
805
805
  }
806
806
  var _a = this._priv_contentInfos, isDirectFile = _a.isDirectFile, manifest = _a.manifest;
807
807
  if (isDirectFile) {
808
- // Calculating the "wall-clock time" necessitate to obtain first an offset,
809
- // and then adding that offset to the HTMLMediaElement's current position.
810
- // That offset is in most case present inside the Manifest file, yet in
811
- // directfile mode, the RxPlayer won't parse that file, the browser does it.
812
- //
813
- // Thankfully Safari declares a `getStartDate` method allowing to obtain
814
- // that offset when available. This logic is thus mainly useful when
815
- // playing HLS contents in directfile mode on Safari.
816
- var mediaElement = this.videoElement;
817
- if (typeof mediaElement.getStartDate === "function") {
818
- var startDate = mediaElement.getStartDate();
819
- if (typeof startDate === "number" && !isNaN(startDate)) {
820
- return startDate + mediaElement.currentTime;
821
- }
822
- }
823
- return mediaElement.currentTime;
808
+ var startDate = getStartDate(this.videoElement);
809
+ return (startDate !== null && startDate !== void 0 ? startDate : 0) + this.videoElement.currentTime;
824
810
  }
825
811
  if (manifest !== null) {
826
812
  var currentTime = this.videoElement.currentTime;
@@ -1069,13 +1055,14 @@ var Player = /** @class */ (function (_super) {
1069
1055
  * @returns {Number} - The time the player has seek to
1070
1056
  */
1071
1057
  Player.prototype.seekTo = function (time) {
1058
+ var _a;
1072
1059
  if (this.videoElement === null) {
1073
1060
  throw new Error("Disposed player");
1074
1061
  }
1075
1062
  if (this._priv_contentInfos === null) {
1076
1063
  throw new Error("player: no content loaded");
1077
1064
  }
1078
- var _a = this._priv_contentInfos, isDirectFile = _a.isDirectFile, manifest = _a.manifest;
1065
+ var _b = this._priv_contentInfos, isDirectFile = _b.isDirectFile, manifest = _b.manifest;
1079
1066
  if (!isDirectFile && manifest === null) {
1080
1067
  throw new Error("player: the content did not load yet");
1081
1068
  }
@@ -1093,11 +1080,18 @@ var Player = /** @class */ (function (_super) {
1093
1080
  positionWanted = timeObj.position;
1094
1081
  }
1095
1082
  else if (!isNullOrUndefined(timeObj.wallClockTime)) {
1096
- positionWanted = (isDirectFile || manifest === null) ?
1097
- timeObj.wallClockTime :
1098
- timeObj.wallClockTime - (manifest.availabilityStartTime !== undefined ?
1099
- manifest.availabilityStartTime :
1100
- 0);
1083
+ if (manifest !== null) {
1084
+ positionWanted = timeObj.wallClockTime - ((_a = manifest.availabilityStartTime) !== null && _a !== void 0 ? _a : 0);
1085
+ }
1086
+ else if (isDirectFile && this.videoElement !== null) {
1087
+ var startDate = getStartDate(this.videoElement);
1088
+ if (startDate !== undefined) {
1089
+ positionWanted = timeObj.wallClockTime - startDate;
1090
+ }
1091
+ }
1092
+ if (positionWanted === undefined) {
1093
+ positionWanted = timeObj.wallClockTime;
1094
+ }
1101
1095
  }
1102
1096
  else {
1103
1097
  throw new Error("invalid time object. You must set one of the " +
@@ -2261,6 +2255,12 @@ var Player = /** @class */ (function (_super) {
2261
2255
  positionData.wallClockTime = observation.position + ast;
2262
2256
  positionData.liveGap = maximumPosition - observation.position;
2263
2257
  }
2258
+ else if (isDirectFile && this.videoElement !== null) {
2259
+ var startDate = getStartDate(this.videoElement);
2260
+ if (startDate !== undefined) {
2261
+ positionData.wallClockTime = startDate + observation.position;
2262
+ }
2263
+ }
2264
2264
  this.trigger("positionUpdate", positionData);
2265
2265
  };
2266
2266
  /**
@@ -2302,5 +2302,5 @@ var Player = /** @class */ (function (_super) {
2302
2302
  };
2303
2303
  return Player;
2304
2304
  }(EventEmitter));
2305
- Player.version = /* PLAYER_VERSION */ "3.27.0-dev.2022032800";
2305
+ Player.version = /* PLAYER_VERSION */ "3.27.1-dev.2022040500";
2306
2306
  export default Player;
@@ -13,7 +13,7 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- import { combineLatest as observableCombineLatest, concatAll, EMPTY, from as observableFrom, ignoreElements, mergeMap, } from "rxjs";
16
+ import { combineLatest as observableCombineLatest, concatAll, EMPTY, from as observableFrom, ignoreElements, mergeMap, of as observableOf, } from "rxjs";
17
17
  import log from "../../log";
18
18
  import { getInnerAndOuterTimeRanges } from "../../utils/ranges";
19
19
  /**
@@ -105,6 +105,9 @@ function clearBuffer(segmentBuffer, position, maxBufferBehind, maxBufferAhead) {
105
105
  collectBufferAhead();
106
106
  var clean$ = observableFrom(cleanedupRanges.map(function (range) {
107
107
  log.debug("GC: cleaning range from SegmentBuffer", range);
108
+ if (range.start >= range.end) {
109
+ return observableOf(null);
110
+ }
108
111
  return segmentBuffer.removeBuffer(range.start, range.end);
109
112
  })).pipe(concatAll(),
110
113
  // NOTE As of now (RxJS 7.4.0), RxJS defines `ignoreElements` default
@@ -240,7 +240,8 @@ export default function StreamOrchestrator(content, playbackObserver, abrManager
240
240
  destroyStreams$.next();
241
241
  return observableConcat.apply(void 0, __spreadArray(__spreadArray([], rangesToClean.map(function (_a) {
242
242
  var start = _a.start, end = _a.end;
243
- return segmentBuffer.removeBuffer(start, end).pipe(ignoreElements());
243
+ return start >= end ? EMPTY :
244
+ segmentBuffer.removeBuffer(start, end).pipe(ignoreElements());
244
245
  }), false), [playbackObserver.observe(true).pipe(take(1), mergeMap(function (observation) {
245
246
  return observableConcat(observableOf(EVENTS.needsDecipherabilityFlush(observation.position, !observation.isPaused, observation.duration)), observableDefer(function () {
246
247
  var lastPosition = observation.position +
@@ -15,7 +15,7 @@
15
15
  */
16
16
  import { catchError, concat as observableConcat, defer as observableDefer, EMPTY, ignoreElements, map, merge as observableMerge, mergeMap, of as observableOf, ReplaySubject, startWith, switchMap, } from "rxjs";
17
17
  import config from "../../../config";
18
- import { formatError } from "../../../errors";
18
+ import { formatError, MediaError, } from "../../../errors";
19
19
  import log from "../../../log";
20
20
  import objectAssign from "../../../utils/object_assign";
21
21
  import { getLeftSizeOfRange } from "../../../utils/ranges";
@@ -63,9 +63,15 @@ export default function PeriodStream(_a) {
63
63
  if (SegmentBuffersStore.isNative(bufferType)) {
64
64
  return reloadAfterSwitch(period, bufferType, playbackObserver, 0);
65
65
  }
66
- cleanBuffer$ = segmentBufferStatus.value
67
- .removeBuffer(period.start, period.end == null ? Infinity :
68
- period.end);
66
+ if (period.end === undefined) {
67
+ cleanBuffer$ = segmentBufferStatus.value.removeBuffer(period.start, Infinity);
68
+ }
69
+ else if (period.end <= period.start) {
70
+ cleanBuffer$ = observableOf(null);
71
+ }
72
+ else {
73
+ cleanBuffer$ = segmentBufferStatus.value.removeBuffer(period.start, period.end);
74
+ }
69
75
  }
70
76
  else {
71
77
  if (segmentBufferStatus.type === "uninitialized") {
@@ -160,9 +166,11 @@ function createOrReuseSegmentBuffer(segmentBuffersStore, bufferType, adaptation,
160
166
  * @returns {string}
161
167
  */
162
168
  function getFirstDeclaredMimeType(adaptation) {
163
- var representations = adaptation.representations;
164
- if (representations[0] == null) {
165
- return "";
169
+ var representations = adaptation.getPlayableRepresentations();
170
+ if (representations.length === 0) {
171
+ var noRepErr = new MediaError("NO_PLAYABLE_REPRESENTATION", "No Representation in the chosen " +
172
+ adaptation.type + " Adaptation can be played");
173
+ throw noRepErr;
166
174
  }
167
175
  return representations[0].getMimeTypeString();
168
176
  }
@@ -13,7 +13,7 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- import { concatAll, defer as observableDefer, from as observableFrom, } from "rxjs";
16
+ import { concatAll, defer as observableDefer, from as observableFrom, of as observableOf, } from "rxjs";
17
17
  import config from "../../../config";
18
18
  import log from "../../../log";
19
19
  import { getInnerAndOuterTimeRanges } from "../../../utils/ranges";
@@ -41,7 +41,8 @@ export default function forceGarbageCollection(currentPosition, bufferingQueue)
41
41
  log.debug("Stream: GC cleaning", cleanedupRanges);
42
42
  return observableFrom(cleanedupRanges.map(function (_a) {
43
43
  var start = _a.start, end = _a.end;
44
- return bufferingQueue.removeBuffer(start, end);
44
+ return start >= end ? observableOf(null) :
45
+ bufferingQueue.removeBuffer(start, end);
45
46
  })).pipe(concatAll());
46
47
  });
47
48
  }
@@ -43,8 +43,8 @@ export interface IBufferStatus {
43
43
  */
44
44
  shouldRefreshManifest: boolean;
45
45
  /**
46
- * If 'true', the buffer memory is saturated before being able to download
47
- * at least MIN_REQUIRED_BUFFER_AHEAD ( default : 10sec )
46
+ * If 'true', the buffer memory is saturated, thus we may have issues loading
47
+ * new segments.
48
48
  */
49
49
  isBufferFull: boolean;
50
50
  }
@@ -55,8 +55,8 @@ export default function getBufferStatus(content, wantedStartPosition, playbackOb
55
55
  /** Callback allowing to retrieve a segment's history in the buffer. */
56
56
  var getBufferedHistory = segmentBuffer.getSegmentHistory.bind(segmentBuffer);
57
57
  /** List of segments we will need to download. */
58
- var _b = getNeededSegments({ content: content, bufferedSegments: bufferedSegments, currentPlaybackTime: currentPlaybackTime, fastSwitchThreshold: fastSwitchThreshold, getBufferedHistory: getBufferedHistory, neededRange: neededRange, segmentsBeingPushed: segmentsBeingPushed, maxBufferSize: maxBufferSize }), neededSegments = _b.neededSegments, isBufferFull = _b.isBufferFull;
59
- var prioritizedNeededSegments = neededSegments.map(function (segment) { return ({
58
+ var _b = getNeededSegments({ content: content, bufferedSegments: bufferedSegments, currentPlaybackTime: currentPlaybackTime, fastSwitchThreshold: fastSwitchThreshold, getBufferedHistory: getBufferedHistory, neededRange: neededRange, segmentsBeingPushed: segmentsBeingPushed, maxBufferSize: maxBufferSize }), segmentsToLoad = _b.segmentsToLoad, segmentsOnHold = _b.segmentsOnHold, isBufferFull = _b.isBufferFull;
59
+ var prioritizedNeededSegments = segmentsToLoad.map(function (segment) { return ({
60
60
  priority: getSegmentPriority(segment.time, wantedStartPosition),
61
61
  segment: segment,
62
62
  }); });
@@ -68,7 +68,8 @@ export default function getBufferStatus(content, wantedStartPosition, playbackOb
68
68
  var lastPosition = representation.index.getLastPosition();
69
69
  if (!representation.index.isInitialized() ||
70
70
  period.end === undefined ||
71
- prioritizedNeededSegments.length > 0) {
71
+ prioritizedNeededSegments.length > 0 ||
72
+ segmentsOnHold.length > 0) {
72
73
  hasFinishedLoading = false;
73
74
  }
74
75
  else {
@@ -112,6 +113,11 @@ export default function getBufferStatus(content, wantedStartPosition, playbackOb
112
113
  if (segmentsBeingPushed.length > 0) {
113
114
  nextSegmentStart = Math.min.apply(Math, segmentsBeingPushed.map(function (info) { return info.segment.time; }));
114
115
  }
116
+ if (segmentsOnHold.length > 0) {
117
+ nextSegmentStart = nextSegmentStart !== null ?
118
+ Math.min(nextSegmentStart, segmentsOnHold[0].time) :
119
+ segmentsOnHold[0].time;
120
+ }
115
121
  if (prioritizedNeededSegments.length > 0) {
116
122
  nextSegmentStart = nextSegmentStart !== null ?
117
123
  Math.min(nextSegmentStart, prioritizedNeededSegments[0].segment.time) :
@@ -65,7 +65,17 @@ export interface IGetNeededSegmentsArguments {
65
65
  getBufferedHistory: (context: IChunkContext) => IBufferedHistoryEntry[];
66
66
  }
67
67
  interface INeededSegments {
68
- neededSegments: ISegment[];
68
+ /** Segments that should be loaded right now, by chronological order. */
69
+ segmentsToLoad: ISegment[];
70
+ /**
71
+ * Segments that should be loaded, but not right now, due to some other
72
+ * constraints, such as memory limitations.
73
+ */
74
+ segmentsOnHold: ISegment[];
75
+ /**
76
+ * If `true` the buffer is currently full according to the given limits.
77
+ * Memory should be freed if possible, for example by cleaning the buffers.
78
+ */
69
79
  isBufferFull: boolean;
70
80
  }
71
81
  /**
@@ -32,8 +32,6 @@ export default function getNeededSegments(_a) {
32
32
  var bufferedSegments = _a.bufferedSegments, content = _a.content, currentPlaybackTime = _a.currentPlaybackTime, fastSwitchThreshold = _a.fastSwitchThreshold, getBufferedHistory = _a.getBufferedHistory, neededRange = _a.neededRange, segmentsBeingPushed = _a.segmentsBeingPushed, maxBufferSize = _a.maxBufferSize;
33
33
  var representation = content.representation;
34
34
  var availableBufferSize = getAvailableBufferSize(bufferedSegments, segmentsBeingPushed, maxBufferSize);
35
- // Current buffer length in seconds
36
- var bufferLength = getBufferLength(bufferedSegments, segmentsBeingPushed);
37
35
  var availableSegmentsForRange = representation.index
38
36
  .getSegments(neededRange.start, neededRange.end - neededRange.start);
39
37
  // Remove from `bufferedSegments` any segments we would prefer to replace:
@@ -63,15 +61,16 @@ export default function getNeededSegments(_a) {
63
61
  }
64
62
  return true;
65
63
  });
66
- var _b = config.getCurrent(), MINIMUM_SEGMENT_SIZE = _b.MINIMUM_SEGMENT_SIZE, MIN_BUFFER_LENGTH = _b.MIN_BUFFER_LENGTH, MIN_BUFFER_DISTANCE_BEFORE_CLEAN_UP = _b.MIN_BUFFER_DISTANCE_BEFORE_CLEAN_UP;
67
- var isMemorySaturated = false;
64
+ var _b = config.getCurrent(), MINIMUM_SEGMENT_SIZE = _b.MINIMUM_SEGMENT_SIZE, MIN_BUFFER_AHEAD = _b.MIN_BUFFER_AHEAD;
65
+ var shouldStopLoadingSegments = false;
68
66
  /**
69
67
  * Epsilon compensating for rounding errors when comparing the start and end
70
68
  * time of multiple segments.
71
69
  */
72
70
  var ROUNDING_ERROR = Math.min(1 / 60, MINIMUM_SEGMENT_SIZE);
73
71
  var isBufferFull = false;
74
- var neededSegments = availableSegmentsForRange.filter(function (segment) {
72
+ var segmentsOnHold = [];
73
+ var segmentsToLoad = availableSegmentsForRange.filter(function (segment) {
75
74
  var contentObject = objectAssign({ segment: segment }, content);
76
75
  // First, check that the segment is not already being pushed
77
76
  if (segmentsBeingPushed.length > 0) {
@@ -85,17 +84,8 @@ export default function getNeededSegments(_a) {
85
84
  if (segment.isInit) {
86
85
  return true; // never skip initialization segments
87
86
  }
88
- if (isMemorySaturated) {
89
- // If we are so saturated in memory
90
- // That we cannot download atleast till
91
- // NeededRange.Start ( current position ) + a CONST
92
- // Then the buffer is full
93
- if (time < neededRange.start + MIN_BUFFER_DISTANCE_BEFORE_CLEAN_UP) {
94
- isBufferFull = true;
95
- }
96
- }
97
- if (isMemorySaturated &&
98
- bufferLength > MIN_BUFFER_LENGTH) {
87
+ if (shouldStopLoadingSegments) {
88
+ segmentsOnHold.push(segment);
99
89
  return false;
100
90
  }
101
91
  if (segment.complete && duration < MINIMUM_SEGMENT_SIZE) {
@@ -138,27 +128,34 @@ export default function getNeededSegments(_a) {
138
128
  }
139
129
  }
140
130
  }
131
+ var estimatedSegmentSize = (duration * content.representation.bitrate) / 8000;
132
+ if (availableBufferSize - estimatedSegmentSize < 0) {
133
+ isBufferFull = true;
134
+ if (time > neededRange.start + MIN_BUFFER_AHEAD) {
135
+ shouldStopLoadingSegments = true;
136
+ segmentsOnHold.push(segment);
137
+ return false;
138
+ }
139
+ }
141
140
  // check if there is an hole in place of the segment currently
142
141
  for (var i = 0; i < segmentsToKeep.length; i++) {
143
142
  var completeSeg = segmentsToKeep[i];
144
- if (completeSeg.end > time) {
145
- // `true` if `completeSeg` starts too far after `time`
146
- return completeSeg.start > time + ROUNDING_ERROR ||
147
- // `true` if `completeSeg` ends too soon before `end`
148
- getLastContiguousSegment(segmentsToKeep, i).end < end - ROUNDING_ERROR;
143
+ // For the first already-loaded segment, take the first one ending after
144
+ // this one' s start
145
+ if ((completeSeg.end + ROUNDING_ERROR) > time) {
146
+ var shouldLoad = completeSeg.start > time + ROUNDING_ERROR ||
147
+ getLastContiguousSegment(segmentsToKeep, i).end <
148
+ end - ROUNDING_ERROR;
149
+ if (shouldLoad) {
150
+ availableBufferSize -= estimatedSegmentSize;
151
+ }
152
+ return shouldLoad;
149
153
  }
150
154
  }
151
- var estimatedSegmentSize = (duration * content.representation.bitrate) / 8000;
152
- if (availableBufferSize - estimatedSegmentSize < 0 &&
153
- bufferLength > MIN_BUFFER_LENGTH) {
154
- isMemorySaturated = true;
155
- return false;
156
- }
157
155
  availableBufferSize -= estimatedSegmentSize;
158
- bufferLength += duration;
159
156
  return true;
160
157
  });
161
- return { neededSegments: neededSegments, isBufferFull: isBufferFull };
158
+ return { segmentsToLoad: segmentsToLoad, segmentsOnHold: segmentsOnHold, isBufferFull: isBufferFull };
162
159
  }
163
160
  /**
164
161
  * Compute the estimated available buffer size in memory in kilobytes
@@ -178,28 +175,13 @@ function getAvailableBufferSize(bufferedSegments, segmentsBeingPushed, maxVideoB
178
175
  }, 0);
179
176
  return bufferedSegments.reduce(function (size, chunk) {
180
177
  if (chunk.chunkSize !== undefined) {
181
- return size - (chunk.chunkSize / 8000);
178
+ return size - (chunk.chunkSize / 1000);
182
179
  }
183
180
  else {
184
181
  return size;
185
182
  }
186
183
  }, availableBufferSize);
187
184
  }
188
- /**
189
- * Compute the length of the buffer in seconds
190
- * @param bufferedSegments
191
- * @param segmentsBeingPushed
192
- * @returns bufferLength in seconds
193
- */
194
- function getBufferLength(bufferedSegments, segmentsBeingPushed) {
195
- var bufferLength = bufferedSegments.reduce(function (length, segment) {
196
- return length + (segment.end - segment.start);
197
- }, 0);
198
- var bufferBeingPushed = segmentsBeingPushed.reduce(function (length, segment) {
199
- return length + segment.segment.duration;
200
- }, 0);
201
- return bufferLength + bufferBeingPushed;
202
- }
203
185
  /**
204
186
  * From the given array of buffered chunks (`bufferedSegments`) returns the last
205
187
  * buffered chunk contiguous with the one at the `startIndex` index given.
@@ -166,10 +166,12 @@ export default function RepresentationStream(_a) {
166
166
  var UPTO_CURRENT_POSITION_CLEANUP = config.getCurrent().UPTO_CURRENT_POSITION_CLEANUP;
167
167
  if (status.isBufferFull) {
168
168
  var gcedPosition = Math.max(0, wantedStartPosition - UPTO_CURRENT_POSITION_CLEANUP);
169
- bufferRemoval = segmentBuffer
170
- .removeBuffer(0, gcedPosition)
171
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
172
- .pipe(ignoreElements());
169
+ if (gcedPosition > 0) {
170
+ bufferRemoval = segmentBuffer
171
+ .removeBuffer(0, gcedPosition)
172
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
173
+ .pipe(ignoreElements());
174
+ }
173
175
  }
174
176
  return status.shouldRefreshManifest ?
175
177
  observableConcat(observableOf(EVENTS.needsManifestRefresh()), bufferStatusEvt, bufferRemoval) :
@@ -1065,17 +1065,11 @@ declare const DEFAULT_CONFIG: {
1065
1065
  * there can be in that history.
1066
1066
  */
1067
1067
  BUFFERED_HISTORY_MAXIMUM_ENTRIES: number;
1068
- /**
1069
- * Minimum buffer (in seconds) we should have, regardless of memory
1070
- * constraints
1071
- */
1072
- MIN_BUFFER_LENGTH: number;
1073
1068
  /**
1074
1069
  * Minimum buffer in seconds ahead relative to current time
1075
- * we should be able to download
1076
- * Before trying to agressively free up memory
1070
+ * we should be able to download, even in cases of saturated memory.
1077
1071
  */
1078
- MIN_BUFFER_DISTANCE_BEFORE_CLEAN_UP: number;
1072
+ MIN_BUFFER_AHEAD: number;
1079
1073
  /**
1080
1074
  * Distance in seconds behind the current position
1081
1075
  * the player will free up to in the case we agressively free up memory
@@ -1097,17 +1097,11 @@ var DEFAULT_CONFIG = {
1097
1097
  * there can be in that history.
1098
1098
  */
1099
1099
  BUFFERED_HISTORY_MAXIMUM_ENTRIES: 200,
1100
- /**
1101
- * Minimum buffer (in seconds) we should have, regardless of memory
1102
- * constraints
1103
- */
1104
- MIN_BUFFER_LENGTH: 5,
1105
1100
  /**
1106
1101
  * Minimum buffer in seconds ahead relative to current time
1107
- * we should be able to download
1108
- * Before trying to agressively free up memory
1102
+ * we should be able to download, even in cases of saturated memory.
1109
1103
  */
1110
- MIN_BUFFER_DISTANCE_BEFORE_CLEAN_UP: 10,
1104
+ MIN_BUFFER_AHEAD: 5,
1111
1105
  /**
1112
1106
  * Distance in seconds behind the current position
1113
1107
  * the player will free up to in the case we agressively free up memory