rx-player 3.27.0 → 3.27.1-dev.2022041500

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 (79) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/VERSION +1 -1
  3. package/dist/_esm5.processed/core/abr/abr_manager.d.ts +7 -3
  4. package/dist/_esm5.processed/core/abr/abr_manager.js +4 -3
  5. package/dist/_esm5.processed/core/abr/representation_estimator.d.ts +13 -8
  6. package/dist/_esm5.processed/core/abr/representation_estimator.js +4 -4
  7. package/dist/_esm5.processed/core/api/public_api.js +13 -14
  8. package/dist/_esm5.processed/core/init/content_time_boundaries_observer.d.ts +52 -0
  9. package/dist/_esm5.processed/core/init/content_time_boundaries_observer.js +223 -0
  10. package/dist/_esm5.processed/core/init/create_stream_playback_observer.js +17 -6
  11. package/dist/_esm5.processed/core/init/get_initial_time.d.ts +30 -5
  12. package/dist/_esm5.processed/core/init/get_initial_time.js +20 -13
  13. package/dist/_esm5.processed/core/init/load_on_media_source.js +16 -3
  14. package/dist/_esm5.processed/core/init/media_duration_updater.d.ts +56 -0
  15. package/dist/_esm5.processed/core/init/media_duration_updater.js +188 -0
  16. package/dist/_esm5.processed/core/stream/adaptation/adaptation_stream.d.ts +2 -5
  17. package/dist/_esm5.processed/core/stream/adaptation/create_representation_estimator.d.ts +3 -2
  18. package/dist/_esm5.processed/core/stream/adaptation/create_representation_estimator.js +3 -3
  19. package/dist/_esm5.processed/core/stream/orchestrator/stream_orchestrator.js +2 -22
  20. package/dist/_esm5.processed/core/stream/period/period_stream.d.ts +2 -5
  21. package/dist/_esm5.processed/core/stream/period/period_stream.js +6 -4
  22. package/dist/_esm5.processed/core/stream/representation/get_buffer_status.d.ts +4 -2
  23. package/dist/_esm5.processed/core/stream/representation/get_buffer_status.js +85 -42
  24. package/dist/_esm5.processed/core/stream/representation/representation_stream.js +3 -3
  25. package/dist/_esm5.processed/manifest/manifest.d.ts +14 -5
  26. package/dist/_esm5.processed/manifest/manifest.js +31 -12
  27. package/dist/_esm5.processed/manifest/period.d.ts +6 -0
  28. package/dist/_esm5.processed/manifest/period.js +9 -0
  29. package/dist/_esm5.processed/manifest/representation_index/types.d.ts +7 -2
  30. package/dist/_esm5.processed/parsers/manifest/dash/common/get_minimum_and_maximum_positions.d.ts +5 -1
  31. package/dist/_esm5.processed/parsers/manifest/dash/common/get_minimum_and_maximum_positions.js +6 -4
  32. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/base.js +2 -1
  33. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/list.d.ts +4 -0
  34. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/list.js +4 -2
  35. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/template.js +4 -2
  36. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/timeline/timeline_representation_index.js +3 -3
  37. package/dist/_esm5.processed/parsers/manifest/dash/common/parse_mpd.js +28 -20
  38. package/dist/_esm5.processed/parsers/manifest/local/parse_local_manifest.js +3 -2
  39. package/dist/_esm5.processed/parsers/manifest/metaplaylist/metaplaylist_parser.js +9 -3
  40. package/dist/_esm5.processed/parsers/manifest/smooth/create_parser.js +26 -17
  41. package/dist/_esm5.processed/parsers/manifest/types.d.ts +45 -17
  42. package/dist/_esm5.processed/parsers/manifest/utils/{get_maximum_position.d.ts → get_maximum_positions.d.ts} +4 -1
  43. package/dist/_esm5.processed/parsers/manifest/utils/{get_maximum_position.js → get_maximum_positions.js} +10 -6
  44. package/dist/rx-player.js +854 -351
  45. package/dist/rx-player.min.js +1 -1
  46. package/package.json +1 -1
  47. package/sonar-project.properties +1 -1
  48. package/src/core/abr/abr_manager.ts +11 -3
  49. package/src/core/abr/representation_estimator.ts +17 -10
  50. package/src/core/api/public_api.ts +13 -15
  51. package/src/core/init/content_time_boundaries_observer.ts +312 -0
  52. package/src/core/init/create_stream_playback_observer.ts +18 -7
  53. package/src/core/init/get_initial_time.ts +52 -19
  54. package/src/core/init/load_on_media_source.ts +22 -5
  55. package/src/core/init/media_duration_updater.ts +268 -0
  56. package/src/core/stream/adaptation/adaptation_stream.ts +2 -5
  57. package/src/core/stream/adaptation/create_representation_estimator.ts +6 -3
  58. package/src/core/stream/orchestrator/stream_orchestrator.ts +2 -29
  59. package/src/core/stream/period/period_stream.ts +12 -9
  60. package/src/core/stream/representation/get_buffer_status.ts +102 -41
  61. package/src/core/stream/representation/representation_stream.ts +3 -3
  62. package/src/manifest/__tests__/manifest.test.ts +94 -68
  63. package/src/manifest/manifest.ts +73 -33
  64. package/src/manifest/period.ts +10 -0
  65. package/src/manifest/representation_index/types.ts +7 -2
  66. package/src/parsers/manifest/dash/common/get_minimum_and_maximum_positions.ts +10 -5
  67. package/src/parsers/manifest/dash/common/indexes/base.ts +4 -3
  68. package/src/parsers/manifest/dash/common/indexes/list.ts +8 -1
  69. package/src/parsers/manifest/dash/common/indexes/template.ts +8 -3
  70. package/src/parsers/manifest/dash/common/indexes/timeline/timeline_representation_index.ts +7 -7
  71. package/src/parsers/manifest/dash/common/parse_mpd.ts +38 -20
  72. package/src/parsers/manifest/local/parse_local_manifest.ts +3 -2
  73. package/src/parsers/manifest/metaplaylist/metaplaylist_parser.ts +9 -4
  74. package/src/parsers/manifest/smooth/create_parser.ts +36 -18
  75. package/src/parsers/manifest/types.ts +45 -17
  76. package/src/parsers/manifest/utils/{get_maximum_position.ts → get_maximum_positions.ts} +13 -7
  77. package/dist/_esm5.processed/core/init/duration_updater.d.ts +0 -27
  78. package/dist/_esm5.processed/core/init/duration_updater.js +0 -136
  79. package/src/core/init/duration_updater.ts +0 -206
@@ -14,6 +14,7 @@
14
14
  * limitations under the License.
15
15
  */
16
16
  import config from "../../../config";
17
+ import isNullOrUndefined from "../../../utils/is_null_or_undefined";
17
18
  import { SegmentBufferOperation, } from "../../segment_buffers";
18
19
  import checkForDiscontinuity from "./check_for_discontinuity";
19
20
  import getNeededSegments from "./get_needed_segments";
@@ -26,20 +27,19 @@ import getSegmentPriority from "./get_segment_priority";
26
27
  * be filled by any segment, even in the future.
27
28
  *
28
29
  * @param {Object} content
29
- * @param {Object} playbackInfo
30
+ * @param {number} initialWantedTime
31
+ * @param {Object} playbackObserver
30
32
  * @param {number|undefined} fastSwitchThreshold
31
33
  * @param {number} bufferGoal
34
+ * @param {number} maxBufferSize
32
35
  * @param {Object} segmentBuffer
33
36
  * @returns {Object}
34
37
  */
35
- export default function getBufferStatus(content, wantedStartPosition, playbackObserver, fastSwitchThreshold, bufferGoal, maxBufferSize, segmentBuffer) {
36
- var _a;
37
- var period = content.period, representation = content.representation;
38
+ export default function getBufferStatus(content, initialWantedTime, playbackObserver, fastSwitchThreshold, bufferGoal, maxBufferSize, segmentBuffer) {
38
39
  segmentBuffer.synchronizeInventory();
39
- var wantedEndPosition = wantedStartPosition + bufferGoal;
40
- var neededRange = { start: Math.max(wantedStartPosition, period.start),
41
- end: Math.min(wantedEndPosition, (_a = period.end) !== null && _a !== void 0 ? _a : Infinity) };
42
- var shouldRefreshManifest = representation.index.shouldRefresh(wantedStartPosition, wantedEndPosition);
40
+ var representation = content.representation;
41
+ var neededRange = getRangeOfNeededSegments(content, initialWantedTime, bufferGoal);
42
+ var shouldRefreshManifest = representation.index.shouldRefresh(neededRange.start, neededRange.end);
43
43
  /**
44
44
  * Every segment awaiting an "EndOfSegment" operation, which indicates that a
45
45
  * completely-loaded segment is still being pushed to the SegmentBuffer.
@@ -55,47 +55,18 @@ 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 }), segmentsToLoad = _b.segmentsToLoad, segmentsOnHold = _b.segmentsOnHold, isBufferFull = _b.isBufferFull;
58
+ var _a = getNeededSegments({ content: content, bufferedSegments: bufferedSegments, currentPlaybackTime: currentPlaybackTime, fastSwitchThreshold: fastSwitchThreshold, getBufferedHistory: getBufferedHistory, neededRange: neededRange, segmentsBeingPushed: segmentsBeingPushed, maxBufferSize: maxBufferSize }), segmentsToLoad = _a.segmentsToLoad, segmentsOnHold = _a.segmentsOnHold, isBufferFull = _a.isBufferFull;
59
59
  var prioritizedNeededSegments = segmentsToLoad.map(function (segment) { return ({
60
- priority: getSegmentPriority(segment.time, wantedStartPosition),
60
+ priority: getSegmentPriority(segment.time, initialWantedTime),
61
61
  segment: segment,
62
62
  }); });
63
63
  /**
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;
68
- var lastPosition = representation.index.getLastPosition();
69
- if (!representation.index.isInitialized() ||
70
- period.end === undefined ||
71
- prioritizedNeededSegments.length > 0 ||
72
- segmentsOnHold.length > 0) {
73
- hasFinishedLoading = false;
74
- }
75
- else {
76
- if (lastPosition === undefined) {
77
- // We do not know the end of this index.
78
- // If we reached the end of the period, check that all segments are
79
- // available.
80
- hasFinishedLoading = neededRange.end >= period.end &&
81
- representation.index.isFinished();
82
- }
83
- else if (lastPosition === null) {
84
- // There is no available segment in the index currently. If the index
85
- // tells us it has finished generating new segments, we're done.
86
- hasFinishedLoading = representation.index.isFinished();
87
- }
88
- else {
89
- // We have a declared end. Check that our range went until the last
90
- // position available in the index. If that's the case and we're left
91
- // with no segments after filtering them, it means we already have
92
- // downloaded the last segments and have nothing left to do: full.
93
- var endOfRange = period.end !== undefined ? Math.min(period.end, lastPosition) :
94
- lastPosition;
95
- hasFinishedLoading = neededRange.end >= endOfRange &&
96
- representation.index.isFinished();
97
- }
98
- }
67
+ var hasFinishedLoading = neededRange.hasReachedPeriodEnd &&
68
+ prioritizedNeededSegments.length === 0 &&
69
+ segmentsOnHold.length === 0;
99
70
  var imminentDiscontinuity;
100
71
  if (!representation.index.isInitialized() ||
101
72
  // TODO better handle contents not chronologically generated
@@ -127,6 +98,78 @@ export default function getBufferStatus(content, wantedStartPosition, playbackOb
127
98
  }
128
99
  return { imminentDiscontinuity: imminentDiscontinuity, hasFinishedLoading: hasFinishedLoading, neededSegments: prioritizedNeededSegments, isBufferFull: isBufferFull, shouldRefreshManifest: shouldRefreshManifest };
129
100
  }
101
+ /**
102
+ * Returns both the time range of segments that should be loaded (from a
103
+ * starting position to an ending position) and whether the end of the Period is
104
+ * reached by that range.
105
+ * @param {Object} content
106
+ * @param {number} initialWantedTime
107
+ * @param {number} bufferGoal
108
+ * @returns {Object}
109
+ */
110
+ function getRangeOfNeededSegments(content, initialWantedTime, bufferGoal) {
111
+ var _a;
112
+ var wantedStartPosition;
113
+ var manifest = content.manifest, period = content.period, representation = content.representation;
114
+ var lastIndexPosition = representation.index.getLastPosition();
115
+ var representationIndex = representation.index;
116
+ // There is an exception for when the current initially wanted time is already
117
+ // after the last position with segments AND when we're playing the absolute
118
+ // last Period in the Manifest.
119
+ // In that case, we want to actually request at least the last segment to
120
+ // avoid ending the last Period - and by extension the content - with a
121
+ // segment which isn't the last one.
122
+ if (!isNullOrUndefined(lastIndexPosition) &&
123
+ initialWantedTime >= lastIndexPosition &&
124
+ representationIndex.isInitialized() &&
125
+ representationIndex.isFinished() &&
126
+ isPeriodTheCurrentAndLastOne(manifest, period, initialWantedTime)) {
127
+ wantedStartPosition = lastIndexPosition - 1;
128
+ }
129
+ else {
130
+ wantedStartPosition = initialWantedTime;
131
+ }
132
+ var wantedEndPosition = wantedStartPosition + bufferGoal;
133
+ var hasReachedPeriodEnd;
134
+ if (!representation.index.isInitialized() ||
135
+ !representation.index.isFinished() ||
136
+ period.end === undefined) {
137
+ hasReachedPeriodEnd = false;
138
+ }
139
+ else if (lastIndexPosition === undefined) {
140
+ // We do not know the end of this index.
141
+ hasReachedPeriodEnd = wantedEndPosition >= period.end;
142
+ }
143
+ else if (lastIndexPosition === null) {
144
+ // There is no available segment in the index currently.
145
+ hasReachedPeriodEnd = true;
146
+ }
147
+ else {
148
+ // We have a declared end. Check that our range went until the last
149
+ // position available in the index. If that's the case and we're left
150
+ // with no segments after filtering them, it means we already have
151
+ // downloaded the last segments and have nothing left to do: full.
152
+ hasReachedPeriodEnd = wantedEndPosition >= lastIndexPosition;
153
+ }
154
+ return { start: Math.max(wantedStartPosition, period.start),
155
+ end: Math.min(wantedEndPosition, (_a = period.end) !== null && _a !== void 0 ? _a : Infinity), hasReachedPeriodEnd: hasReachedPeriodEnd };
156
+ }
157
+ /**
158
+ * Returns `true` if the given Period is both:
159
+ * - the one being played (the current position is known from `time`)
160
+ * - the absolute last one in the Manifest (that is, there will never be a
161
+ * Period after it).
162
+ * @param {Object} manifest
163
+ * @param {Object} period
164
+ * @param {number} time
165
+ * @returns {boolean}
166
+ */
167
+ function isPeriodTheCurrentAndLastOne(manifest, period, time) {
168
+ var _a;
169
+ return period.containsTime(time) &&
170
+ manifest.isLastPeriodKnown &&
171
+ period.id === ((_a = manifest.periods[manifest.periods.length - 1]) === null || _a === void 0 ? void 0 : _a.id);
172
+ }
130
173
  /**
131
174
  * From the given SegmentInventory, filters the "playable" (in a supported codec
132
175
  * and not known to be undecipherable) buffered Segment Objects which overlap
@@ -99,8 +99,8 @@ export default function RepresentationStream(_a) {
99
99
  reCheckNeededSegments$.pipe(startWith(undefined)),
100
100
  ]).pipe(withLatestFrom(fastSwitchThreshold$), mergeMap(function (_a) {
101
101
  var _b = _a[0], observation = _b[0], bufferGoal = _b[1], maxBufferSize = _b[2], terminate = _b[3], fastSwitchThreshold = _a[1];
102
- var wantedStartPosition = observation.position + observation.wantedTimeOffset;
103
- var status = getBufferStatus(content, wantedStartPosition, playbackObserver, fastSwitchThreshold, bufferGoal, maxBufferSize, segmentBuffer);
102
+ var initialWantedTime = observation.position + observation.wantedTimeOffset;
103
+ var status = getBufferStatus(content, initialWantedTime, playbackObserver, fastSwitchThreshold, bufferGoal, maxBufferSize, segmentBuffer);
104
104
  var neededSegments = status.neededSegments;
105
105
  var neededInitSegment = null;
106
106
  // Add initialization segment if required
@@ -165,7 +165,7 @@ export default function RepresentationStream(_a) {
165
165
  var bufferRemoval = EMPTY;
166
166
  var UPTO_CURRENT_POSITION_CLEANUP = config.getCurrent().UPTO_CURRENT_POSITION_CLEANUP;
167
167
  if (status.isBufferFull) {
168
- var gcedPosition = Math.max(0, wantedStartPosition - UPTO_CURRENT_POSITION_CLEANUP);
168
+ var gcedPosition = Math.max(0, initialWantedTime - UPTO_CURRENT_POSITION_CLEANUP);
169
169
  if (gcedPosition > 0) {
170
170
  bufferRemoval = segmentBuffer
171
171
  .removeBuffer(0, gcedPosition)
@@ -254,15 +254,24 @@ export default class Manifest extends EventEmitter<IManifestEvents> {
254
254
  */
255
255
  update(newManifest: Manifest): void;
256
256
  /**
257
- * Get the minimum position currently defined by the Manifest, in seconds.
257
+ * Returns the theoretical minimum playable position on the content
258
+ * regardless of the current Adaptation chosen, as estimated at parsing
259
+ * time.
258
260
  * @returns {number}
259
261
  */
260
- getMinimumPosition(): number;
262
+ getMinimumSafePosition(): number;
261
263
  /**
262
- * Get the maximum position currently defined by the Manifest, in seconds.
263
- * @returns {number}
264
+ * Get the position of the live edge - that is, the position of what is
265
+ * currently being broadcasted, in seconds.
266
+ * @returns {number|undefined}
267
+ */
268
+ getLivePosition(): number | undefined;
269
+ /**
270
+ * Returns the theoretical maximum playable position on the content
271
+ * regardless of the current Adaptation chosen, as estimated at parsing
272
+ * time.
264
273
  */
265
- getMaximumPosition(): number;
274
+ getMaximumSafePosition(): number;
266
275
  /**
267
276
  * Look in the Manifest for Representations linked to the given key ID,
268
277
  * and mark them as being impossible to decrypt.
@@ -194,38 +194,57 @@ var Manifest = /** @class */ (function (_super) {
194
194
  this._performUpdate(newManifest, MANIFEST_UPDATE_TYPE.Partial);
195
195
  };
196
196
  /**
197
- * Get the minimum position currently defined by the Manifest, in seconds.
197
+ * Returns the theoretical minimum playable position on the content
198
+ * regardless of the current Adaptation chosen, as estimated at parsing
199
+ * time.
198
200
  * @returns {number}
199
201
  */
200
- Manifest.prototype.getMinimumPosition = function () {
202
+ Manifest.prototype.getMinimumSafePosition = function () {
201
203
  var _a, _b;
202
204
  var windowData = this._timeBounds;
203
205
  if (windowData.timeshiftDepth === null) {
204
- return (_a = windowData.absoluteMinimumTime) !== null && _a !== void 0 ? _a : 0;
206
+ return (_a = windowData.minimumSafePosition) !== null && _a !== void 0 ? _a : 0;
205
207
  }
206
208
  var maximumTimeData = windowData.maximumTimeData;
207
209
  var maximumTime;
208
210
  if (!windowData.maximumTimeData.isLinear) {
209
- maximumTime = maximumTimeData.value;
211
+ maximumTime = maximumTimeData.maximumSafePosition;
210
212
  }
211
213
  else {
212
214
  var timeDiff = performance.now() - maximumTimeData.time;
213
- maximumTime = maximumTimeData.value + timeDiff / 1000;
215
+ maximumTime = maximumTimeData.maximumSafePosition + timeDiff / 1000;
214
216
  }
215
217
  var theoricalMinimum = maximumTime - windowData.timeshiftDepth;
216
- return Math.max((_b = windowData.absoluteMinimumTime) !== null && _b !== void 0 ? _b : 0, theoricalMinimum);
218
+ return Math.max((_b = windowData.minimumSafePosition) !== null && _b !== void 0 ? _b : 0, theoricalMinimum);
217
219
  };
218
220
  /**
219
- * Get the maximum position currently defined by the Manifest, in seconds.
220
- * @returns {number}
221
+ * Get the position of the live edge - that is, the position of what is
222
+ * currently being broadcasted, in seconds.
223
+ * @returns {number|undefined}
224
+ */
225
+ Manifest.prototype.getLivePosition = function () {
226
+ var maximumTimeData = this._timeBounds.maximumTimeData;
227
+ if (!this.isLive || maximumTimeData.livePosition === undefined) {
228
+ return undefined;
229
+ }
230
+ if (!maximumTimeData.isLinear) {
231
+ return maximumTimeData.livePosition;
232
+ }
233
+ var timeDiff = performance.now() - maximumTimeData.time;
234
+ return maximumTimeData.livePosition + timeDiff / 1000;
235
+ };
236
+ /**
237
+ * Returns the theoretical maximum playable position on the content
238
+ * regardless of the current Adaptation chosen, as estimated at parsing
239
+ * time.
221
240
  */
222
- Manifest.prototype.getMaximumPosition = function () {
241
+ Manifest.prototype.getMaximumSafePosition = function () {
223
242
  var maximumTimeData = this._timeBounds.maximumTimeData;
224
243
  if (!maximumTimeData.isLinear) {
225
- return maximumTimeData.value;
244
+ return maximumTimeData.maximumSafePosition;
226
245
  }
227
246
  var timeDiff = performance.now() - maximumTimeData.time;
228
- return maximumTimeData.value + timeDiff / 1000;
247
+ return maximumTimeData.maximumSafePosition + timeDiff / 1000;
229
248
  };
230
249
  /**
231
250
  * Look in the Manifest for Representations linked to the given key ID,
@@ -403,7 +422,7 @@ var Manifest = /** @class */ (function (_super) {
403
422
  // Partial updates do not remove old Periods.
404
423
  // This can become a memory problem when playing a content long enough.
405
424
  // Let's clean manually Periods behind the minimum possible position.
406
- var min = this.getMinimumPosition();
425
+ var min = this.getMinimumSafePosition();
407
426
  while (this.periods.length > 0) {
408
427
  var period = this.periods[0];
409
428
  if (period.end === undefined || period.end > min) {
@@ -80,4 +80,10 @@ export default class Period {
80
80
  * @returns {Array.<Adaptation>}
81
81
  */
82
82
  getSupportedAdaptations(type?: IAdaptationType): Adaptation[];
83
+ /**
84
+ * Returns true if the give time is in the time boundaries of this `Period`.
85
+ * @param {number} time
86
+ * @returns {boolean}
87
+ */
88
+ containsTime(time: number): boolean;
83
89
  }
@@ -127,6 +127,15 @@ var Period = /** @class */ (function () {
127
127
  return ada.isSupported;
128
128
  });
129
129
  };
130
+ /**
131
+ * Returns true if the give time is in the time boundaries of this `Period`.
132
+ * @param {number} time
133
+ * @returns {boolean}
134
+ */
135
+ Period.prototype.containsTime = function (time) {
136
+ return time >= this.start && (this.end === undefined ||
137
+ time < this.end);
138
+ };
130
139
  return Period;
131
140
  }());
132
141
  export default Period;
@@ -248,10 +248,15 @@ export interface IRepresentationIndex {
248
248
  */
249
249
  getFirstPosition(): number | null | undefined;
250
250
  /**
251
- * Returns the ending time, in seconds, of the last segment currently
252
- * available in this index.
251
+ * Returns the ending time, in seconds, of the last playable position
252
+ * currently available in this index.
253
253
  * Returns `null` if nothing is in the index
254
254
  * Returns `undefined` if we cannot know this value.
255
+ *
256
+ * The last playable position is generally equivalent to the end of the last
257
+ * segment associated to this index, excepted when it goes over the end limit
258
+ * of the corresponding `Period`, in which case it will be equal to this end
259
+ * instead.
255
260
  * @returns {Number|null|undefined}
256
261
  */
257
262
  getLastPosition(): number | null | undefined;
@@ -18,4 +18,8 @@ import { IParsedPeriod } from "../../types";
18
18
  * @param {Object} periods
19
19
  * @returns {Array.<number>}
20
20
  */
21
- export default function getMinimumAndMaximumPosition(periods: IParsedPeriod[]): [number | undefined, number | undefined];
21
+ export default function getMinimumAndMaximumPositions(periods: IParsedPeriod[]): {
22
+ minimumSafePosition: number | undefined;
23
+ maximumSafePosition: number | undefined;
24
+ maximumUnsafePosition: number | undefined;
25
+ };
@@ -13,16 +13,18 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- import getMaximumPosition from "../../utils/get_maximum_position";
16
+ import getMaximumPositions from "../../utils/get_maximum_positions";
17
17
  import getMinimumPosition from "../../utils/get_minimum_position";
18
18
  /**
19
19
  * @param {Object} periods
20
20
  * @returns {Array.<number>}
21
21
  */
22
- export default function getMinimumAndMaximumPosition(periods) {
22
+ export default function getMinimumAndMaximumPositions(periods) {
23
23
  if (periods.length === 0) {
24
24
  throw new Error("DASH Parser: no period available for a dynamic content");
25
25
  }
26
- return [getMinimumPosition(periods),
27
- getMaximumPosition(periods)];
26
+ var minimumSafePosition = getMinimumPosition(periods);
27
+ var maxPositions = getMaximumPositions(periods);
28
+ return { minimumSafePosition: minimumSafePosition, maximumSafePosition: maxPositions.safe,
29
+ maximumUnsafePosition: maxPositions.unsafe };
28
30
  }
@@ -129,12 +129,13 @@ var BaseRepresentationIndex = /** @class */ (function () {
129
129
  * @returns {Number|null}
130
130
  */
131
131
  BaseRepresentationIndex.prototype.getLastPosition = function () {
132
+ var _a;
132
133
  var timeline = this._index.timeline;
133
134
  if (timeline.length === 0) {
134
135
  return null;
135
136
  }
136
137
  var lastTimelineElement = timeline[timeline.length - 1];
137
- var lastTime = getIndexSegmentEnd(lastTimelineElement, null, this._scaledPeriodEnd);
138
+ var lastTime = Math.min(getIndexSegmentEnd(lastTimelineElement, null, this._scaledPeriodEnd), (_a = this._scaledPeriodEnd) !== null && _a !== void 0 ? _a : Infinity);
138
139
  return fromIndexTime(lastTime, this._index);
139
140
  };
140
141
  /**
@@ -84,6 +84,8 @@ export interface IListIndexIndexArgument {
84
84
  export interface IListIndexContextArgument {
85
85
  /** Start of the period concerned by this RepresentationIndex, in seconds. */
86
86
  periodStart: number;
87
+ /** End of the period concerned by this RepresentationIndex, in seconds. */
88
+ periodEnd: number | undefined;
87
89
  /** Base URL for the Representation concerned. */
88
90
  representationBaseURLs: IResolvedBaseUrl[];
89
91
  /** ID of the Representation concerned. */
@@ -97,6 +99,8 @@ export default class ListRepresentationIndex implements IRepresentationIndex {
97
99
  private _index;
98
100
  /** Start of the period concerned by this RepresentationIndex, in seconds. */
99
101
  protected _periodStart: number;
102
+ /** End of the period concerned by this RepresentationIndex, in seconds. */
103
+ protected _periodEnd: number | undefined;
100
104
  private _isEMSGWhitelisted;
101
105
  /**
102
106
  * @param {Object} index
@@ -27,9 +27,10 @@ var ListRepresentationIndex = /** @class */ (function () {
27
27
  if (index.duration === undefined) {
28
28
  throw new Error("Invalid SegmentList: no duration");
29
29
  }
30
- var periodStart = context.periodStart, representationBaseURLs = context.representationBaseURLs, representationId = context.representationId, representationBitrate = context.representationBitrate, isEMSGWhitelisted = context.isEMSGWhitelisted;
30
+ var periodStart = context.periodStart, periodEnd = context.periodEnd, representationBaseURLs = context.representationBaseURLs, representationId = context.representationId, representationBitrate = context.representationBitrate, isEMSGWhitelisted = context.isEMSGWhitelisted;
31
31
  this._isEMSGWhitelisted = isEMSGWhitelisted;
32
32
  this._periodStart = periodStart;
33
+ this._periodEnd = periodEnd;
33
34
  var presentationTimeOffset = index.presentationTimeOffset != null ? index.presentationTimeOffset :
34
35
  0;
35
36
  var timescale = (_a = index.timescale) !== null && _a !== void 0 ? _a : 1;
@@ -111,9 +112,10 @@ var ListRepresentationIndex = /** @class */ (function () {
111
112
  * @returns {Number}
112
113
  */
113
114
  ListRepresentationIndex.prototype.getLastPosition = function () {
115
+ var _a;
114
116
  var index = this._index;
115
117
  var duration = index.duration, list = index.list;
116
- return ((list.length * duration) / index.timescale) + this._periodStart;
118
+ return Math.min(((list.length * duration) / index.timescale) + this._periodStart, (_a = this._periodEnd) !== null && _a !== void 0 ? _a : Infinity);
117
119
  };
118
120
  /**
119
121
  * Returns true if a Segment returned by this index is still considered
@@ -55,7 +55,8 @@ var TemplateRepresentationIndex = /** @class */ (function () {
55
55
  mediaURLs: createIndexURLs(urlSources, index.media, representationId, representationBitrate), presentationTimeOffset: presentationTimeOffset, startNumber: index.startNumber };
56
56
  this._isDynamic = isDynamic;
57
57
  this._periodStart = periodStart;
58
- this._scaledPeriodEnd = periodEnd == null ? undefined :
58
+ this._scaledPeriodEnd = periodEnd === undefined ?
59
+ undefined :
59
60
  (periodEnd - periodStart) * timescale;
60
61
  this._isEMSGWhitelisted = isEMSGWhitelisted;
61
62
  }
@@ -143,6 +144,7 @@ var TemplateRepresentationIndex = /** @class */ (function () {
143
144
  * @returns {number|null}
144
145
  */
145
146
  TemplateRepresentationIndex.prototype.getLastPosition = function () {
147
+ var _a;
146
148
  var lastSegmentStart = this._getLastSegmentStart();
147
149
  if (lastSegmentStart == null) {
148
150
  // In that case (null or undefined), getLastPosition should reflect
@@ -150,7 +152,7 @@ var TemplateRepresentationIndex = /** @class */ (function () {
150
152
  // the two functions. So, we return the result of the latter.
151
153
  return lastSegmentStart;
152
154
  }
153
- var lastSegmentEnd = lastSegmentStart + this._index.duration;
155
+ var lastSegmentEnd = Math.min(lastSegmentStart + this._index.duration, (_a = this._scaledPeriodEnd) !== null && _a !== void 0 ? _a : Infinity);
154
156
  return (lastSegmentEnd / this._index.timescale) + this._periodStart;
155
157
  };
156
158
  /**
@@ -74,7 +74,7 @@ var TimelineRepresentationIndex = /** @class */ (function () {
74
74
  startNumber: index.startNumber,
75
75
  timeline: (_c = index.timeline) !== null && _c !== void 0 ? _c : null, timescale: timescale };
76
76
  this._scaledPeriodStart = toIndexTime(periodStart, this._index);
77
- this._scaledPeriodEnd = periodEnd == null ? undefined :
77
+ this._scaledPeriodEnd = periodEnd === undefined ? undefined :
78
78
  toIndexTime(periodEnd, this._index);
79
79
  }
80
80
  /**
@@ -136,7 +136,7 @@ var TimelineRepresentationIndex = /** @class */ (function () {
136
136
  if (this._index.timeline === null) {
137
137
  this._index.timeline = this._getTimeline();
138
138
  }
139
- var lastTime = TimelineRepresentationIndex.getIndexEnd(this._index.timeline, this._scaledPeriodStart);
139
+ var lastTime = TimelineRepresentationIndex.getIndexEnd(this._index.timeline, this._scaledPeriodEnd);
140
140
  return lastTime === null ? null :
141
141
  fromIndexTime(lastTime, this._index);
142
142
  };
@@ -293,7 +293,7 @@ var TimelineRepresentationIndex = /** @class */ (function () {
293
293
  if (timeline.length <= 0) {
294
294
  return null;
295
295
  }
296
- return getIndexSegmentEnd(timeline[timeline.length - 1], null, scaledPeriodEnd);
296
+ return Math.min(getIndexSegmentEnd(timeline[timeline.length - 1], null, scaledPeriodEnd), scaledPeriodEnd !== null && scaledPeriodEnd !== void 0 ? scaledPeriodEnd : Infinity);
297
297
  };
298
298
  /**
299
299
  * Allows to generate the "timeline" for this RepresentationIndex.
@@ -29,7 +29,7 @@ import { normalizeBaseURL } from "../../../../utils/resolve_url";
29
29
  // eslint-disable-next-line max-len
30
30
  import getClockOffset from "./get_clock_offset";
31
31
  import getHTTPUTCTimingURL from "./get_http_utc-timing_url";
32
- import getMinimumAndMaximumPosition from "./get_minimum_and_maximum_positions";
32
+ import getMinimumAndMaximumPositions from "./get_minimum_and_maximum_positions";
33
33
  import parseAvailabilityStartTime from "./parse_availability_start_time";
34
34
  import parsePeriods from "./parse_periods";
35
35
  import resolveBaseURLs from "./resolve_base_urls";
@@ -159,56 +159,64 @@ function parseCompleteIntermediateRepresentation(mpdIR, args, warnings, xlinkInf
159
159
  config.getCurrent().DASH_FALLBACK_LIFETIME_WHEN_MINIMUM_UPDATE_PERIOD_EQUAL_0 :
160
160
  rootAttributes.minimumUpdatePeriod;
161
161
  }
162
- var _d = getMinimumAndMaximumPosition(parsedPeriods), contentStart = _d[0], contentEnd = _d[1];
162
+ var _d = getMinimumAndMaximumPositions(parsedPeriods), minimumSafePosition = _d.minimumSafePosition, maximumSafePosition = _d.maximumSafePosition, maximumUnsafePosition = _d.maximumUnsafePosition;
163
163
  var now = performance.now();
164
164
  if (!isDynamic) {
165
- minimumTime = contentStart !== undefined ? contentStart :
165
+ minimumTime = minimumSafePosition !== undefined ? minimumSafePosition :
166
166
  ((_a = parsedPeriods[0]) === null || _a === void 0 ? void 0 : _a.start) !== undefined ? parsedPeriods[0].start :
167
167
  0;
168
- var maximumTime = mediaPresentationDuration !== null && mediaPresentationDuration !== void 0 ? mediaPresentationDuration : Infinity;
168
+ var finalMaximumSafePosition = mediaPresentationDuration !== null && mediaPresentationDuration !== void 0 ? mediaPresentationDuration : Infinity;
169
169
  if (parsedPeriods[parsedPeriods.length - 1] !== undefined) {
170
170
  var lastPeriod = parsedPeriods[parsedPeriods.length - 1];
171
171
  var lastPeriodEnd = (_b = lastPeriod.end) !== null && _b !== void 0 ? _b : (lastPeriod.duration !== undefined ?
172
172
  lastPeriod.start + lastPeriod.duration :
173
173
  undefined);
174
- if (lastPeriodEnd !== undefined && lastPeriodEnd < maximumTime) {
175
- maximumTime = lastPeriodEnd;
174
+ if (lastPeriodEnd !== undefined && lastPeriodEnd < finalMaximumSafePosition) {
175
+ finalMaximumSafePosition = lastPeriodEnd;
176
176
  }
177
177
  }
178
- if (contentEnd !== undefined && contentEnd < maximumTime) {
179
- maximumTime = contentEnd;
178
+ if (maximumSafePosition !== undefined &&
179
+ maximumSafePosition < finalMaximumSafePosition) {
180
+ finalMaximumSafePosition = maximumSafePosition;
180
181
  }
181
182
  maximumTimeData = { isLinear: false,
182
- value: maximumTime,
183
+ maximumSafePosition: finalMaximumSafePosition,
184
+ livePosition: undefined,
183
185
  time: now };
184
186
  }
185
187
  else {
186
- minimumTime = contentStart;
188
+ minimumTime = minimumSafePosition;
187
189
  timeshiftDepth = timeShiftBufferDepth !== null && timeShiftBufferDepth !== void 0 ? timeShiftBufferDepth : null;
188
- var maximumTime = void 0;
189
- if (contentEnd !== undefined) {
190
- maximumTime = contentEnd;
190
+ var finalMaximumSafePosition = void 0;
191
+ var livePosition = void 0;
192
+ if (maximumUnsafePosition !== undefined) {
193
+ livePosition = maximumUnsafePosition;
194
+ }
195
+ if (maximumSafePosition !== undefined) {
196
+ finalMaximumSafePosition = maximumSafePosition;
191
197
  }
192
198
  else {
193
199
  var ast = availabilityStartTime !== null && availabilityStartTime !== void 0 ? availabilityStartTime : 0;
194
200
  var externalClockOffset = args.externalClockOffset;
195
201
  if (externalClockOffset === undefined) {
196
202
  log.warn("DASH Parser: use system clock to define maximum position");
197
- maximumTime = (Date.now() / 1000) - ast;
203
+ finalMaximumSafePosition = (Date.now() / 1000) - ast;
198
204
  }
199
205
  else {
200
206
  var serverTime = performance.now() + externalClockOffset;
201
- maximumTime = (serverTime / 1000) - ast;
207
+ finalMaximumSafePosition = (serverTime / 1000) - ast;
202
208
  }
203
209
  }
210
+ if (livePosition === undefined) {
211
+ livePosition = finalMaximumSafePosition;
212
+ }
204
213
  maximumTimeData = { isLinear: true,
205
- value: maximumTime,
206
- time: now };
214
+ maximumSafePosition: finalMaximumSafePosition, livePosition: livePosition, time: now };
207
215
  // if the minimum calculated time is even below the buffer depth, perhaps we
208
216
  // can go even lower in terms of depth
209
217
  if (timeshiftDepth !== null && minimumTime !== undefined &&
210
- maximumTime - minimumTime > timeshiftDepth) {
211
- timeshiftDepth = maximumTime - minimumTime;
218
+ finalMaximumSafePosition - minimumTime > timeshiftDepth) {
219
+ timeshiftDepth = finalMaximumSafePosition - minimumTime;
212
220
  }
213
221
  }
214
222
  // `isLastPeriodKnown` should be `true` in two cases for DASH contents:
@@ -232,7 +240,7 @@ function parseCompleteIntermediateRepresentation(mpdIR, args, warnings, xlinkInf
232
240
  publishTime: rootAttributes.publishTime,
233
241
  suggestedPresentationDelay: rootAttributes.suggestedPresentationDelay,
234
242
  transportType: "dash",
235
- timeBounds: { absoluteMinimumTime: minimumTime, timeshiftDepth: timeshiftDepth, maximumTimeData: maximumTimeData },
243
+ timeBounds: { minimumSafePosition: minimumTime, timeshiftDepth: timeshiftDepth, maximumTimeData: maximumTimeData },
236
244
  lifetime: lifetime,
237
245
  uris: args.url == null ?
238
246
  rootChildren.locations : __spreadArray([args.url], rootChildren.locations, true),
@@ -38,10 +38,11 @@ export default function parseLocalManifest(localManifest) {
38
38
  isLastPeriodKnown: isFinished,
39
39
  isLive: false,
40
40
  uris: [],
41
- timeBounds: { absoluteMinimumTime: minimumPosition !== null && minimumPosition !== void 0 ? minimumPosition : 0,
41
+ timeBounds: { minimumSafePosition: minimumPosition !== null && minimumPosition !== void 0 ? minimumPosition : 0,
42
42
  timeshiftDepth: null,
43
43
  maximumTimeData: { isLinear: false,
44
- value: maximumPosition,
44
+ maximumSafePosition: maximumPosition,
45
+ livePosition: undefined,
45
46
  time: performance.now() } },
46
47
  periods: parsedPeriods };
47
48
  }
@@ -230,9 +230,15 @@ function createManifest(mplData, manifests, parserOptions) {
230
230
  var manifest = { availabilityStartTime: 0, clockOffset: clockOffset, suggestedPresentationDelay: 10, periods: periods, transportType: "metaplaylist",
231
231
  isLive: isDynamic, isDynamic: isDynamic, isLastPeriodKnown: isLastPeriodKnown, uris: url == null ? [] :
232
232
  [url],
233
- timeBounds: { minimumTime: minimumTime, timeshiftDepth: null,
234
- maximumTimeData: { isLinear: false,
235
- value: maximumTime, time: time } },
233
+ // TODO more precize time bounds?
234
+ timeBounds: { minimumSafePosition: minimumTime,
235
+ timeshiftDepth: null,
236
+ maximumTimeData: {
237
+ isLinear: false,
238
+ maximumSafePosition: maximumTime,
239
+ livePosition: undefined,
240
+ time: time,
241
+ } },
236
242
  lifetime: mplData.pollInterval };
237
243
  return manifest;
238
244
  }