rx-player 3.28.0 → 3.28.1-dev.2022083000

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 (104) hide show
  1. package/.github/workflows/checks.yml +20 -18
  2. package/CHANGELOG.md +11 -0
  3. package/VERSION +1 -1
  4. package/dist/_esm5.processed/compat/eme/load_session.d.ts +5 -6
  5. package/dist/_esm5.processed/compat/eme/load_session.js +5 -6
  6. package/dist/_esm5.processed/core/api/public_api.js +2 -2
  7. package/dist/_esm5.processed/core/decrypt/create_session.js +33 -4
  8. package/dist/_esm5.processed/core/decrypt/utils/loaded_sessions_store.d.ts +14 -0
  9. package/dist/_esm5.processed/core/decrypt/utils/loaded_sessions_store.js +25 -0
  10. package/dist/_esm5.processed/core/fetchers/segment/segment_fetcher.d.ts +1 -1
  11. package/dist/_esm5.processed/core/fetchers/segment/segment_fetcher.js +1 -1
  12. package/dist/_esm5.processed/core/init/content_time_boundaries_observer.js +110 -38
  13. package/dist/_esm5.processed/core/stream/representation/check_for_discontinuity.js +21 -9
  14. package/dist/_esm5.processed/core/stream/representation/get_buffer_status.js +21 -29
  15. package/dist/_esm5.processed/errors/encrypted_media_error.d.ts +1 -2
  16. package/dist/_esm5.processed/errors/encrypted_media_error.js +0 -1
  17. package/dist/_esm5.processed/errors/error_codes.d.ts +6 -1
  18. package/dist/_esm5.processed/errors/media_error.d.ts +1 -2
  19. package/dist/_esm5.processed/errors/media_error.js +0 -1
  20. package/dist/_esm5.processed/errors/network_error.d.ts +1 -2
  21. package/dist/_esm5.processed/errors/network_error.js +0 -1
  22. package/dist/_esm5.processed/errors/other_error.d.ts +1 -2
  23. package/dist/_esm5.processed/errors/other_error.js +0 -1
  24. package/dist/_esm5.processed/manifest/manifest.d.ts +1 -1
  25. package/dist/_esm5.processed/manifest/manifest.js +1 -1
  26. package/dist/_esm5.processed/manifest/representation_index/static.d.ts +21 -6
  27. package/dist/_esm5.processed/manifest/representation_index/static.js +26 -8
  28. package/dist/_esm5.processed/manifest/representation_index/types.d.ts +55 -44
  29. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/base.d.ts +21 -8
  30. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/base.js +25 -10
  31. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/list.d.ts +26 -12
  32. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/list.js +26 -13
  33. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/template.d.ts +23 -7
  34. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/template.js +65 -22
  35. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/timeline/timeline_representation_index.d.ts +20 -3
  36. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/timeline/timeline_representation_index.js +57 -7
  37. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/{is_period_fulfilled.d.ts → utils.d.ts} +3 -6
  38. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/{is_period_fulfilled.js → utils.js} +4 -8
  39. package/dist/_esm5.processed/parsers/manifest/dash/common/parse_periods.js +1 -1
  40. package/dist/_esm5.processed/parsers/manifest/local/parse_local_manifest.js +5 -8
  41. package/dist/_esm5.processed/parsers/manifest/local/representation_index.d.ts +21 -8
  42. package/dist/_esm5.processed/parsers/manifest/local/representation_index.js +49 -14
  43. package/dist/_esm5.processed/parsers/manifest/local/types.d.ts +16 -0
  44. package/dist/_esm5.processed/parsers/manifest/metaplaylist/representation_index.d.ts +20 -6
  45. package/dist/_esm5.processed/parsers/manifest/metaplaylist/representation_index.js +28 -10
  46. package/dist/_esm5.processed/parsers/manifest/smooth/create_parser.js +4 -4
  47. package/dist/_esm5.processed/parsers/manifest/smooth/representation_index.d.ts +21 -12
  48. package/dist/_esm5.processed/parsers/manifest/smooth/representation_index.js +39 -14
  49. package/dist/_esm5.processed/parsers/manifest/utils/get_first_time_from_adaptation.js +1 -1
  50. package/dist/_esm5.processed/parsers/manifest/utils/get_last_time_from_adaptation.js +1 -1
  51. package/dist/_esm5.processed/transports/metaplaylist/pipelines.js +0 -2
  52. package/dist/_esm5.processed/transports/smooth/segment_loader.js +1 -1
  53. package/dist/_esm5.processed/transports/types.d.ts +1 -1
  54. package/dist/_esm5.processed/utils/deep_merge.d.ts +1 -1
  55. package/dist/_esm5.processed/utils/deep_merge.js +6 -5
  56. package/dist/_esm5.processed/utils/task_canceller.d.ts +0 -3
  57. package/dist/_esm5.processed/utils/task_canceller.js +0 -3
  58. package/dist/mpd-parser.wasm +0 -0
  59. package/dist/rx-player.js +1382 -1058
  60. package/dist/rx-player.min.js +1 -1
  61. package/jest.config.js +5 -0
  62. package/package.json +31 -30
  63. package/sonar-project.properties +1 -1
  64. package/src/compat/eme/load_session.ts +5 -6
  65. package/src/core/api/public_api.ts +2 -2
  66. package/src/core/decrypt/create_session.ts +28 -2
  67. package/src/core/decrypt/utils/loaded_sessions_store.ts +29 -0
  68. package/src/core/fetchers/segment/segment_fetcher.ts +1 -1
  69. package/src/core/init/content_time_boundaries_observer.ts +116 -42
  70. package/src/core/stream/representation/check_for_discontinuity.ts +28 -10
  71. package/src/core/stream/representation/get_buffer_status.ts +27 -34
  72. package/src/errors/encrypted_media_error.ts +1 -2
  73. package/src/errors/error_codes.ts +2 -2
  74. package/src/errors/media_error.ts +1 -2
  75. package/src/errors/network_error.ts +1 -2
  76. package/src/errors/other_error.ts +1 -2
  77. package/src/manifest/__tests__/adaptation.test.ts +4 -3
  78. package/src/manifest/__tests__/representation.test.ts +4 -3
  79. package/src/manifest/manifest.ts +1 -1
  80. package/src/manifest/representation_index/__tests__/static.test.ts +5 -4
  81. package/src/manifest/representation_index/static.ts +28 -9
  82. package/src/manifest/representation_index/types.ts +62 -46
  83. package/src/parsers/manifest/dash/common/indexes/base.ts +27 -11
  84. package/src/parsers/manifest/dash/common/indexes/list.ts +32 -15
  85. package/src/parsers/manifest/dash/common/indexes/template.ts +73 -27
  86. package/src/parsers/manifest/dash/common/indexes/timeline/timeline_representation_index.ts +60 -8
  87. package/src/parsers/manifest/dash/common/indexes/{is_period_fulfilled.ts → utils.ts} +4 -13
  88. package/src/parsers/manifest/dash/common/parse_periods.ts +1 -1
  89. package/src/parsers/manifest/local/parse_local_manifest.ts +8 -20
  90. package/src/parsers/manifest/local/representation_index.ts +51 -16
  91. package/src/parsers/manifest/local/types.ts +13 -0
  92. package/src/parsers/manifest/metaplaylist/representation_index.ts +31 -11
  93. package/src/parsers/manifest/smooth/create_parser.ts +4 -4
  94. package/src/parsers/manifest/smooth/representation_index.ts +40 -15
  95. package/src/parsers/manifest/utils/__tests__/get_first_time_from_adaptations.test.ts +4 -3
  96. package/src/parsers/manifest/utils/__tests__/get_last_time_from_adaptation.test.ts +4 -3
  97. package/src/parsers/manifest/utils/get_first_time_from_adaptation.ts +1 -1
  98. package/src/parsers/manifest/utils/get_last_time_from_adaptation.ts +1 -1
  99. package/src/transports/metaplaylist/pipelines.ts +0 -2
  100. package/src/transports/smooth/segment_loader.ts +1 -1
  101. package/src/transports/types.ts +1 -1
  102. package/src/utils/__tests__/initialization_segment_cache.test.ts +7 -0
  103. package/src/utils/deep_merge.ts +7 -4
  104. package/src/utils/task_canceller.ts +0 -3
@@ -54,27 +54,29 @@ jobs:
54
54
  - run: node tests/integration/run.js --bchromehl
55
55
  - run: npm run test:memory
56
56
 
57
- integration_windows:
57
+ # Windows seems to be a lot less stable for some reason.
58
+ # TODO debug?
59
+ # integration_windows:
58
60
 
59
- runs-on: windows-latest
61
+ # runs-on: windows-latest
60
62
 
61
- strategy:
62
- matrix:
63
- node-version: [16.x]
64
- # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
63
+ # strategy:
64
+ # matrix:
65
+ # node-version: [16.x]
66
+ # # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
65
67
 
66
- steps:
67
- - uses: actions/checkout@v2
68
- - name: Use Node.js ${{ matrix.node-version }}
69
- uses: actions/setup-node@v2
70
- with:
71
- node-version: ${{ matrix.node-version }}
72
- cache: 'npm'
73
- - run: npm install
74
- # Firefox seems to have issue with integration tests on GitHub actions only
75
- # TODO to check
76
- - run: node tests/integration/run.js --bchromehl
77
- - run: npm run test:memory
68
+ # steps:
69
+ # - uses: actions/checkout@v2
70
+ # - name: Use Node.js ${{ matrix.node-version }}
71
+ # uses: actions/setup-node@v2
72
+ # with:
73
+ # node-version: ${{ matrix.node-version }}
74
+ # cache: 'npm'
75
+ # - run: npm install
76
+ # # Firefox seems to have issue with integration tests on GitHub actions only
77
+ # # TODO to check
78
+ # - run: node tests/integration/run.js --bchromehl
79
+ # - run: npm run test:memory
78
80
 
79
81
  # MacOS seems to be a lot less stable for some reason.
80
82
  # TODO debug?
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## v3.28.1-dev.2022083000 (2022-08-30)
4
+
5
+ ### Bug fixes
6
+
7
+ - DRM: When using persistent licenses, create new MediaKeySession when `load` resolves with `false`, instead of relying the same, to fix issues with such persistent sessions [#1139]
8
+
9
+ ### Other improvements
10
+
11
+ - In the experimental "local" transport, add `incomingRanges` property to signal the time ranges of remaining data, allowing better discontinuity handling and duration estimates for sill-loading dowloaded contents [#1151]
12
+
3
13
  ## v3.28.0 (2022-07-12)
4
14
 
5
15
  ### Features
@@ -25,6 +35,7 @@
25
35
  ### Other improvements
26
36
 
27
37
  - TTML: Add support for percent based thickness for textOutline in TTML Subtitles [#1108]
38
+ - Improve TypeScript's language servers auto import feature with the RxPlayer by better redirecting to the exported type [#1126]
28
39
  - If seeking after the last potential position, load last segments before ending [#1097]
29
40
  - The duration set on the media element is now only relative to the current chosen tracks (it was previously relative to all potential track). This allows to seek later when switching e.g. to a longer video track [#1102]
30
41
  - Errors coming from an HTMLMediaElement now have the browser's error message if it exists [#1112]
package/VERSION CHANGED
@@ -1 +1 @@
1
- 3.28.0
1
+ 3.28.1-dev.2022083000
@@ -18,14 +18,13 @@ import { ICustomMediaKeySession } from "./custom_media_keys";
18
18
  * Load a persistent session, based on its `sessionId`, on the given
19
19
  * MediaKeySession.
20
20
  *
21
- * Returns an Observable which emits:
22
- * - true if the persistent MediaKeySession was found and loaded
23
- * - false if no persistent MediaKeySession was found with that `sessionId`.
24
- * Then completes.
21
+ * Returns a Promise which resolves with:
22
+ * - `true` if the persistent MediaKeySession was found and loaded
23
+ * - `false` if no persistent MediaKeySession was found with that `sessionId`.
25
24
  *
26
- * The Observable throws if anything goes wrong in the process.
25
+ * The Promise rejects if anything goes wrong in the process.
27
26
  * @param {MediaKeySession} session
28
27
  * @param {string} sessionId
29
- * @returns {Observable}
28
+ * @returns {Promise.<boolean>}
30
29
  */
31
30
  export default function loadSession(session: MediaKeySession | ICustomMediaKeySession, sessionId: string): Promise<boolean>;
@@ -55,15 +55,14 @@ var EME_WAITING_DELAY_LOADED_SESSION_EMPTY_KEYSTATUSES = 100;
55
55
  * Load a persistent session, based on its `sessionId`, on the given
56
56
  * MediaKeySession.
57
57
  *
58
- * Returns an Observable which emits:
59
- * - true if the persistent MediaKeySession was found and loaded
60
- * - false if no persistent MediaKeySession was found with that `sessionId`.
61
- * Then completes.
58
+ * Returns a Promise which resolves with:
59
+ * - `true` if the persistent MediaKeySession was found and loaded
60
+ * - `false` if no persistent MediaKeySession was found with that `sessionId`.
62
61
  *
63
- * The Observable throws if anything goes wrong in the process.
62
+ * The Promise rejects if anything goes wrong in the process.
64
63
  * @param {MediaKeySession} session
65
64
  * @param {string} sessionId
66
- * @returns {Observable}
65
+ * @returns {Promise.<boolean>}
67
66
  */
68
67
  export default function loadSession(session, sessionId) {
69
68
  return __awaiter(this, void 0, void 0, function () {
@@ -88,7 +88,7 @@ var Player = /** @class */ (function (_super) {
88
88
  // Workaround to support Firefox autoplay on FF 42.
89
89
  // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1194624
90
90
  videoElement.preload = "auto";
91
- _this.version = /* PLAYER_VERSION */ "3.28.0";
91
+ _this.version = /* PLAYER_VERSION */ "3.28.1-dev.2022083000";
92
92
  _this.log = log;
93
93
  _this.state = "STOPPED";
94
94
  _this.videoElement = videoElement;
@@ -2295,5 +2295,5 @@ var Player = /** @class */ (function (_super) {
2295
2295
  };
2296
2296
  return Player;
2297
2297
  }(EventEmitter));
2298
- Player.version = /* PLAYER_VERSION */ "3.28.0";
2298
+ Player.version = /* PLAYER_VERSION */ "3.28.1-dev.2022083000";
2299
2299
  export default Player;
@@ -110,7 +110,7 @@ function createAndTryToRetrievePersistentSession(loadedSessionsStore, persistent
110
110
  */
111
111
  function recreatePersistentSession() {
112
112
  return __awaiter(this, void 0, void 0, function () {
113
- var persistentEntry, newEntry;
113
+ var persistentEntry, err_2, newEntry;
114
114
  return __generator(this, function (_a) {
115
115
  switch (_a.label) {
116
116
  case 0:
@@ -122,9 +122,30 @@ function createAndTryToRetrievePersistentSession(loadedSessionsStore, persistent
122
122
  if (persistentEntry !== null) {
123
123
  persistentSessionsStore.delete(persistentEntry.sessionId);
124
124
  }
125
- return [4 /*yield*/, loadedSessionsStore.closeSession(entry.mediaKeySession)];
125
+ _a.label = 1;
126
126
  case 1:
127
+ _a.trys.push([1, 3, , 4]);
128
+ return [4 /*yield*/, loadedSessionsStore.closeSession(entry.mediaKeySession)];
129
+ case 2:
127
130
  _a.sent();
131
+ return [3 /*break*/, 4];
132
+ case 3:
133
+ err_2 = _a.sent();
134
+ // From reading the EME specification in details, it seems that a
135
+ // `MediaKeySession`'s ability to be closed is tightly linked to its
136
+ // possession of a "sanitized session ID" set as `sessionId`.
137
+ // This is never clearly stated however and I'm (Paul B.) always afraid of
138
+ // breaking compatibility when it comes to EME code.
139
+ // So we still try to close the `MediaKeySession` in any case, only, if it
140
+ // fails and it didn't had any `sessionId` set, we just ignore the error.
141
+ // Note that trying to close the `MediaKeySession` might incur some delays
142
+ // in those rare cases.
143
+ if (entry.mediaKeySession.sessionId !== "") {
144
+ throw err_2;
145
+ }
146
+ loadedSessionsStore.removeSessionWithoutClosingIt(entry.mediaKeySession);
147
+ return [3 /*break*/, 4];
148
+ case 4:
128
149
  if (cancelSignal.cancellationError !== null) {
129
150
  throw cancelSignal.cancellationError;
130
151
  }
@@ -135,7 +156,7 @@ function createAndTryToRetrievePersistentSession(loadedSessionsStore, persistent
135
156
  });
136
157
  });
137
158
  }
138
- var entry, storedEntry, hasLoadedSession, err_1;
159
+ var entry, storedEntry, hasLoadedSession, newEntry, err_1;
139
160
  return __generator(this, function (_a) {
140
161
  switch (_a.label) {
141
162
  case 0:
@@ -158,8 +179,16 @@ function createAndTryToRetrievePersistentSession(loadedSessionsStore, persistent
158
179
  if (!hasLoadedSession) {
159
180
  log.warn("DRM: No data stored for the loaded session");
160
181
  persistentSessionsStore.delete(storedEntry.sessionId);
182
+ // The EME specification is kind of implicit about it but it seems from my
183
+ // understanding (Paul B.) that a MediaKeySession on wich a `load` attempt
184
+ // did not succeed due to the loaded session not being found by the
185
+ // browser/CDM, should neither be used anymore nor closed.
186
+ // Thus, we're creating another `"persistent-license"` `MediaKeySession`
187
+ // in that specific case.
188
+ loadedSessionsStore.removeSessionWithoutClosingIt(entry.mediaKeySession);
189
+ newEntry = loadedSessionsStore.createSession(initData, "persistent-license");
161
190
  return [2 /*return*/, { type: "created-session" /* MediaKeySessionLoadingType.Created */,
162
- value: entry }];
191
+ value: newEntry }];
163
192
  }
164
193
  if (hasLoadedSession && isSessionUsable(entry.mediaKeySession)) {
165
194
  persistentSessionsStore.add(initData, initData.keyIds, entry.mediaKeySession);
@@ -106,6 +106,20 @@ export default class LoadedSessionsStore {
106
106
  * @returns {Promise}
107
107
  */
108
108
  closeAllSessions(): Promise<void>;
109
+ /**
110
+ * Find the given `MediaKeySession` in the `LoadedSessionsStore` and removes
111
+ * any reference to it without actually closing it.
112
+ *
113
+ * Returns `true` if the given `mediaKeySession` has been found and removed,
114
+ * `false` otherwise.
115
+ *
116
+ * Note that this may create a `MediaKeySession` leakage in the wrong
117
+ * conditions, cases where this method should be called should be very
118
+ * carefully evaluated.
119
+ * @param {MediaKeySession} mediaKeySession
120
+ * @returns {boolean}
121
+ */
122
+ removeSessionWithoutClosingIt(mediaKeySession: MediaKeySession | ICustomMediaKeySession): boolean;
109
123
  /**
110
124
  * Get the index of a stored MediaKeySession entry based on its
111
125
  * `KeySessionRecord`.
@@ -62,6 +62,7 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
62
62
  };
63
63
  import { closeSession, generateKeyRequest, loadSession, } from "../../../compat";
64
64
  import log from "../../../log";
65
+ import assert from "../../../utils/assert";
65
66
  import isNullOrUndefined from "../../../utils/is_null_or_undefined";
66
67
  import KeySessionRecord from "./key_session_record";
67
68
  /**
@@ -346,6 +347,30 @@ var LoadedSessionsStore = /** @class */ (function () {
346
347
  });
347
348
  });
348
349
  };
350
+ /**
351
+ * Find the given `MediaKeySession` in the `LoadedSessionsStore` and removes
352
+ * any reference to it without actually closing it.
353
+ *
354
+ * Returns `true` if the given `mediaKeySession` has been found and removed,
355
+ * `false` otherwise.
356
+ *
357
+ * Note that this may create a `MediaKeySession` leakage in the wrong
358
+ * conditions, cases where this method should be called should be very
359
+ * carefully evaluated.
360
+ * @param {MediaKeySession} mediaKeySession
361
+ * @returns {boolean}
362
+ */
363
+ LoadedSessionsStore.prototype.removeSessionWithoutClosingIt = function (mediaKeySession) {
364
+ assert(mediaKeySession.sessionId === "", "Initialized `MediaKeySession`s should always be properly closed");
365
+ for (var i = this._storage.length - 1; i >= 0; i--) {
366
+ var stored = this._storage[i];
367
+ if (stored.mediaKeySession === mediaKeySession) {
368
+ this._storage.splice(i, 1);
369
+ return true;
370
+ }
371
+ }
372
+ return false;
373
+ };
349
374
  /**
350
375
  * Get the index of a stored MediaKeySession entry based on its
351
376
  * `KeySessionRecord`.
@@ -26,7 +26,7 @@ import { IBufferType } from "../../segment_buffers";
26
26
  * `options` argument, which may retry a segment request when it fails.
27
27
  *
28
28
  * @param {string} bufferType
29
- * @param {Object} transport
29
+ * @param {Object} pipeline
30
30
  * @param {Object} callbacks
31
31
  * @param {Object} options
32
32
  * @returns {Function}
@@ -34,7 +34,7 @@ var generateRequestID = idGenerator();
34
34
  * `options` argument, which may retry a segment request when it fails.
35
35
  *
36
36
  * @param {string} bufferType
37
- * @param {Object} transport
37
+ * @param {Object} pipeline
38
38
  * @param {Object} callbacks
39
39
  * @param {Object} options
40
40
  * @returns {Function}
@@ -17,6 +17,7 @@ import { distinctUntilChanged, ignoreElements, map, merge as observableMerge, sk
17
17
  import { MediaError } from "../../errors";
18
18
  import { fromEvent } from "../../utils/event_emitter";
19
19
  import filterMap from "../../utils/filter_map";
20
+ import isNullOrUndefined from "../../utils/is_null_or_undefined";
20
21
  import createSharedReference from "../../utils/reference";
21
22
  import EVENTS from "./events_generators";
22
23
  // NOTE As of now (RxJS 7.4.0), RxJS defines `ignoreElements` default
@@ -54,7 +55,7 @@ export default function ContentTimeBoundariesObserver(manifest, streams, playbac
54
55
  "earliest time announced in the Manifest.");
55
56
  return EVENTS.warning(warning);
56
57
  }
57
- else if (wantedPosition > maximumPositionCalculator.getCurrentMaximumPosition()) {
58
+ else if (wantedPosition > maximumPositionCalculator.getMaximumAvailablePosition()) {
58
59
  var warning = new MediaError("MEDIA_TIME_AFTER_MANIFEST", "The current position is after the latest " +
59
60
  "time announced in the Manifest.");
60
61
  return EVENTS.warning(warning);
@@ -68,32 +69,31 @@ export default function ContentTimeBoundariesObserver(manifest, streams, playbac
68
69
  */
69
70
  var contentDuration = createSharedReference(undefined);
70
71
  var updateDurationOnManifestUpdate$ = fromEvent(manifest, "manifestUpdate").pipe(startWith(null), tap(function () {
71
- if (!manifest.isDynamic) {
72
- var maxPos = maximumPositionCalculator.getCurrentMaximumPosition();
73
- contentDuration.setValue(maxPos);
74
- }
75
- else {
76
- // TODO handle finished dynamic contents?
77
- contentDuration.setValue(undefined);
78
- }
72
+ var duration = manifest.isDynamic ?
73
+ maximumPositionCalculator.getEndingPosition() :
74
+ maximumPositionCalculator.getMaximumAvailablePosition();
75
+ contentDuration.setValue(duration);
79
76
  }), ignoreElements());
80
77
  var updateDurationAndTimeBoundsOnTrackChange$ = streams.pipe(tap(function (message) {
81
78
  if (message.type === "adaptationChange") {
79
+ if (!manifest.isLastPeriodKnown) {
80
+ return;
81
+ }
82
82
  var lastPeriod = manifest.periods[manifest.periods.length - 1];
83
83
  if (message.value.period.id === (lastPeriod === null || lastPeriod === void 0 ? void 0 : lastPeriod.id)) {
84
- if (message.value.type === "audio") {
85
- maximumPositionCalculator
86
- .updateLastAudioAdaptation(message.value.adaptation);
87
- if (!manifest.isDynamic) {
88
- contentDuration.setValue(maximumPositionCalculator.getCurrentMaximumPosition());
84
+ if (message.value.type === "audio" || message.value.type === "video") {
85
+ if (message.value.type === "audio") {
86
+ maximumPositionCalculator
87
+ .updateLastAudioAdaptation(message.value.adaptation);
89
88
  }
90
- }
91
- else if (message.value.type === "video") {
92
- maximumPositionCalculator
93
- .updateLastVideoAdaptation(message.value.adaptation);
94
- if (!manifest.isDynamic) {
95
- contentDuration.setValue(maximumPositionCalculator.getCurrentMaximumPosition());
89
+ else {
90
+ maximumPositionCalculator
91
+ .updateLastVideoAdaptation(message.value.adaptation);
96
92
  }
93
+ var newDuration = manifest.isDynamic ?
94
+ maximumPositionCalculator.getMaximumAvailablePosition() :
95
+ maximumPositionCalculator.getEndingPosition();
96
+ contentDuration.setValue(newDuration);
97
97
  }
98
98
  }
99
99
  }
@@ -119,7 +119,7 @@ var MaximumPositionCalculator = /** @class */ (function () {
119
119
  * If no Adaptation has been set, it should be set to `null`.
120
120
  *
121
121
  * Allows to calculate the maximum position more precizely in
122
- * `getCurrentMaximumPosition`.
122
+ * `getMaximumAvailablePosition` and `getEndingPosition`.
123
123
  * @param {Object|null} adaptation
124
124
  */
125
125
  MaximumPositionCalculator.prototype.updateLastAudioAdaptation = function (adaptation) {
@@ -130,18 +130,18 @@ var MaximumPositionCalculator = /** @class */ (function () {
130
130
  * If no Adaptation has been set, it should be set to `null`.
131
131
  *
132
132
  * Allows to calculate the maximum position more precizely in
133
- * `getCurrentMaximumPosition`.
133
+ * `getMaximumAvailablePosition` and `getEndingPosition`.
134
134
  * @param {Object|null} adaptation
135
135
  */
136
136
  MaximumPositionCalculator.prototype.updateLastVideoAdaptation = function (adaptation) {
137
137
  this._lastVideoAdaptation = adaptation;
138
138
  };
139
139
  /**
140
- * Returns an estimate of the maximum position reachable under the current
141
- * circumstances.
140
+ * Returns an estimate of the maximum position currently reachable (i.e.
141
+ * segments are available) under the current circumstances.
142
142
  * @returns {number}
143
143
  */
144
- MaximumPositionCalculator.prototype.getCurrentMaximumPosition = function () {
144
+ MaximumPositionCalculator.prototype.getMaximumAvailablePosition = function () {
145
145
  var _a;
146
146
  if (this._manifest.isDynamic) {
147
147
  return (_a = this._manifest.getLivePosition()) !== null && _a !== void 0 ? _a : this._manifest.getMaximumSafePosition();
@@ -155,7 +155,7 @@ var MaximumPositionCalculator = /** @class */ (function () {
155
155
  return this._manifest.getMaximumSafePosition();
156
156
  }
157
157
  else {
158
- var lastVideoPosition = getLastPositionFromAdaptation(this._lastVideoAdaptation);
158
+ var lastVideoPosition = getLastAvailablePositionFromAdaptation(this._lastVideoAdaptation);
159
159
  if (typeof lastVideoPosition !== "number") {
160
160
  return this._manifest.getMaximumSafePosition();
161
161
  }
@@ -163,15 +163,15 @@ var MaximumPositionCalculator = /** @class */ (function () {
163
163
  }
164
164
  }
165
165
  else if (this._lastVideoAdaptation === null) {
166
- var lastAudioPosition = getLastPositionFromAdaptation(this._lastAudioAdaptation);
166
+ var lastAudioPosition = getLastAvailablePositionFromAdaptation(this._lastAudioAdaptation);
167
167
  if (typeof lastAudioPosition !== "number") {
168
168
  return this._manifest.getMaximumSafePosition();
169
169
  }
170
170
  return lastAudioPosition;
171
171
  }
172
172
  else {
173
- var lastAudioPosition = getLastPositionFromAdaptation(this._lastAudioAdaptation);
174
- var lastVideoPosition = getLastPositionFromAdaptation(this._lastVideoAdaptation);
173
+ var lastAudioPosition = getLastAvailablePositionFromAdaptation(this._lastAudioAdaptation);
174
+ var lastVideoPosition = getLastAvailablePositionFromAdaptation(this._lastVideoAdaptation);
175
175
  if (typeof lastAudioPosition !== "number" ||
176
176
  typeof lastVideoPosition !== "number") {
177
177
  return this._manifest.getMaximumSafePosition();
@@ -181,20 +181,58 @@ var MaximumPositionCalculator = /** @class */ (function () {
181
181
  }
182
182
  }
183
183
  };
184
+ /**
185
+ * Returns an estimate of the actual ending position once
186
+ * the full content is available.
187
+ * Returns `undefined` if that could not be determined, for various reasons.
188
+ * @returns {number|undefined}
189
+ */
190
+ MaximumPositionCalculator.prototype.getEndingPosition = function () {
191
+ var _a, _b;
192
+ if (!this._manifest.isDynamic) {
193
+ return this.getMaximumAvailablePosition();
194
+ }
195
+ if (this._lastVideoAdaptation === undefined ||
196
+ this._lastAudioAdaptation === undefined) {
197
+ return undefined;
198
+ }
199
+ else if (this._lastAudioAdaptation === null) {
200
+ if (this._lastVideoAdaptation === null) {
201
+ return undefined;
202
+ }
203
+ else {
204
+ return (_a = getEndingPositionFromAdaptation(this._lastVideoAdaptation)) !== null && _a !== void 0 ? _a : undefined;
205
+ }
206
+ }
207
+ else if (this._lastVideoAdaptation === null) {
208
+ return (_b = getEndingPositionFromAdaptation(this._lastAudioAdaptation)) !== null && _b !== void 0 ? _b : undefined;
209
+ }
210
+ else {
211
+ var lastAudioPosition = getEndingPositionFromAdaptation(this._lastAudioAdaptation);
212
+ var lastVideoPosition = getEndingPositionFromAdaptation(this._lastVideoAdaptation);
213
+ if (typeof lastAudioPosition !== "number" ||
214
+ typeof lastVideoPosition !== "number") {
215
+ return undefined;
216
+ }
217
+ else {
218
+ return Math.min(lastAudioPosition, lastVideoPosition);
219
+ }
220
+ }
221
+ };
184
222
  return MaximumPositionCalculator;
185
223
  }());
186
224
  /**
187
- * Returns "last time of reference" from the adaptation given.
225
+ * Returns last currently available position from the Adaptation given.
188
226
  * `undefined` if a time could not be found.
189
- * Null if the Adaptation has no segments (it could be that it didn't started or
227
+ * `null` if the Adaptation has no segments (it could be that it didn't started or
190
228
  * that it already finished for example).
191
229
  *
192
- * We consider the earliest last time from every representations in the given
193
- * adaptation.
230
+ * We consider the earliest last available position from every Representation
231
+ * in the given Adaptation.
194
232
  * @param {Object} adaptation
195
233
  * @returns {Number|undefined|null}
196
234
  */
197
- function getLastPositionFromAdaptation(adaptation) {
235
+ function getLastAvailablePositionFromAdaptation(adaptation) {
198
236
  var representations = adaptation.representations;
199
237
  var min = null;
200
238
  /**
@@ -207,18 +245,52 @@ function getLastPositionFromAdaptation(adaptation) {
207
245
  for (var i = 0; i < representations.length; i++) {
208
246
  if (representations[i].index !== lastIndex) {
209
247
  lastIndex = representations[i].index;
210
- var lastPosition = representations[i].index.getLastPosition();
248
+ var lastPosition = representations[i].index.getLastAvailablePosition();
211
249
  if (lastPosition === undefined) { // we cannot tell
212
250
  return undefined;
213
251
  }
214
252
  if (lastPosition !== null) {
215
- min = min == null ? lastPosition :
253
+ min = isNullOrUndefined(min) ? lastPosition :
216
254
  Math.min(min, lastPosition);
217
255
  }
218
256
  }
219
257
  }
220
- if (min === null) { // It means that all positions were null === no segments (yet?)
221
- return null;
258
+ return min;
259
+ }
260
+ /**
261
+ * Returns ending time from the Adaptation given, once all its segments are
262
+ * available.
263
+ * `undefined` if a time could not be found.
264
+ * `null` if the Adaptation has no segments (it could be that it already
265
+ * finished for example).
266
+ *
267
+ * We consider the earliest ending time from every Representation in the given
268
+ * Adaptation.
269
+ * @param {Object} adaptation
270
+ * @returns {Number|undefined|null}
271
+ */
272
+ function getEndingPositionFromAdaptation(adaptation) {
273
+ var representations = adaptation.representations;
274
+ var min = null;
275
+ /**
276
+ * Some Manifest parsers use the exact same `IRepresentationIndex` reference
277
+ * for each Representation of a given Adaptation, because in the actual source
278
+ * Manifest file, indexing data is often defined at Adaptation-level.
279
+ * This variable allows to optimize the logic here when this is the case.
280
+ */
281
+ var lastIndex;
282
+ for (var i = 0; i < representations.length; i++) {
283
+ if (representations[i].index !== lastIndex) {
284
+ lastIndex = representations[i].index;
285
+ var lastPosition = representations[i].index.getEnd();
286
+ if (lastPosition === undefined) { // we cannot tell
287
+ return undefined;
288
+ }
289
+ if (lastPosition !== null) {
290
+ min = isNullOrUndefined(min) ? lastPosition :
291
+ Math.min(min, lastPosition);
292
+ }
293
+ }
222
294
  }
223
295
  return min;
224
296
  }
@@ -75,23 +75,35 @@ export default function checkForDiscontinuity(content, checkedRange, nextSegment
75
75
  // and no segment will fill in that hole
76
76
  (nextSegmentStart === null ||
77
77
  nextBufferedSegment.infos.segment.end <= nextSegmentStart)) {
78
+ var discontinuityEnd = nextBufferedSegment.bufferedStart;
79
+ if (!hasFinishedLoading &&
80
+ representation.index.awaitSegmentBetween(checkedRange.start, discontinuityEnd) !== false) {
81
+ return null;
82
+ }
78
83
  log.debug("RS: current discontinuity encountered", adaptation.type, nextBufferedSegment.bufferedStart);
79
84
  return { start: undefined,
80
- end: nextBufferedSegment.bufferedStart };
85
+ end: discontinuityEnd };
81
86
  }
82
87
  // Check if there's a discontinuity BETWEEN segments of the current range
83
88
  var nextHoleIdx = getIndexOfFirstDiscontinuityBetweenChunks(bufferedSegments, checkedRange, nextBufferedInRangeIdx + 1);
84
89
  // If there was a hole between two consecutives segments, and if this hole
85
90
  // comes before the next segment to load, there is a discontinuity (that hole!)
86
- if (nextHoleIdx !== null &&
87
- (nextSegmentStart === null ||
88
- bufferedSegments[nextHoleIdx].infos.segment.end <= nextSegmentStart)) {
89
- var start = bufferedSegments[nextHoleIdx - 1].bufferedEnd;
90
- var end = bufferedSegments[nextHoleIdx].bufferedStart;
91
- log.debug("RS: future discontinuity encountered", adaptation.type, start, end);
92
- return { start: start, end: end };
91
+ if (nextHoleIdx !== null) {
92
+ var segmentInfoBeforeHole = bufferedSegments[nextHoleIdx - 1];
93
+ var segmentInfoAfterHole = bufferedSegments[nextHoleIdx];
94
+ if (nextSegmentStart === null ||
95
+ segmentInfoAfterHole.infos.segment.end <= nextSegmentStart) {
96
+ if (!hasFinishedLoading && representation.index
97
+ .awaitSegmentBetween(segmentInfoBeforeHole.infos.segment.end, segmentInfoAfterHole.infos.segment.time) !== false) {
98
+ return null;
99
+ }
100
+ var start = segmentInfoBeforeHole.bufferedEnd;
101
+ var end = segmentInfoAfterHole.bufferedStart;
102
+ log.debug("RS: future discontinuity encountered", adaptation.type, start, end);
103
+ return { start: start, end: end };
104
+ }
93
105
  }
94
- else if (nextSegmentStart === null) {
106
+ if (nextSegmentStart === null) {
95
107
  // If no hole between segments and no segment to load, check for a
96
108
  // discontinuity at the end of the Period
97
109
  if (hasFinishedLoading && period.end !== undefined) { // Period is finished
@@ -64,38 +64,30 @@ export default function getBufferStatus(content, initialWantedTime, playbackObse
64
64
  * `true` if the current `RepresentationStream` has loaded all the
65
65
  * needed segments for this Representation until the end of the Period.
66
66
  */
67
- var hasFinishedLoading = neededRange.hasReachedPeriodEnd &&
67
+ var hasFinishedLoading = representation.index.isInitialized() &&
68
+ representation.index.isFinished() &&
69
+ neededRange.hasReachedPeriodEnd &&
68
70
  prioritizedNeededSegments.length === 0 &&
69
71
  segmentsOnHold.length === 0;
70
- var imminentDiscontinuity;
71
- if (!representation.index.isInitialized() ||
72
- // TODO better handle contents not chronologically generated
73
- (!representation.index.areSegmentsChronologicallyGenerated() &&
74
- !hasFinishedLoading)) {
75
- // We might be missing information about future segments
76
- imminentDiscontinuity = null;
72
+ /**
73
+ * Start time in seconds of the next available not-yet pushed segment.
74
+ * `null` if no segment is wanted for the current wanted range.
75
+ */
76
+ var nextSegmentStart = null;
77
+ if (segmentsBeingPushed.length > 0) {
78
+ nextSegmentStart = Math.min.apply(Math, segmentsBeingPushed.map(function (info) { return info.segment.time; }));
77
79
  }
78
- else {
79
- /**
80
- * Start time in seconds of the next available not-yet pushed segment.
81
- * `null` if no segment is wanted for the current wanted range.
82
- */
83
- var nextSegmentStart = null;
84
- if (segmentsBeingPushed.length > 0) {
85
- nextSegmentStart = Math.min.apply(Math, segmentsBeingPushed.map(function (info) { return info.segment.time; }));
86
- }
87
- if (segmentsOnHold.length > 0) {
88
- nextSegmentStart = nextSegmentStart !== null ?
89
- Math.min(nextSegmentStart, segmentsOnHold[0].time) :
90
- segmentsOnHold[0].time;
91
- }
92
- if (prioritizedNeededSegments.length > 0) {
93
- nextSegmentStart = nextSegmentStart !== null ?
94
- Math.min(nextSegmentStart, prioritizedNeededSegments[0].segment.time) :
95
- prioritizedNeededSegments[0].segment.time;
96
- }
97
- imminentDiscontinuity = checkForDiscontinuity(content, neededRange, nextSegmentStart, hasFinishedLoading, bufferedSegments);
80
+ if (segmentsOnHold.length > 0) {
81
+ nextSegmentStart = nextSegmentStart !== null ?
82
+ Math.min(nextSegmentStart, segmentsOnHold[0].time) :
83
+ segmentsOnHold[0].time;
84
+ }
85
+ if (prioritizedNeededSegments.length > 0) {
86
+ nextSegmentStart = nextSegmentStart !== null ?
87
+ Math.min(nextSegmentStart, prioritizedNeededSegments[0].segment.time) :
88
+ prioritizedNeededSegments[0].segment.time;
98
89
  }
90
+ var imminentDiscontinuity = checkForDiscontinuity(content, neededRange, nextSegmentStart, hasFinishedLoading, bufferedSegments);
99
91
  return { imminentDiscontinuity: imminentDiscontinuity, hasFinishedLoading: hasFinishedLoading, neededSegments: prioritizedNeededSegments, isBufferFull: isBufferFull, shouldRefreshManifest: shouldRefreshManifest };
100
92
  }
101
93
  /**
@@ -111,7 +103,7 @@ function getRangeOfNeededSegments(content, initialWantedTime, bufferGoal) {
111
103
  var _a;
112
104
  var wantedStartPosition;
113
105
  var manifest = content.manifest, period = content.period, representation = content.representation;
114
- var lastIndexPosition = representation.index.getLastPosition();
106
+ var lastIndexPosition = representation.index.getLastAvailablePosition();
115
107
  var representationIndex = representation.index;
116
108
  // There is an exception for when the current initially wanted time is already
117
109
  // after the last position with segments AND when we're playing the absolute