rx-player 3.28.1-dev.2022083000 → 3.29.0-dev.2022090500

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 (139) hide show
  1. package/CHANGELOG.md +11 -1
  2. package/VERSION +1 -1
  3. package/dist/_esm5.processed/compat/event_listeners.d.ts +18 -2
  4. package/dist/_esm5.processed/compat/event_listeners.js +64 -2
  5. package/dist/_esm5.processed/compat/on_height_width_change.d.ts +5 -4
  6. package/dist/_esm5.processed/compat/on_height_width_change.js +43 -34
  7. package/dist/_esm5.processed/core/api/playback_observer.d.ts +12 -2
  8. package/dist/_esm5.processed/core/api/playback_observer.js +27 -12
  9. package/dist/_esm5.processed/core/api/public_api.js +14 -14
  10. package/dist/_esm5.processed/core/fetchers/manifest/manifest_fetcher.d.ts +7 -0
  11. package/dist/_esm5.processed/core/fetchers/manifest/manifest_fetcher.js +10 -2
  12. package/dist/_esm5.processed/core/fetchers/segment/segment_fetcher.d.ts +8 -1
  13. package/dist/_esm5.processed/core/fetchers/segment/segment_fetcher.js +10 -4
  14. package/dist/_esm5.processed/core/fetchers/segment/segment_fetcher_creator.d.ts +7 -0
  15. package/dist/_esm5.processed/core/init/initialize_directfile.js +1 -1
  16. package/dist/_esm5.processed/core/init/load_on_media_source.js +1 -1
  17. package/dist/_esm5.processed/core/init/stall_avoider.d.ts +4 -2
  18. package/dist/_esm5.processed/core/init/stall_avoider.js +32 -26
  19. package/dist/_esm5.processed/core/segment_buffers/garbage_collector.d.ts +12 -6
  20. package/dist/_esm5.processed/core/segment_buffers/garbage_collector.js +142 -78
  21. package/dist/_esm5.processed/core/segment_buffers/implementations/audio_video/audio_video_segment_buffer.d.ts +18 -16
  22. package/dist/_esm5.processed/core/segment_buffers/implementations/audio_video/audio_video_segment_buffer.js +53 -41
  23. package/dist/_esm5.processed/core/segment_buffers/implementations/image/image_segment_buffer.d.ts +6 -5
  24. package/dist/_esm5.processed/core/segment_buffers/implementations/image/image_segment_buffer.js +37 -39
  25. package/dist/_esm5.processed/core/segment_buffers/implementations/text/html/html_text_segment_buffer.d.ts +23 -22
  26. package/dist/_esm5.processed/core/segment_buffers/implementations/text/html/html_text_segment_buffer.js +84 -72
  27. package/dist/_esm5.processed/core/segment_buffers/implementations/text/native/native_text_segment_buffer.d.ts +6 -12
  28. package/dist/_esm5.processed/core/segment_buffers/implementations/text/native/native_text_segment_buffer.js +33 -43
  29. package/dist/_esm5.processed/core/segment_buffers/implementations/types.d.ts +12 -9
  30. package/dist/_esm5.processed/core/segment_buffers/segment_buffers_store.d.ts +7 -6
  31. package/dist/_esm5.processed/core/segment_buffers/segment_buffers_store.js +17 -10
  32. package/dist/_esm5.processed/core/stream/orchestrator/stream_orchestrator.js +20 -9
  33. package/dist/_esm5.processed/core/stream/period/period_stream.js +25 -14
  34. package/dist/_esm5.processed/core/stream/representation/append_segment_to_buffer.d.ts +4 -7
  35. package/dist/_esm5.processed/core/stream/representation/append_segment_to_buffer.js +80 -23
  36. package/dist/_esm5.processed/core/stream/representation/force_garbage_collection.d.ts +5 -4
  37. package/dist/_esm5.processed/core/stream/representation/force_garbage_collection.js +78 -26
  38. package/dist/_esm5.processed/core/stream/representation/get_buffer_status.js +7 -3
  39. package/dist/_esm5.processed/core/stream/representation/push_init_segment.js +7 -1
  40. package/dist/_esm5.processed/core/stream/representation/push_media_segment.js +7 -1
  41. package/dist/_esm5.processed/core/stream/representation/representation_stream.js +15 -8
  42. package/dist/_esm5.processed/default_config.js +1 -1
  43. package/dist/_esm5.processed/errors/custom_loader_error.d.ts +3 -2
  44. package/dist/_esm5.processed/errors/custom_loader_error.js +3 -2
  45. package/dist/_esm5.processed/experimental/tools/VideoThumbnailLoader/get_initialized_source_buffer.js +5 -2
  46. package/dist/_esm5.processed/experimental/tools/VideoThumbnailLoader/push_data.js +5 -2
  47. package/dist/_esm5.processed/experimental/tools/VideoThumbnailLoader/remove_buffer_around_time.js +9 -2
  48. package/dist/_esm5.processed/experimental/tools/VideoThumbnailLoader/thumbnail_loader.js +3 -1
  49. package/dist/_esm5.processed/experimental/tools/createMetaplaylist/get_duration_from_manifest.js +4 -3
  50. package/dist/_esm5.processed/public_types.d.ts +22 -3
  51. package/dist/_esm5.processed/transports/dash/add_segment_integrity_checks_to_loader.js +2 -2
  52. package/dist/_esm5.processed/transports/dash/image_pipelines.d.ts +3 -2
  53. package/dist/_esm5.processed/transports/dash/image_pipelines.js +3 -1
  54. package/dist/_esm5.processed/transports/dash/init_segment_loader.d.ts +3 -2
  55. package/dist/_esm5.processed/transports/dash/init_segment_loader.js +12 -6
  56. package/dist/_esm5.processed/transports/dash/low_latency_segment_loader.d.ts +3 -2
  57. package/dist/_esm5.processed/transports/dash/low_latency_segment_loader.js +3 -2
  58. package/dist/_esm5.processed/transports/dash/manifest_parser.js +6 -2
  59. package/dist/_esm5.processed/transports/dash/segment_loader.d.ts +3 -2
  60. package/dist/_esm5.processed/transports/dash/segment_loader.js +12 -9
  61. package/dist/_esm5.processed/transports/dash/text_loader.js +6 -3
  62. package/dist/_esm5.processed/transports/local/pipelines.d.ts +2 -2
  63. package/dist/_esm5.processed/transports/local/pipelines.js +6 -6
  64. package/dist/_esm5.processed/transports/local/segment_loader.d.ts +3 -2
  65. package/dist/_esm5.processed/transports/local/segment_loader.js +4 -3
  66. package/dist/_esm5.processed/transports/metaplaylist/manifest_loader.d.ts +2 -2
  67. package/dist/_esm5.processed/transports/metaplaylist/manifest_loader.js +5 -2
  68. package/dist/_esm5.processed/transports/metaplaylist/pipelines.js +15 -10
  69. package/dist/_esm5.processed/transports/smooth/pipelines.d.ts +1 -1
  70. package/dist/_esm5.processed/transports/smooth/pipelines.js +18 -14
  71. package/dist/_esm5.processed/transports/smooth/segment_loader.d.ts +2 -2
  72. package/dist/_esm5.processed/transports/smooth/segment_loader.js +8 -6
  73. package/dist/_esm5.processed/transports/types.d.ts +25 -2
  74. package/dist/_esm5.processed/transports/utils/call_custom_manifest_loader.d.ts +2 -2
  75. package/dist/_esm5.processed/transports/utils/call_custom_manifest_loader.js +3 -3
  76. package/dist/_esm5.processed/transports/utils/generate_manifest_loader.d.ts +2 -2
  77. package/dist/_esm5.processed/transports/utils/generate_manifest_loader.js +9 -6
  78. package/dist/_esm5.processed/utils/request/fetch.js +7 -8
  79. package/dist/_esm5.processed/utils/request/xhr.d.ts +1 -1
  80. package/dist/_esm5.processed/utils/request/xhr.js +28 -14
  81. package/dist/_esm5.processed/utils/task_canceller.d.ts +1 -2
  82. package/dist/_esm5.processed/utils/task_canceller.js +1 -2
  83. package/dist/mpd-parser.wasm +0 -0
  84. package/dist/rx-player.js +1116 -695
  85. package/dist/rx-player.min.js +1 -1
  86. package/package.json +7 -7
  87. package/sonar-project.properties +1 -1
  88. package/src/compat/event_listeners.ts +86 -1
  89. package/src/compat/on_height_width_change.ts +48 -49
  90. package/src/core/api/playback_observer.ts +34 -14
  91. package/src/core/api/public_api.ts +23 -18
  92. package/src/core/fetchers/manifest/manifest_fetcher.ts +20 -2
  93. package/src/core/fetchers/segment/segment_fetcher.ts +23 -3
  94. package/src/core/fetchers/segment/segment_fetcher_creator.ts +7 -0
  95. package/src/core/init/initialize_directfile.ts +1 -1
  96. package/src/core/init/load_on_media_source.ts +1 -0
  97. package/src/core/init/stall_avoider.ts +40 -26
  98. package/src/core/segment_buffers/garbage_collector.ts +55 -47
  99. package/src/core/segment_buffers/implementations/audio_video/audio_video_segment_buffer.ts +92 -70
  100. package/src/core/segment_buffers/implementations/image/image_segment_buffer.ts +37 -42
  101. package/src/core/segment_buffers/implementations/text/html/html_text_segment_buffer.ts +103 -105
  102. package/src/core/segment_buffers/implementations/text/native/native_text_segment_buffer.ts +35 -46
  103. package/src/core/segment_buffers/implementations/types.ts +22 -9
  104. package/src/core/segment_buffers/segment_buffers_store.ts +23 -14
  105. package/src/core/stream/orchestrator/stream_orchestrator.ts +31 -12
  106. package/src/core/stream/period/period_stream.ts +31 -18
  107. package/src/core/stream/representation/append_segment_to_buffer.ts +27 -42
  108. package/src/core/stream/representation/force_garbage_collection.ts +28 -32
  109. package/src/core/stream/representation/get_buffer_status.ts +7 -3
  110. package/src/core/stream/representation/push_init_segment.ts +12 -6
  111. package/src/core/stream/representation/push_media_segment.ts +12 -6
  112. package/src/core/stream/representation/representation_stream.ts +11 -5
  113. package/src/default_config.ts +17 -17
  114. package/src/errors/custom_loader_error.ts +3 -2
  115. package/src/experimental/tools/VideoThumbnailLoader/get_initialized_source_buffer.ts +7 -2
  116. package/src/experimental/tools/VideoThumbnailLoader/push_data.ts +6 -2
  117. package/src/experimental/tools/VideoThumbnailLoader/remove_buffer_around_time.ts +10 -2
  118. package/src/experimental/tools/VideoThumbnailLoader/thumbnail_loader.ts +3 -1
  119. package/src/experimental/tools/createMetaplaylist/get_duration_from_manifest.ts +4 -3
  120. package/src/public_types.ts +28 -4
  121. package/src/transports/dash/add_segment_integrity_checks_to_loader.ts +2 -2
  122. package/src/transports/dash/image_pipelines.ts +4 -0
  123. package/src/transports/dash/init_segment_loader.ts +8 -0
  124. package/src/transports/dash/low_latency_segment_loader.ts +4 -0
  125. package/src/transports/dash/manifest_parser.ts +4 -0
  126. package/src/transports/dash/segment_loader.ts +21 -5
  127. package/src/transports/dash/text_loader.ts +7 -2
  128. package/src/transports/local/pipelines.ts +7 -5
  129. package/src/transports/local/segment_loader.ts +4 -2
  130. package/src/transports/metaplaylist/manifest_loader.ts +9 -2
  131. package/src/transports/metaplaylist/pipelines.ts +16 -6
  132. package/src/transports/smooth/pipelines.ts +17 -9
  133. package/src/transports/smooth/segment_loader.ts +8 -0
  134. package/src/transports/types.ts +27 -0
  135. package/src/transports/utils/call_custom_manifest_loader.ts +8 -2
  136. package/src/transports/utils/generate_manifest_loader.ts +18 -5
  137. package/src/utils/request/fetch.ts +7 -8
  138. package/src/utils/request/xhr.ts +31 -15
  139. package/src/utils/task_canceller.ts +1 -2
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "rx-player",
3
3
  "author": "Canal+",
4
- "version": "3.28.1-dev.2022083000",
4
+ "version": "3.29.0-dev.2022090500",
5
5
  "description": "Canal+ HTML5 Video Player",
6
6
  "main": "./dist/rx-player.js",
7
7
  "keywords": [
@@ -88,17 +88,17 @@
88
88
  "@types/chai": "4.3.3",
89
89
  "@types/jest": "28.1.8",
90
90
  "@types/mocha": "9.1.1",
91
- "@types/node": "18.7.13",
91
+ "@types/node": "18.7.14",
92
92
  "@types/sinon": "10.0.13",
93
- "@typescript-eslint/eslint-plugin": "5.35.1",
94
- "@typescript-eslint/eslint-plugin-tslint": "5.35.1",
95
- "@typescript-eslint/parser": "5.35.1",
93
+ "@typescript-eslint/eslint-plugin": "5.36.1",
94
+ "@typescript-eslint/eslint-plugin-tslint": "5.36.1",
95
+ "@typescript-eslint/parser": "5.36.1",
96
96
  "arraybuffer-loader": "1.0.8",
97
97
  "babel-loader": "8.2.5",
98
98
  "chai": "4.3.6",
99
99
  "cheerio": "1.0.0-rc.12",
100
100
  "core-js": "3.25.0",
101
- "esbuild": "0.15.5",
101
+ "esbuild": "0.15.6",
102
102
  "eslint": "8.23.0",
103
103
  "eslint-plugin-import": "2.26.0",
104
104
  "eslint-plugin-jsdoc": "39.3.6",
@@ -124,7 +124,7 @@
124
124
  "rimraf": "3.0.2",
125
125
  "semver": "7.3.7",
126
126
  "sinon": "14.0.0",
127
- "terser-webpack-plugin": "5.3.5",
127
+ "terser-webpack-plugin": "5.3.6",
128
128
  "ts-jest": "28.0.8",
129
129
  "ts-loader": "9.3.1",
130
130
  "tslint": "6.1.3",
@@ -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.28.1-dev.2022083000
4
+ sonar.projectVersion=3.29.0-dev.2022090500
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
@@ -107,6 +107,69 @@ export type IEventTargetLike = HTMLElement |
107
107
  IEventEmitterLike |
108
108
  IEventEmitter<unknown>;
109
109
 
110
+ /**
111
+ * Returns a function allowing to add event listeners for particular event(s)
112
+ * optionally automatically adding browser prefixes if needed.
113
+ * @param {Array.<string>} eventNames - The event(s) to listen to. If multiple
114
+ * events are set, the event listener will be triggered when any of them emits.
115
+ * @returns {Function} - Returns function allowing to easily add a callback to
116
+ * be triggered when that event is emitted on a given event target.
117
+ */
118
+ function createCompatibleEventListener(
119
+ eventNames : string[]
120
+ ) :
121
+ (
122
+ element : IEventTargetLike,
123
+ listener : (event? : unknown) => void,
124
+ cancelSignal: CancellationSignal
125
+ ) => void
126
+ {
127
+ let mem : string|undefined;
128
+ const prefixedEvents = eventPrefixed(eventNames);
129
+
130
+ return (
131
+ element : IEventTargetLike,
132
+ listener: (event? : unknown) => void,
133
+ cancelSignal: CancellationSignal
134
+ ) => {
135
+ if (cancelSignal.isCancelled) {
136
+ return;
137
+ }
138
+
139
+ // if the element is a HTMLElement we can detect
140
+ // the supported event, and memoize it in `mem`
141
+ if (element instanceof HTMLElement) {
142
+ if (typeof mem === "undefined") {
143
+ mem = findSupportedEvent(element, prefixedEvents);
144
+ }
145
+
146
+ if (isNonEmptyString(mem)) {
147
+ element.addEventListener(mem, listener);
148
+ cancelSignal.register(() => {
149
+ if (mem !== undefined) {
150
+ element.removeEventListener(mem, listener);
151
+ }
152
+ });
153
+ } else {
154
+ if (__ENVIRONMENT__.CURRENT_ENV === __ENVIRONMENT__.DEV as number) {
155
+ log.warn(`compat: element ${element.tagName}` +
156
+ " does not support any of these events: " +
157
+ prefixedEvents.join(", "));
158
+ }
159
+ return ;
160
+ }
161
+ }
162
+
163
+ prefixedEvents.forEach(eventName => {
164
+ (element as IEventEmitterLike).addEventListener(eventName, listener);
165
+ cancelSignal.register(() => {
166
+ (element as IEventEmitterLike).removeEventListener(eventName, listener);
167
+ });
168
+ });
169
+ };
170
+
171
+ }
172
+
110
173
  /**
111
174
  * @param {Array.<string>} eventNames
112
175
  * @param {Array.<string>|undefined} prefixes
@@ -247,7 +310,8 @@ export interface IPictureInPictureEvent {
247
310
 
248
311
  /**
249
312
  * Emit when video enters and leaves Picture-In-Picture mode.
250
- * @param {HTMLMediaElement} mediaElement
313
+ * @param {HTMLMediaElement} elt
314
+ * @param {Object} stopListening
251
315
  * @returns {Observable}
252
316
  */
253
317
  function getPictureOnPictureStateRef(
@@ -505,6 +569,24 @@ const onKeyError$ = compatibleListener(["keyerror", "error"]);
505
569
  */
506
570
  const onKeyStatusesChange$ = compatibleListener(["keystatuseschange"]);
507
571
 
572
+ /**
573
+ * @param {HTMLMediaElement} mediaElement
574
+ * @returns {Observable}
575
+ */
576
+ const onSeeking = createCompatibleEventListener(["seeking"]);
577
+
578
+ /**
579
+ * @param {HTMLMediaElement} mediaElement
580
+ * @returns {Observable}
581
+ */
582
+ const onSeeked = createCompatibleEventListener(["seeked"]);
583
+
584
+ /**
585
+ * @param {HTMLMediaElement} mediaElement
586
+ * @returns {Observable}
587
+ */
588
+ const onEnded = createCompatibleEventListener(["ended"]);
589
+
508
590
  /**
509
591
  * Utilitary function allowing to add an event listener and remove it
510
592
  * automatically once the given `CancellationSignal` emits.
@@ -534,6 +616,7 @@ export {
534
616
  getVideoVisibilityRef,
535
617
  getVideoWidthRef,
536
618
  onEncrypted$,
619
+ onEnded,
537
620
  onEnded$,
538
621
  onFullscreenChange$,
539
622
  onKeyAdded$,
@@ -542,7 +625,9 @@ export {
542
625
  onKeyStatusesChange$,
543
626
  onLoadedMetadata$,
544
627
  onRemoveSourceBuffers$,
628
+ onSeeked,
545
629
  onSeeked$,
630
+ onSeeking,
546
631
  onSeeking$,
547
632
  onSourceClose$,
548
633
  onSourceEnded$,
@@ -14,16 +14,11 @@
14
14
  * limitations under the License.
15
15
  */
16
16
 
17
- import {
18
- defer as observableDefer,
19
- distinctUntilChanged,
20
- interval as observableInterval,
21
- map,
22
- Observable,
23
- Observer,
24
- startWith,
25
- } from "rxjs";
26
17
  import log from "../log";
18
+ import createSharedReference, {
19
+ IReadOnlySharedReference,
20
+ } from "../utils/reference";
21
+ import { CancellationSignal } from "../utils/task_canceller";
27
22
  import isNode from "./is_node";
28
23
 
29
24
  export interface IResolution { width : number;
@@ -63,8 +58,8 @@ const _ResizeObserver : IResizeObserverConstructor |
63
58
  /* eslint-enable @typescript-eslint/no-unsafe-assignment */
64
59
 
65
60
  /**
66
- * Emit the current height and width of the given `element` on subscribtion
67
- * and each time it changes.
61
+ * Emit the current height and width of the given `element` each time it
62
+ * changes.
68
63
  *
69
64
  * On some browsers, we might not be able to rely on a native API to know when
70
65
  * it changes, the `interval` argument allow us to provide us an inverval in
@@ -75,46 +70,50 @@ const _ResizeObserver : IResizeObserverConstructor |
75
70
  */
76
71
  export default function onHeightWidthChange(
77
72
  element : HTMLElement,
78
- interval : number
79
- ) : Observable<IResolution> {
80
- return observableDefer(() : Observable<IResolution> => {
81
- if (_ResizeObserver !== undefined) {
82
- let lastHeight : number = -1;
83
- let lastWidth : number = -1;
84
-
85
- return new Observable((obs : Observer<IResolution>) => {
86
- const resizeObserver = new _ResizeObserver(entries => {
87
- if (entries.length === 0) {
88
- log.error("Compat: Resized but no observed element.");
89
- return;
90
- }
73
+ interval : number,
74
+ cancellationSignal : CancellationSignal
75
+ ) : IReadOnlySharedReference<IResolution> {
76
+ const { height: initHeight, width: initWidth } = element.getBoundingClientRect();
77
+ const ref = createSharedReference<IResolution>({
78
+ height: initHeight,
79
+ width: initWidth,
80
+ });
81
+ let lastHeight : number = initHeight;
82
+ let lastWidth : number = initWidth;
91
83
 
92
- const entry = entries[0];
93
- const { height, width } = entry.contentRect;
84
+ if (_ResizeObserver !== undefined) {
85
+ const resizeObserver = new _ResizeObserver(entries => {
86
+ if (entries.length === 0) {
87
+ log.error("Compat: Resized but no observed element.");
88
+ return;
89
+ }
94
90
 
95
- if (height !== lastHeight || width !== lastWidth) {
96
- lastHeight = height;
97
- lastWidth = width;
98
- obs.next({ height, width });
99
- }
100
- });
91
+ const entry = entries[0];
92
+ const { height, width } = entry.contentRect;
101
93
 
102
- resizeObserver.observe(element);
103
- return () => {
104
- resizeObserver.disconnect();
105
- };
106
- });
107
- }
94
+ if (height !== lastHeight || width !== lastWidth) {
95
+ lastHeight = height;
96
+ lastWidth = width;
97
+ ref.setValue({ height, width });
98
+ }
99
+ });
108
100
 
109
- return observableInterval(interval).pipe(
110
- startWith(null),
111
- map(() : IResolution => {
112
- const { height, width } = element.getBoundingClientRect();
113
- return { height, width };
114
- }),
115
- distinctUntilChanged((o, n) => {
116
- return o.height === n.height && o.width === n.width;
117
- })
118
- );
119
- });
101
+ resizeObserver.observe(element);
102
+ cancellationSignal.register(() => {
103
+ resizeObserver.disconnect();
104
+ });
105
+ } else {
106
+ const intervalId = setInterval(() => {
107
+ const { height, width } = element.getBoundingClientRect();
108
+ if (height !== lastHeight || width !== lastWidth) {
109
+ lastHeight = height;
110
+ lastWidth = width;
111
+ ref.setValue({ height, width });
112
+ }
113
+ }, interval);
114
+ cancellationSignal.register(() => {
115
+ clearInterval(intervalId);
116
+ });
117
+ }
118
+ return ref;
120
119
  }
@@ -82,7 +82,7 @@ export default class PlaybackObserver {
82
82
  * This allows us to correctly characterize seeking events: if the counter is
83
83
  * superior to `0`, it is probably due to an internal "seek".
84
84
  */
85
- private _internalSeekingEventsIncomingCounter : number;
85
+ private _internalSeeksIncoming : number[];
86
86
 
87
87
  /**
88
88
  * Stores the last playback observation produced by the `PlaybackObserver`.:
@@ -106,7 +106,7 @@ export default class PlaybackObserver {
106
106
  * @param {Object} options
107
107
  */
108
108
  constructor(mediaElement : HTMLMediaElement, options : IPlaybackObserverOptions) {
109
- this._internalSeekingEventsIncomingCounter = 0;
109
+ this._internalSeeksIncoming = [];
110
110
  this._mediaElement = mediaElement;
111
111
  this._withMediaSource = options.withMediaSource;
112
112
  this._lowLatencyMode = options.lowLatencyMode;
@@ -137,6 +137,14 @@ export default class PlaybackObserver {
137
137
  return this._mediaElement.currentTime;
138
138
  }
139
139
 
140
+ /**
141
+ * Returns the current playback rate advertised by the `HTMLMediaElement`.
142
+ * @returns {number}
143
+ */
144
+ public getPlaybackRate() : number {
145
+ return this._mediaElement.playbackRate;
146
+ }
147
+
140
148
  /**
141
149
  * Returns the current `paused` status advertised by the `HTMLMediaElement`.
142
150
  *
@@ -159,7 +167,7 @@ export default class PlaybackObserver {
159
167
  * @param {number} time
160
168
  */
161
169
  public setCurrentTime(time: number) : void {
162
- this._internalSeekingEventsIncomingCounter += 1;
170
+ this._internalSeeksIncoming.push(time);
163
171
  this._mediaElement.currentTime = time;
164
172
  }
165
173
 
@@ -250,17 +258,21 @@ export default class PlaybackObserver {
250
258
  event : IPlaybackObserverEventType
251
259
  ) : IPlaybackObservation => {
252
260
  let tmpEvt: IPlaybackObserverEventType = event;
253
- if (tmpEvt === "seeking" && this._internalSeekingEventsIncomingCounter > 0) {
261
+ let startedInternalSeekTime : number | undefined;
262
+ if (tmpEvt === "seeking" && this._internalSeeksIncoming.length > 0) {
254
263
  tmpEvt = "internal-seeking";
255
- this._internalSeekingEventsIncomingCounter -= 1;
264
+ startedInternalSeekTime = this._internalSeeksIncoming.shift();
256
265
  }
257
266
  const _lastObservation = lastObservation ?? this._generateInitialObservation();
258
267
  const mediaTimings = getMediaInfos(this._mediaElement, tmpEvt);
259
- const internalSeeking = mediaTimings.seeking &&
260
- // We've just received the event for internally seeking
261
- (tmpEvt === "internal-seeking" ||
262
- // or We're still waiting on the previous internal-seek
263
- (_lastObservation.internalSeeking && tmpEvt !== "seeking"));
268
+ let pendingInternalSeek : number | null = null;
269
+ if (mediaTimings.seeking) {
270
+ if (typeof startedInternalSeekTime === "number") {
271
+ pendingInternalSeek = startedInternalSeekTime;
272
+ } else if (_lastObservation.pendingInternalSeek !== null && event !== "seeking") {
273
+ pendingInternalSeek = _lastObservation.pendingInternalSeek;
274
+ }
275
+ }
264
276
  const rebufferingStatus = getRebufferingStatus(
265
277
  _lastObservation,
266
278
  mediaTimings,
@@ -272,14 +284,14 @@ export default class PlaybackObserver {
272
284
  {},
273
285
  { rebuffering: rebufferingStatus,
274
286
  freezing: freezingStatus,
275
- internalSeeking },
287
+ pendingInternalSeek },
276
288
  mediaTimings);
277
289
  if (log.hasLevel("DEBUG")) {
278
290
  log.debug("API: current media element state tick",
279
291
  "event", timings.event,
280
292
  "position", timings.position,
281
293
  "seeking", timings.seeking,
282
- "internalSeeking", timings.internalSeeking,
294
+ "internalSeek", timings.pendingInternalSeek,
283
295
  "rebuffering", timings.rebuffering !== null,
284
296
  "freezing", timings.freezing !== null,
285
297
  "ended", timings.ended,
@@ -341,7 +353,7 @@ export default class PlaybackObserver {
341
353
  return objectAssign(getMediaInfos(this._mediaElement, "init"),
342
354
  { rebuffering: null,
343
355
  freezing: null,
344
- internalSeeking: false });
356
+ pendingInternalSeek: null });
345
357
  }
346
358
  }
347
359
 
@@ -457,7 +469,7 @@ export interface IPlaybackObservation extends IMediaInfos {
457
469
  * If `true`, an "internal seek" (a seeking operation triggered by the
458
470
  * RxPlayer code) is currently pending.
459
471
  */
460
- internalSeeking : boolean;
472
+ pendingInternalSeek : number | null;
461
473
  }
462
474
 
463
475
  /**
@@ -475,6 +487,11 @@ export interface IPlaybackObservation extends IMediaInfos {
475
487
  export interface IReadOnlyPlaybackObserver<TObservationType> {
476
488
  /** Get the current playing position, in seconds. */
477
489
  getCurrentTime() : number;
490
+ /**
491
+ * Returns the current playback rate advertised by the `HTMLMediaElement`.
492
+ * @returns {number}
493
+ */
494
+ getPlaybackRate() : number;
478
495
  /** Get the HTMLMediaElement's current `readyState`. */
479
496
  getReadyState() : number;
480
497
  /**
@@ -859,6 +876,9 @@ function generateReadOnlyObserver<TSource, TDest>(
859
876
  getReadyState() {
860
877
  return src.getReadyState();
861
878
  },
879
+ getPlaybackRate() : number {
880
+ return src.getPlaybackRate();
881
+ },
862
882
  getIsPaused() {
863
883
  return src.getIsPaused();
864
884
  },
@@ -432,7 +432,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
432
432
  // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1194624
433
433
  videoElement.preload = "auto";
434
434
 
435
- this.version = /* PLAYER_VERSION */"3.28.1-dev.2022083000";
435
+ this.version = /* PLAYER_VERSION */"3.29.0-dev.2022090500";
436
436
  this.log = log;
437
437
  this.state = "STOPPED";
438
438
  this.videoElement = videoElement;
@@ -741,21 +741,28 @@ class Player extends EventEmitter<IPublicAPIEvent> {
741
741
 
742
742
  const transportPipelines = transportFn(transportOptions);
743
743
 
744
- const { offlineRetry, segmentRetry, manifestRetry } = networkConfig;
744
+ const { offlineRetry,
745
+ segmentRetry,
746
+ manifestRetry,
747
+ manifestRequestTimeout,
748
+ segmentRequestTimeout } = networkConfig;
745
749
 
746
750
  /** Interface used to load and refresh the Manifest. */
747
- const manifestFetcher = new ManifestFetcher(url,
748
- transportPipelines,
749
- { lowLatencyMode,
750
- maxRetryRegular: manifestRetry,
751
- maxRetryOffline: offlineRetry });
751
+ const manifestFetcher = new ManifestFetcher(
752
+ url,
753
+ transportPipelines,
754
+ { lowLatencyMode,
755
+ maxRetryRegular: manifestRetry,
756
+ maxRetryOffline: offlineRetry,
757
+ requestTimeout: manifestRequestTimeout });
752
758
 
753
759
  /** Interface used to download segments. */
754
760
  const segmentFetcherCreator = new SegmentFetcherCreator(
755
761
  transportPipelines,
756
762
  { lowLatencyMode,
757
763
  maxRetryOffline: offlineRetry,
758
- maxRetryRegular: segmentRetry });
764
+ maxRetryRegular: segmentRetry,
765
+ requestTimeout: segmentRequestTimeout });
759
766
 
760
767
  /** Observable emitting the initial Manifest */
761
768
  let manifest$ : Observable<IManifestFetcherParsedResult |
@@ -1046,16 +1053,14 @@ class Player extends EventEmitter<IPublicAPIEvent> {
1046
1053
  // Handle state updates
1047
1054
  playerState$
1048
1055
  .pipe(takeUntil(stoppedContent$))
1049
- .subscribe(x => this._priv_setPlayerState(x));
1056
+ .subscribe(newState => {
1057
+ this._priv_setPlayerState(newState);
1050
1058
 
1051
- const endedEvent$ = observation$.pipe(filter(o => {
1052
- return o.event === "ended";
1053
- }));
1054
- (this._priv_stopAtEnd ? endedEvent$ :
1055
- EMPTY)
1056
- .pipe(takeUntil(stoppedContent$))
1057
- .subscribe(() => {
1058
- currentContentCanceller.cancel();
1059
+ // Previous call could have performed all kind of side-effects, thus,
1060
+ // we re-check the current state associated to the RxPlayer
1061
+ if (this.state === "ENDED" && this._priv_stopAtEnd) {
1062
+ currentContentCanceller.cancel();
1063
+ }
1059
1064
  });
1060
1065
 
1061
1066
  // Link playback events to the corresponding callbacks
@@ -2943,7 +2948,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
2943
2948
  return activeRepresentations[currentPeriod.id];
2944
2949
  }
2945
2950
  }
2946
- Player.version = /* PLAYER_VERSION */"3.28.1-dev.2022083000";
2951
+ Player.version = /* PLAYER_VERSION */"3.29.0-dev.2022090500";
2947
2952
 
2948
2953
  /** Every events sent by the RxPlayer's public API. */
2949
2954
  interface IPublicAPIEvent {
@@ -28,6 +28,7 @@ import {
28
28
  ITransportPipelines,
29
29
  } from "../../../transports";
30
30
  import assert from "../../../utils/assert";
31
+ import isNullOrUndefined from "../../../utils/is_null_or_undefined";
31
32
  import TaskCanceller from "../../../utils/task_canceller";
32
33
  import errorSelector from "../utils/error_selector";
33
34
  import {
@@ -103,6 +104,13 @@ export interface IManifestFetcherSettings {
103
104
  maxRetryRegular : number | undefined;
104
105
  /** Maximum number of time a request be retried when the user is offline. */
105
106
  maxRetryOffline : number | undefined;
107
+ /**
108
+ * Timeout after which request are aborted and, depending on other options,
109
+ * retried.
110
+ * To set to `-1` for no timeout.
111
+ * `undefined` will lead to a default, large, timeout being used.
112
+ */
113
+ requestTimeout : number | undefined;
106
114
  }
107
115
 
108
116
  /**
@@ -164,6 +172,7 @@ export default class ManifestFetcher {
164
172
  IManifestFetcherWarningEvent>
165
173
  {
166
174
  return new Observable((obs) => {
175
+ const settings = this._settings;
167
176
  const pipelines = this._pipelines;
168
177
  const requestUrl = url ?? this._manifestUrl;
169
178
 
@@ -228,12 +237,21 @@ export default class ManifestFetcher {
228
237
  * Call the loader part of the pipeline, retrying if it fails according
229
238
  * to the current settings.
230
239
  * Returns the Promise of the last attempt.
231
- * @param {string | undefined} resolverUrl
240
+ * @param {string | undefined} manifestUrl
232
241
  * @returns {Promise}
233
242
  */
234
243
  function callLoaderWithRetries(manifestUrl : string | undefined) {
235
244
  const { loadManifest } = pipelines;
236
- const callLoader = () => loadManifest(manifestUrl, canceller.signal);
245
+ let requestTimeout : number | undefined =
246
+ isNullOrUndefined(settings.requestTimeout) ?
247
+ config.getCurrent().DEFAULT_REQUEST_TIMEOUT :
248
+ settings.requestTimeout;
249
+ if (requestTimeout < 0) {
250
+ requestTimeout = undefined;
251
+ }
252
+ const callLoader = () => loadManifest(manifestUrl,
253
+ { timeout: requestTimeout },
254
+ canceller.signal);
237
255
  return tryRequestPromiseWithBackoff(callLoader,
238
256
  backoffSettings,
239
257
  canceller.signal);
@@ -72,6 +72,11 @@ export default function createSegmentFetcher<TLoadedFormat, TSegmentDataType>(
72
72
  callbacks : ISegmentFetcherCreatorCallbacks,
73
73
  options : ISegmentFetcherOptions
74
74
  ) : ISegmentFetcher<TSegmentDataType> {
75
+ const requestOptions = {
76
+ timeout: options.requestTimeout < 0 ? undefined :
77
+ options.requestTimeout,
78
+ };
79
+
75
80
  /**
76
81
  * Cache audio and video initialization segments.
77
82
  * This allows to avoid doing too many requests for what are usually very
@@ -257,7 +262,11 @@ export default function createSegmentFetcher<TLoadedFormat, TSegmentDataType>(
257
262
  url : string | null,
258
263
  cancellationSignal: CancellationSignal
259
264
  ) {
260
- return loadSegment(url, content, cancellationSignal, loaderCallbacks);
265
+ return loadSegment(url,
266
+ content,
267
+ requestOptions,
268
+ cancellationSignal,
269
+ loaderCallbacks);
261
270
  }
262
271
 
263
272
  /**
@@ -428,6 +437,12 @@ export interface ISegmentFetcherOptions {
428
437
  * currently offline.
429
438
  */
430
439
  maxRetryOffline : number;
440
+ /**
441
+ * Timeout after which request are aborted and, depending on other options,
442
+ * retried.
443
+ * To set to `-1` for no timeout.
444
+ */
445
+ requestTimeout : number;
431
446
  }
432
447
 
433
448
  /**
@@ -439,11 +454,14 @@ export function getSegmentFetcherOptions(
439
454
  bufferType : string,
440
455
  { maxRetryRegular,
441
456
  maxRetryOffline,
442
- lowLatencyMode } : { maxRetryRegular? : number | undefined;
457
+ lowLatencyMode,
458
+ requestTimeout } : { maxRetryRegular? : number | undefined;
443
459
  maxRetryOffline? : number | undefined;
460
+ requestTimeout? : number | undefined;
444
461
  lowLatencyMode : boolean; }
445
462
  ) : ISegmentFetcherOptions {
446
463
  const { DEFAULT_MAX_REQUESTS_RETRY_ON_ERROR,
464
+ DEFAULT_REQUEST_TIMEOUT,
447
465
  DEFAULT_MAX_REQUESTS_RETRY_ON_OFFLINE,
448
466
  INITIAL_BACKOFF_DELAY_BASE,
449
467
  MAX_BACKOFF_DELAY_BASE } = config.getCurrent();
@@ -453,5 +471,7 @@ export function getSegmentFetcherOptions(
453
471
  baseDelay: lowLatencyMode ? INITIAL_BACKOFF_DELAY_BASE.LOW_LATENCY :
454
472
  INITIAL_BACKOFF_DELAY_BASE.REGULAR,
455
473
  maxDelay: lowLatencyMode ? MAX_BACKOFF_DELAY_BASE.LOW_LATENCY :
456
- MAX_BACKOFF_DELAY_BASE.REGULAR };
474
+ MAX_BACKOFF_DELAY_BASE.REGULAR,
475
+ requestTimeout: isNullOrUndefined(requestTimeout) ? DEFAULT_REQUEST_TIMEOUT :
476
+ requestTimeout };
457
477
  }
@@ -135,4 +135,11 @@ export interface ISegmentFetcherCreatorBackoffOptions {
135
135
  maxRetryRegular : number | undefined;
136
136
  /** Maximum number of time a request be retried when the user is offline. */
137
137
  maxRetryOffline : number | undefined;
138
+ /**
139
+ * Timeout after which request are aborted and, depending on other options,
140
+ * retried.
141
+ * To set to `-1` for no timeout.
142
+ * `undefined` will lead to a default, large, timeout being used.
143
+ */
144
+ requestTimeout : number | undefined;
138
145
  }
@@ -170,7 +170,7 @@ export default function initializeDirectfileContent({
170
170
  * Observable trying to avoid various stalling situations, emitting "stalled"
171
171
  * events when it cannot, as well as "unstalled" events when it get out of one.
172
172
  */
173
- const stallAvoider$ = StallAvoider(playbackObserver, null, EMPTY, EMPTY);
173
+ const stallAvoider$ = StallAvoider(playbackObserver, null, speed, EMPTY, EMPTY);
174
174
 
175
175
  /**
176
176
  * Emit a "loaded" events once the initial play has been performed and the
@@ -212,6 +212,7 @@ export default function createMediaSourceLoader(
212
212
  */
213
213
  const stallAvoider$ = StallAvoider(playbackObserver,
214
214
  manifest,
215
+ speed,
215
216
  lockedStream$,
216
217
  discontinuityUpdate$);
217
218