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
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "rx-player",
3
3
  "author": "Canal+",
4
- "version": "3.27.0",
4
+ "version": "3.27.1-dev.2022041500",
5
5
  "description": "Canal+ HTML5 Video Player",
6
6
  "main": "./dist/rx-player.js",
7
7
  "keywords": [
@@ -1,7 +1,7 @@
1
1
  sonar.projectKey=rx-player
2
2
  sonar.organization=rx-player
3
3
  sonar.projectName=rx-player
4
- sonar.projectVersion=3.27.0
4
+ sonar.projectVersion=3.27.1-dev.2022041500
5
5
  sonar.sources=./src,./demo,./tests
6
6
  sonar.exclusions=demo/full/bundle.js,demo/standalone/lib.js,demo/bundle.js
7
7
  sonar.host.url=https://sonarcloud.io
@@ -21,7 +21,11 @@ import {
21
21
  of as observableOf,
22
22
  } from "rxjs";
23
23
  import log from "../../log";
24
- import { Representation } from "../../manifest";
24
+ import Manifest, {
25
+ Adaptation,
26
+ Period,
27
+ Representation,
28
+ } from "../../manifest";
25
29
  import objectAssign from "../../utils/object_assign";
26
30
  import { ISharedReference } from "../../utils/reference";
27
31
  import takeFirstSet from "../../utils/take_first_set";
@@ -98,18 +102,21 @@ export default class ABRManager {
98
102
  * Take type and an array of the available representations, spit out an
99
103
  * observable emitting the best representation (given the network/buffer
100
104
  * state).
101
- * @param {string} type
105
+ * @param {Object} context
102
106
  * @param {Array.<Representation>} representations
103
107
  * @param {Observable<Object>} observation$
104
108
  * @param {Observable<Object>} streamEvents$
105
109
  * @returns {Observable}
106
110
  */
107
111
  public get$(
108
- type : IBufferType,
112
+ context : { manifest : Manifest;
113
+ period : Period;
114
+ adaptation : Adaptation; },
109
115
  representations : Representation[],
110
116
  observation$ : Observable<IABRManagerPlaybackObservation>,
111
117
  streamEvents$ : Observable<IABRStreamEvents>
112
118
  ) : Observable<IABREstimate> {
119
+ const { type } = context.adaptation;
113
120
  const bandwidthEstimator = this._getBandwidthEstimator(type);
114
121
  const manualBitrate$ =
115
122
  takeFirstSet<Observable<number>>(this._manualBitrates[type]?.asObservable(),
@@ -125,6 +132,7 @@ export default class ABRManager {
125
132
  this._throttlers.throttleBitrate[type],
126
133
  this._throttlers.throttle[type]);
127
134
  return RepresentationEstimator({ bandwidthEstimator,
135
+ context,
128
136
  streamEvents$,
129
137
  observation$,
130
138
  filters$,
@@ -29,9 +29,10 @@ import {
29
29
  withLatestFrom,
30
30
  } from "rxjs";
31
31
  import log from "../../log";
32
- import {
32
+ import Manifest, {
33
33
  Adaptation,
34
34
  ISegment,
35
+ Period,
35
36
  Representation,
36
37
  } from "../../manifest";
37
38
  import { getLeftSizeOfRange } from "../../utils/ranges";
@@ -141,12 +142,8 @@ export interface IRepresentationEstimatorPlaybackObservation {
141
142
  speed : number;
142
143
  /** `duration` property of the HTMLMediaElement on which the content plays. */
143
144
  duration : number;
144
- /**
145
- * For live contents only, difference between the maximum playable position
146
- * and the current position.
147
- * `undefined` for non-live contents.
148
- */
149
- liveGap : number | undefined;
145
+ /** Theoretical maximum position on the content that can currently be played. */
146
+ maximumPosition : number;
150
147
  }
151
148
 
152
149
  /** Content of the `IABRMetricsEvent` event's payload. */
@@ -357,6 +354,15 @@ export interface IRepresentationEstimatorArguments {
357
354
  maxAutoBitrate$ : Observable<number>;
358
355
  /** The list of Representations the `RepresentationEstimator` can choose from. */
359
356
  representations : Representation[];
357
+ /** Context for the list of Representations to choose. */
358
+ context : {
359
+ /** In which Manifest the Representations are. */
360
+ manifest : Manifest;
361
+ /** In which Period the Representations are. */
362
+ period : Period;
363
+ /** In which Adaptation the Representations are. */
364
+ adaptation : Adaptation;
365
+ };
360
366
  }
361
367
 
362
368
  /**
@@ -396,6 +402,7 @@ function getFilteredRepresentations(
396
402
  */
397
403
  export default function RepresentationEstimator({
398
404
  bandwidthEstimator,
405
+ context,
399
406
  observation$,
400
407
  filters$,
401
408
  initialBitrate,
@@ -534,7 +541,7 @@ export default function RepresentationEstimator({
534
541
  bufferBasedBitrate ],
535
542
  currentRepresentation ]
536
543
  ) : IABREstimate => {
537
- const { bufferGap, liveGap } = observation;
544
+ const { bufferGap, position, maximumPosition } = observation;
538
545
 
539
546
  const filteredReps = getFilteredRepresentations(representations,
540
547
  filters);
@@ -615,8 +622,8 @@ export default function RepresentationEstimator({
615
622
  let chosenRepFromGuessMode : Representation | null = null;
616
623
  if (lowLatencyMode &&
617
624
  currentRepresentation !== null &&
618
- liveGap !== undefined &&
619
- liveGap < 40)
625
+ context.manifest.isDynamic &&
626
+ maximumPosition - position < 40)
620
627
  {
621
628
  chosenRepFromGuessMode = guessBasedChooser.getGuess(representations,
622
629
  observation,
@@ -428,7 +428,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
428
428
  // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1194624
429
429
  videoElement.preload = "auto";
430
430
 
431
- this.version = /* PLAYER_VERSION */"3.27.0";
431
+ this.version = /* PLAYER_VERSION */"3.27.1-dev.2022041500";
432
432
  this.log = log;
433
433
  this.state = "STOPPED";
434
434
  this.videoElement = videoElement;
@@ -1604,14 +1604,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
1604
1604
  if (positionWanted === undefined) {
1605
1605
  throw new Error("invalid time given");
1606
1606
  }
1607
- let seekAt = positionWanted;
1608
- if (manifest !== null && !manifest.isLive) {
1609
- const maximumTime = manifest.getMaximumPosition();
1610
- seekAt = maximumTime !== undefined ? Math.min(positionWanted,
1611
- maximumTime - 0.001) :
1612
- positionWanted;
1613
- }
1614
- this.videoElement.currentTime = seekAt;
1607
+ this.videoElement.currentTime = positionWanted;
1615
1608
  return positionWanted;
1616
1609
  }
1617
1610
 
@@ -2235,7 +2228,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
2235
2228
 
2236
2229
  const { manifest } = this._priv_contentInfos;
2237
2230
  if (manifest !== null) {
2238
- return manifest.getMinimumPosition();
2231
+ return manifest.getMinimumSafePosition();
2239
2232
  }
2240
2233
  return null;
2241
2234
  }
@@ -2259,7 +2252,10 @@ class Player extends EventEmitter<IPublicAPIEvent> {
2259
2252
  }
2260
2253
 
2261
2254
  if (manifest !== null) {
2262
- return manifest.getMaximumPosition();
2255
+ if (!manifest.isDynamic && this.videoElement !== null) {
2256
+ return this.videoElement.duration;
2257
+ }
2258
+ return manifest.getMaximumSafePosition();
2263
2259
  }
2264
2260
  return null;
2265
2261
  }
@@ -2864,7 +2860,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
2864
2860
 
2865
2861
  this._priv_lastContentPlaybackInfos.lastPlaybackPosition = observation.position;
2866
2862
 
2867
- const maximumPosition = manifest !== null ? manifest.getMaximumPosition() :
2863
+ const maximumPosition = manifest !== null ? manifest.getMaximumSafePosition() :
2868
2864
  undefined;
2869
2865
  const positionData : IPositionUpdateItem = {
2870
2866
  position: observation.position,
@@ -2878,13 +2874,15 @@ class Player extends EventEmitter<IPublicAPIEvent> {
2878
2874
  };
2879
2875
 
2880
2876
  if (manifest !== null &&
2881
- maximumPosition !== undefined &&
2882
2877
  manifest.isLive &&
2883
2878
  observation.position > 0
2884
2879
  ) {
2885
2880
  const ast = manifest.availabilityStartTime ?? 0;
2886
2881
  positionData.wallClockTime = observation.position + ast;
2887
- positionData.liveGap = maximumPosition - observation.position;
2882
+ const livePosition = manifest.getLivePosition();
2883
+ if (livePosition !== undefined) {
2884
+ positionData.liveGap = livePosition - observation.position;
2885
+ }
2888
2886
  } else if (isDirectFile && this.videoElement !== null) {
2889
2887
  const startDate = getStartDate(this.videoElement);
2890
2888
  if (startDate !== undefined) {
@@ -2942,7 +2940,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
2942
2940
  return activeRepresentations[currentPeriod.id];
2943
2941
  }
2944
2942
  }
2945
- Player.version = /* PLAYER_VERSION */"3.27.0";
2943
+ Player.version = /* PLAYER_VERSION */"3.27.1-dev.2022041500";
2946
2944
 
2947
2945
  /** Payload emitted with a `positionUpdate` event. */
2948
2946
  export interface IPositionUpdateItem {
@@ -0,0 +1,312 @@
1
+ /**
2
+ * Copyright 2015 CANAL+ Group
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import {
18
+ distinctUntilChanged,
19
+ ignoreElements,
20
+ map,
21
+ merge as observableMerge,
22
+ Observable,
23
+ skipWhile,
24
+ startWith,
25
+ tap,
26
+ } from "rxjs";
27
+ import { MediaError } from "../../errors";
28
+ import Manifest, {
29
+ Adaptation,
30
+ IRepresentationIndex,
31
+ } from "../../manifest";
32
+ import { fromEvent } from "../../utils/event_emitter";
33
+ import filterMap from "../../utils/filter_map";
34
+ import createSharedReference from "../../utils/reference";
35
+ import { IReadOnlyPlaybackObserver } from "../api";
36
+ import { IStreamOrchestratorEvent } from "../stream";
37
+ import EVENTS from "./events_generators";
38
+ import { IWarningEvent } from "./types";
39
+
40
+ // NOTE As of now (RxJS 7.4.0), RxJS defines `ignoreElements` default
41
+ // first type parameter as `any` instead of the perfectly fine `unknown`,
42
+ // leading to linter issues, as it forbids the usage of `any`.
43
+ // This is why we're disabling the eslint rule.
44
+ /* eslint-disable @typescript-eslint/no-unsafe-argument */
45
+
46
+ /**
47
+ * Observes the position and Adaptations being played and deduce various events
48
+ * related to the available time boundaries:
49
+ * - Emit when the theoretical duration of the content becomes known or when it
50
+ * changes.
51
+ * - Emit warnings when the duration goes out of what is currently
52
+ * theoretically playable.
53
+ *
54
+ * @param {Object} manifest
55
+ * @param {Observable} streams
56
+ * @param {Object} playbackObserver
57
+ * @returns {Observable}
58
+ */
59
+ export default function ContentTimeBoundariesObserver(
60
+ manifest : Manifest,
61
+ streams : Observable<IStreamOrchestratorEvent>,
62
+ playbackObserver : IReadOnlyPlaybackObserver<IContentTimeObserverPlaybackObservation>
63
+
64
+ ) : Observable<IContentDurationUpdateEvent | IWarningEvent> {
65
+ /**
66
+ * Allows to calculate the minimum and maximum playable position on the
67
+ * whole content.
68
+ */
69
+ const maximumPositionCalculator = new MaximumPositionCalculator(manifest);
70
+
71
+ // trigger warnings when the wanted time is before or after the manifest's
72
+ // segments
73
+ const outOfManifest$ = playbackObserver.observe(true).pipe(
74
+ filterMap<IContentTimeObserverPlaybackObservation, IWarningEvent, null>((
75
+ { position, wantedTimeOffset }
76
+ ) => {
77
+ const offsetedPosition = wantedTimeOffset + position;
78
+ if (
79
+ offsetedPosition < manifest.getMinimumSafePosition()
80
+ ) {
81
+ const warning = new MediaError("MEDIA_TIME_BEFORE_MANIFEST",
82
+ "The current position is behind the " +
83
+ "earliest time announced in the Manifest.");
84
+ return EVENTS.warning(warning);
85
+ } else if (
86
+ offsetedPosition > maximumPositionCalculator.getCurrentMaximumPosition()
87
+ ) {
88
+ const warning = new MediaError("MEDIA_TIME_AFTER_MANIFEST",
89
+ "The current position is after the latest " +
90
+ "time announced in the Manifest.");
91
+ return EVENTS.warning(warning);
92
+ }
93
+ return null;
94
+ }, null));
95
+
96
+ /**
97
+ * Contains the content duration according to the last audio and video
98
+ * Adaptation chosen for the last Period.
99
+ * `undefined` if unknown yet.
100
+ */
101
+ const contentDuration = createSharedReference<number | undefined>(undefined);
102
+
103
+ const updateDurationOnManifestUpdate$ = fromEvent(manifest, "manifestUpdate").pipe(
104
+ startWith(null),
105
+ tap(() => {
106
+ if (!manifest.isDynamic) {
107
+ const maxPos = maximumPositionCalculator.getCurrentMaximumPosition();
108
+ contentDuration.setValue(maxPos);
109
+ } else {
110
+ // TODO handle finished dynamic contents?
111
+ contentDuration.setValue(undefined);
112
+ }
113
+ }),
114
+ ignoreElements()
115
+ );
116
+
117
+ const updateDurationAndTimeBoundsOnTrackChange$ = streams.pipe(
118
+ tap((message) => { // Update Manifest's bounds and duration if necessary
119
+ if (message.type === "adaptationChange") {
120
+ const lastPeriod = manifest.periods[manifest.periods.length - 1];
121
+ if (message.value.period.id === lastPeriod?.id) {
122
+ if (message.value.type === "audio") {
123
+ maximumPositionCalculator
124
+ .updateLastAudioAdaptation(message.value.adaptation);
125
+ if (!manifest.isDynamic) {
126
+ contentDuration.setValue(
127
+ maximumPositionCalculator.getCurrentMaximumPosition()
128
+ );
129
+ }
130
+ } else if (message.value.type === "video") {
131
+ maximumPositionCalculator
132
+ .updateLastVideoAdaptation(message.value.adaptation);
133
+ if (!manifest.isDynamic) {
134
+ contentDuration.setValue(
135
+ maximumPositionCalculator.getCurrentMaximumPosition()
136
+ );
137
+ }
138
+ }
139
+ }
140
+ }
141
+ }),
142
+ ignoreElements()
143
+ );
144
+
145
+ return observableMerge(
146
+ updateDurationOnManifestUpdate$,
147
+ updateDurationAndTimeBoundsOnTrackChange$,
148
+ outOfManifest$,
149
+ contentDuration.asObservable().pipe(
150
+ skipWhile((val) => val === undefined),
151
+ distinctUntilChanged(),
152
+ map(value => ({ type: "contentDurationUpdate" as const, value }))
153
+ ));
154
+ }
155
+
156
+ /**
157
+ * Calculate the last position from the last chosen audio and video Adaptations
158
+ * for the last Period (or a default one, if no Adaptations has been chosen).
159
+ * @class MaximumPositionCalculator
160
+ */
161
+ class MaximumPositionCalculator {
162
+ private _manifest : Manifest;
163
+
164
+ // TODO replicate for the minimum position ?
165
+ private _lastAudioAdaptation : Adaptation | undefined | null;
166
+ private _lastVideoAdaptation : Adaptation | undefined | null;
167
+
168
+ /**
169
+ * @param {Object} manifest
170
+ */
171
+ constructor(manifest : Manifest) {
172
+ this._manifest = manifest;
173
+ this._lastAudioAdaptation = undefined;
174
+ this._lastVideoAdaptation = undefined;
175
+ }
176
+
177
+ /**
178
+ * Update the last known audio Adaptation for the last Period.
179
+ * If no Adaptation has been set, it should be set to `null`.
180
+ *
181
+ * Allows to calculate the maximum position more precizely in
182
+ * `getCurrentMaximumPosition`.
183
+ * @param {Object|null} adaptation
184
+ */
185
+ public updateLastAudioAdaptation(adaptation : Adaptation | null) : void {
186
+ this._lastAudioAdaptation = adaptation;
187
+ }
188
+
189
+ /**
190
+ * Update the last known video Adaptation for the last Period.
191
+ * If no Adaptation has been set, it should be set to `null`.
192
+ *
193
+ * Allows to calculate the maximum position more precizely in
194
+ * `getCurrentMaximumPosition`.
195
+ * @param {Object|null} adaptation
196
+ */
197
+ public updateLastVideoAdaptation(adaptation : Adaptation | null) : void {
198
+ this._lastVideoAdaptation = adaptation;
199
+ }
200
+
201
+ /**
202
+ * Returns an estimate of the maximum position reachable under the current
203
+ * circumstances.
204
+ * @returns {number}
205
+ */
206
+ public getCurrentMaximumPosition() : number {
207
+ if (this._manifest.isDynamic) {
208
+ return this._manifest.getLivePosition() ??
209
+ this._manifest.getMaximumSafePosition();
210
+ }
211
+ if (this._lastVideoAdaptation === undefined ||
212
+ this._lastAudioAdaptation === undefined)
213
+ {
214
+ return this._manifest.getMaximumSafePosition();
215
+ } else if (this._lastAudioAdaptation === null) {
216
+ if (this._lastVideoAdaptation === null) {
217
+ return this._manifest.getMaximumSafePosition();
218
+ } else {
219
+ const lastVideoPosition =
220
+ getLastPositionFromAdaptation(this._lastVideoAdaptation);
221
+ if (typeof lastVideoPosition !== "number") {
222
+ return this._manifest.getMaximumSafePosition();
223
+ }
224
+ return lastVideoPosition;
225
+ }
226
+ } else if (this._lastVideoAdaptation === null) {
227
+ const lastAudioPosition =
228
+ getLastPositionFromAdaptation(this._lastAudioAdaptation);
229
+ if (typeof lastAudioPosition !== "number") {
230
+ return this._manifest.getMaximumSafePosition();
231
+ }
232
+ return lastAudioPosition;
233
+ } else {
234
+ const lastAudioPosition = getLastPositionFromAdaptation(
235
+ this._lastAudioAdaptation
236
+ );
237
+ const lastVideoPosition = getLastPositionFromAdaptation(
238
+ this._lastVideoAdaptation
239
+ );
240
+ if (typeof lastAudioPosition !== "number" ||
241
+ typeof lastVideoPosition !== "number")
242
+ {
243
+ return this._manifest.getMaximumSafePosition();
244
+ } else {
245
+ return Math.min(lastAudioPosition, lastVideoPosition);
246
+ }
247
+ }
248
+ }
249
+ }
250
+
251
+ /**
252
+ * Returns "last time of reference" from the adaptation given.
253
+ * `undefined` if a time could not be found.
254
+ * Null if the Adaptation has no segments (it could be that it didn't started or
255
+ * that it already finished for example).
256
+ *
257
+ * We consider the earliest last time from every representations in the given
258
+ * adaptation.
259
+ * @param {Object} adaptation
260
+ * @returns {Number|undefined|null}
261
+ */
262
+ function getLastPositionFromAdaptation(
263
+ adaptation: Adaptation
264
+ ) : number | undefined | null {
265
+ const { representations } = adaptation;
266
+ let min : null | number = null;
267
+
268
+ /**
269
+ * Some Manifest parsers use the exact same `IRepresentationIndex` reference
270
+ * for each Representation of a given Adaptation, because in the actual source
271
+ * Manifest file, indexing data is often defined at Adaptation-level.
272
+ * This variable allows to optimize the logic here when this is the case.
273
+ */
274
+ let lastIndex : IRepresentationIndex | undefined;
275
+ for (let i = 0; i < representations.length; i++) {
276
+ if (representations[i].index !== lastIndex) {
277
+ lastIndex = representations[i].index;
278
+ const lastPosition = representations[i].index.getLastPosition();
279
+ if (lastPosition === undefined) { // we cannot tell
280
+ return undefined;
281
+ }
282
+ if (lastPosition !== null) {
283
+ min = min == null ? lastPosition :
284
+ Math.min(min, lastPosition);
285
+ }
286
+ }
287
+ }
288
+ if (min === null) { // It means that all positions were null === no segments (yet?)
289
+ return null;
290
+ }
291
+ return min;
292
+ }
293
+
294
+ /**
295
+ * Emitted when the duration of the full content (== the last playable position)
296
+ * has changed.
297
+ */
298
+ export interface IContentDurationUpdateEvent {
299
+ type: "contentDurationUpdate";
300
+ /** The new theoretical duration, `undefined` if unknown, */
301
+ value : number | undefined;
302
+ }
303
+
304
+ export interface IContentTimeObserverPlaybackObservation {
305
+ /** The position we are in the video in seconds at the time of the observation. */
306
+ position : number;
307
+ /**
308
+ * Offset, in seconds to add to `position` to obtain the starting position at
309
+ * which we actually want to download segments for.
310
+ */
311
+ wantedTimeOffset : number;
312
+ }
@@ -62,26 +62,37 @@ export default function createStreamPlaybackObserver(
62
62
  ) : Observable<IStreamOrchestratorPlaybackObservation> {
63
63
  return observableCombineLatest([observation$, speed.asObservable()]).pipe(
64
64
  map(([observation, lastSpeed]) => {
65
+ let wantedTimeOffset = 0;
66
+ if (!initialSeekPerformed.getValue()) {
67
+ wantedTimeOffset = startTime - observation.position;
68
+ } else if (!manifest.isDynamic || manifest.isLastPeriodKnown) {
69
+ const lastPeriod = manifest.periods[manifest.periods.length - 1];
70
+ if (lastPeriod !== undefined &&
71
+ lastPeriod.end !== undefined &&
72
+ observation.position > lastPeriod.end)
73
+ {
74
+ wantedTimeOffset = lastPeriod.end -
75
+ observation.position -
76
+ 1;
77
+ }
78
+ }
79
+
65
80
  return {
66
- liveGap: manifest.isLive ?
67
- manifest.getMaximumPosition() - observation.position :
68
- undefined,
81
+ // TODO more exact according to the current Adaptation chosen?
82
+ maximumPosition: manifest.getMaximumSafePosition(),
69
83
  position: observation.position,
70
84
  duration: observation.duration,
71
85
  isPaused: initialPlayPerformed.getValue() ? observation.paused :
72
86
  !autoPlay,
73
87
  readyState: observation.readyState,
74
88
  speed: lastSpeed,
75
-
76
89
  // wantedTimeOffset is an offset to add to the timing's current time to have
77
90
  // the "real" wanted position.
78
91
  // For now, this is seen when the media element has not yet seeked to its
79
92
  // initial position, the currentTime will most probably be 0 where the
80
93
  // effective starting position will be _startTime_.
81
94
  // Thus we initially set a wantedTimeOffset equal to startTime.
82
- wantedTimeOffset: initialSeekPerformed.getValue() ?
83
- 0 :
84
- startTime - observation.position,
95
+ wantedTimeOffset,
85
96
  };
86
97
  }));
87
98
  });