From 735512ce3db7a802598dc704806ade942208dbea Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Fri, 20 Mar 2020 18:48:02 +0100
Subject: [PATCH 01/72] source-buffers: update SourceBufferStore to move the
native buffer synchronization logic out of init
---
src/core/api/public_api.ts | 9 +-
.../orchestrator/buffer_orchestrator.ts | 6 +-
src/core/buffers/period/period_buffer.ts | 22 ++--
src/core/init/load_on_media_source.ts | 44 +------
.../source_buffers/source_buffers_store.ts | 120 ++++++++++++++++--
5 files changed, 134 insertions(+), 67 deletions(-)
diff --git a/src/core/api/public_api.ts b/src/core/api/public_api.ts
index 194dce0703..ec2d2a857e 100644
--- a/src/core/api/public_api.ts
+++ b/src/core/api/public_api.ts
@@ -1873,10 +1873,11 @@ class Player extends EventEmitter {
{
return null;
}
- const queuedSourceBuffer = this._priv_contentInfos
- .sourceBuffersStore.get(bufferType);
- return queuedSourceBuffer === null ? null :
- queuedSourceBuffer.getInventory();
+ const sourceBufferStatus = this._priv_contentInfos
+ .sourceBuffersStore.getStatus(bufferType);
+ return sourceBufferStatus.type === "set" ?
+ sourceBufferStatus.value.getInventory() :
+ null;
}
/**
diff --git a/src/core/buffers/orchestrator/buffer_orchestrator.ts b/src/core/buffers/orchestrator/buffer_orchestrator.ts
index b8c23b0cdf..16c9f44728 100644
--- a/src/core/buffers/orchestrator/buffer_orchestrator.ts
+++ b/src/core/buffers/orchestrator/buffer_orchestrator.ts
@@ -281,15 +281,17 @@ export default function BufferOrchestrator(
const handleDecipherabilityUpdate$ = fromEvent(manifest, "decipherabilityUpdate")
.pipe(mergeMap((updates) => {
- const queuedSourceBuffer = sourceBuffersStore.get(bufferType);
+ const sourceBufferStatus = sourceBuffersStore.getStatus(bufferType);
const hasType = updates.some(update => update.adaptation.type === bufferType);
- if (!hasType || queuedSourceBuffer == null) {
+ if (!hasType || sourceBufferStatus.type !== "set") {
return EMPTY; // no need to stop the current buffers
}
+ const queuedSourceBuffer = sourceBufferStatus.value;
const rangesToClean = getBlacklistedRanges(queuedSourceBuffer, updates);
enableOutOfBoundsCheck = false;
destroyBuffers$.next();
return observableConcat(
+ sourceBuffersStore.onSourceBuffersReady().pipe(ignoreElements()),
...rangesToClean.map(({ start, end }) =>
queuedSourceBuffer.removeBuffer(start, end).pipe(ignoreElements())),
clock$.pipe(take(1), mergeMap((lastTick) => {
diff --git a/src/core/buffers/period/period_buffer.ts b/src/core/buffers/period/period_buffer.ts
index 60ab546557..4ba910c48b 100644
--- a/src/core/buffers/period/period_buffer.ts
+++ b/src/core/buffers/period/period_buffer.ts
@@ -114,18 +114,22 @@ export default function PeriodBuffer({
const adaptation$ = new ReplaySubject(1);
return adaptation$.pipe(
switchMap((adaptation) => {
- if (adaptation == null) {
+ if (adaptation === null) {
log.info(`Buffer: Set no ${bufferType} Adaptation`, period);
- const previousQSourceBuffer = sourceBuffersStore.get(bufferType);
+ const sourceBufferStatus = sourceBuffersStore.getStatus(bufferType);
let cleanBuffer$ : Observable;
- if (previousQSourceBuffer != null) {
+ if (sourceBufferStatus.type === "set") {
log.info(`Buffer: Clearing previous ${bufferType} SourceBuffer`);
+ const previousQSourceBuffer = sourceBufferStatus.value;
cleanBuffer$ = previousQSourceBuffer
.removeBuffer(period.start,
period.end == null ? Infinity :
period.end);
} else {
+ if (sourceBufferStatus.type === "unset") {
+ sourceBuffersStore.disableSourceBuffer(bufferType);
+ }
cleanBuffer$ = observableOf(null);
}
@@ -161,7 +165,9 @@ export default function PeriodBuffer({
const bufferGarbageCollector$ = garbageCollectors.get(qSourceBuffer);
const adaptationBuffer$ = createAdaptationBuffer(adaptation, qSourceBuffer);
- return observableConcat(cleanBuffer$,
+ return observableConcat(sourceBuffersStore.onSourceBuffersReady()
+ .pipe(ignoreElements()),
+ cleanBuffer$,
observableMerge(adaptationBuffer$,
bufferGarbageCollector$));
}));
@@ -232,10 +238,10 @@ function createOrReuseQueuedSourceBuffer(
adaptation : Adaptation,
options: { textTrackOptions? : ITextTrackSourceBufferOptions }
) : QueuedSourceBuffer {
- const currentQSourceBuffer = sourceBuffersStore.get(bufferType);
- if (currentQSourceBuffer != null) {
+ const sourceBufferStatus = sourceBuffersStore.getStatus(bufferType);
+ if (sourceBufferStatus.type === "set") {
log.info("Buffer: Reusing a previous SourceBuffer for the type", bufferType);
- return currentQSourceBuffer;
+ return sourceBufferStatus.value;
}
const codec = getFirstDeclaredMimeType(adaptation);
const sbOptions = bufferType === "text" ? options.textTrackOptions : undefined;
@@ -243,7 +249,7 @@ function createOrReuseQueuedSourceBuffer(
}
/**
- * Get mimetype string of the first representation declared in the given
+ * Get mime-type string of the first representation declared in the given
* adaptation.
* @param {Adaptation} adaptation
* @returns {string}
diff --git a/src/core/init/load_on_media_source.ts b/src/core/init/load_on_media_source.ts
index 196895b7b9..6938a0a1c2 100644
--- a/src/core/init/load_on_media_source.ts
+++ b/src/core/init/load_on_media_source.ts
@@ -33,9 +33,7 @@ import {
} from "rxjs/operators";
import { MediaError } from "../../errors";
import log from "../../log";
-import Manifest, {
- Period,
-} from "../../manifest";
+import Manifest from "../../manifest";
import ABRManager from "../abr";
import BufferOrchestrator, {
IBufferOrchestratorEvent,
@@ -135,18 +133,6 @@ export default function createMediaSourceLoader({
// single SourceBuffer per type.
const sourceBuffersStore = new SourceBuffersStore(mediaElement, mediaSource);
- // Initialize all native source buffers from the first period at the same
- // time.
- // We cannot lazily create native sourcebuffers since the spec does not
- // allow adding them during playback.
- //
- // From https://w3c.github.io/media-source/#methods
- // For example, a user agent may throw a QuotaExceededError
- // exception if the media element has reached the HAVE_METADATA
- // readyState. This can occur if the user agent's media engine
- // does not support adding more tracks during playback.
- createNativeSourceBuffersForPeriod(sourceBuffersStore, initialPeriod);
-
const { seek$, load$ } = seekAndLoadOnMediaEvents({ clock$,
mediaElement,
startTime: initialTime,
@@ -243,31 +229,3 @@ export default function createMediaSourceLoader({
}));
};
}
-
-/**
- * Create all native SourceBuffers needed for a given Period.
- *
- * Native Buffers have the particulary to need to be created at the beginning of
- * the content.
- * Custom source buffers (entirely managed in JS) can generally be created and
- * disposed at will during the lifecycle of the content.
- * @param {SourceBuffersStore} sourceBuffersStore
- * @param {Period} period
- */
-function createNativeSourceBuffersForPeriod(
- sourceBuffersStore : SourceBuffersStore,
- period : Period
-) : void {
- Object.keys(period.adaptations).forEach(bufferType => {
- if (SourceBuffersStore.isNative(bufferType)) {
- const adaptations = period.adaptations[bufferType];
- const representations = adaptations != null &&
- adaptations.length > 0 ? adaptations[0].representations :
- [];
- if (representations.length > 0) {
- const codec = representations[0].getMimeTypeString();
- sourceBuffersStore.createSourceBuffer(bufferType, codec);
- }
- }
- });
-}
diff --git a/src/core/source_buffers/source_buffers_store.ts b/src/core/source_buffers/source_buffers_store.ts
index 86b14ed5f5..2295650648 100644
--- a/src/core/source_buffers/source_buffers_store.ts
+++ b/src/core/source_buffers/source_buffers_store.ts
@@ -14,6 +14,10 @@
* limitations under the License.
*/
+import {
+ Observable,
+ of as observableOf,
+} from "rxjs";
import { ICustomSourceBuffer } from "../../compat";
import { MediaError } from "../../errors";
import features from "../../features";
@@ -81,6 +85,10 @@ type INativeSourceBufferType = "audio" | "video";
* The returned SourceBuffer is actually a QueuedSourceBuffer instance which
* wrap a SourceBuffer implementation to queue all its actions.
*
+ * To be able to use a native SourceBuffer, you will first need to wait until
+ * both of these are either created or disabled. The Observable returned by
+ * `onSourceBuffersReady` will emit when that is the case.
+ *
* @class SourceBuffersStore
*/
export default class SourceBuffersStore {
@@ -99,12 +107,30 @@ export default class SourceBuffersStore {
private readonly _mediaSource : MediaSource;
private _initializedSourceBuffers : {
- audio? : QueuedSourceBuffer;
- video? : QueuedSourceBuffer;
- text? : QueuedSourceBuffer;
- image? : QueuedSourceBuffer;
+ audio? : QueuedSourceBuffer |
+ null;
+ video? : QueuedSourceBuffer |
+ null;
+ text? : QueuedSourceBuffer |
+ null;
+ image? : QueuedSourceBuffer |
+ null;
};
+ /**
+ * Callbacks called when a native SourceBuffers is either created or disabled.
+ * Used for example to trigger the `this.onSourceBuffersReady` Observable.
+ */
+ private _onNativeSourceBufferAddedOrDisabled : Array<() => void>;
+
/**
* @param {HTMLMediaElement} mediaElement
* @param {MediaSource} mediaSource
@@ -114,25 +140,93 @@ export default class SourceBuffersStore {
this._mediaElement = mediaElement;
this._mediaSource = mediaSource;
this._initializedSourceBuffers = {};
+ this._onNativeSourceBufferAddedOrDisabled = [];
}
/**
- * Returns the created QueuedSourceBuffer for the given type.
- * Returns null if no QueuedSourceBuffer were created for the given type.
+ * Returns the current "status" of the buffer in the SourceBuffer.
+ *
+ * This function will return an object containing the key `type` which can
+ * be equal to either one of those three value:
+ *
+ * - "set": A SourceBuffer has been created. You will in this case also have
+ * a second key, `value`, which will contain the related
+ * QueuedSourceBuffer instance.
+ * Please note that you will need to wait until `this.onSourceBuffersReady()`
+ * has emitted before updating a native SourceBuffer.
+ *
+ * - "disabled": The SourceBuffer has been explicitely disabled for this
+ * type.
+ *
+ * - "unset": No action has been taken yet for that SourceBuffer.
*
* @param {string} bufferType
* @returns {QueuedSourceBuffer|null}
*/
- public get(bufferType : IBufferType) : QueuedSourceBuffer|null {
+ public getStatus(bufferType : IBufferType) : { type : "set";
+ value : QueuedSourceBuffer; } |
+ { type : "unset" } |
+ { type : "disabled" }
+ {
const initializedBuffer = this._initializedSourceBuffers[bufferType];
- return initializedBuffer != null ? initializedBuffer :
- null;
+ return initializedBuffer === undefined ? { type: "unset" } :
+ initializedBuffer === null ? { type: "disabled" } :
+ { type: "set",
+ value: initializedBuffer };
+ }
+
+ /**
+ * Native SourceBuffers (audio and video) need to all be created before they
+ * can be used.
+ *
+ * From https://w3c.github.io/media-source/#methods
+ * For example, a user agent may throw a QuotaExceededError
+ * exception if the media element has reached the HAVE_METADATA
+ * readyState. This can occur if the user agent's media engine
+ * does not support adding more tracks during playback.
+ *
+ * This function will return an Observable emitting when each of these
+ * SourceBuffers is either created or disabled.
+ * @return {Observable}
+ */
+ public onSourceBuffersReady() : Observable {
+ if (this._areNativeSourceBuffersReady()) {
+ return observableOf(undefined);
+ }
+ return new Observable(obs => {
+ this._onNativeSourceBufferAddedOrDisabled.push(() => {
+ if (this._areNativeSourceBuffersReady()) {
+ obs.next(undefined);
+ obs.complete();
+ }
+ });
+ });
+ }
+
+ /**
+ * Explicitely set no SourceBuffer for a given buffer type.
+ * A call to this function is needed at least for unused native buffer types
+ * ("audio" and "video"), to be able to emit through `onSourceBuffersReady`
+ * when both of those are set.
+ * @param {string}
+ */
+ public disableSourceBuffer(bufferType : IBufferType) : void {
+ if (this._initializedSourceBuffers[bufferType] !== undefined) {
+ throw new Error("Cannot disable an active SourceBuffer.");
+ }
+ this._initializedSourceBuffers[bufferType] = null;
+ if (SourceBuffersStore.isNative(bufferType)) {
+ this._onNativeSourceBufferAddedOrDisabled.forEach(cb => cb());
+ }
}
/**
* Creates a new QueuedSourceBuffer for the SourceBuffer type.
* Reuse an already created one if a QueuedSourceBuffer for the given type
* already exists.
+ *
+ * Please note that you will need to wait until `this.onSourceBuffersReady()`
+ * has emitted before updating a native SourceBuffer.
* @param {string} bufferType
* @param {string} codec
* @param {Object|undefined} options
@@ -159,6 +253,7 @@ export default class SourceBuffersStore {
this._mediaSource,
codec);
this._initializedSourceBuffers[bufferType] = nativeSourceBuffer;
+ this._onNativeSourceBufferAddedOrDisabled.forEach(cb => cb());
return nativeSourceBuffer;
}
@@ -239,11 +334,16 @@ export default class SourceBuffersStore {
*/
public disposeAll() {
POSSIBLE_BUFFER_TYPES.forEach((bufferType : IBufferType) => {
- if (this.get(bufferType) != null) {
+ if (this.getStatus(bufferType).type === "set") {
this.disposeSourceBuffer(bufferType);
}
});
}
+
+ private _areNativeSourceBuffersReady() {
+ return this._initializedSourceBuffers.audio !== undefined &&
+ this._initializedSourceBuffers.video !== undefined;
+ }
}
/**
From 5d83741fe9e840c89b7d3bec5f0f3db1a370b8f0 Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Mon, 23 Mar 2020 14:16:40 +0100
Subject: [PATCH 02/72] buffers: remove unnecessary onSourceBuffersReady call
---
src/core/buffers/orchestrator/buffer_orchestrator.ts | 1 -
src/core/buffers/period/period_buffer.ts | 10 +++++-----
2 files changed, 5 insertions(+), 6 deletions(-)
diff --git a/src/core/buffers/orchestrator/buffer_orchestrator.ts b/src/core/buffers/orchestrator/buffer_orchestrator.ts
index 16c9f44728..6052e56ed3 100644
--- a/src/core/buffers/orchestrator/buffer_orchestrator.ts
+++ b/src/core/buffers/orchestrator/buffer_orchestrator.ts
@@ -291,7 +291,6 @@ export default function BufferOrchestrator(
enableOutOfBoundsCheck = false;
destroyBuffers$.next();
return observableConcat(
- sourceBuffersStore.onSourceBuffersReady().pipe(ignoreElements()),
...rangesToClean.map(({ start, end }) =>
queuedSourceBuffer.removeBuffer(start, end).pipe(ignoreElements())),
clock$.pipe(take(1), mergeMap((lastTick) => {
diff --git a/src/core/buffers/period/period_buffer.ts b/src/core/buffers/period/period_buffer.ts
index 4ba910c48b..3ea5486177 100644
--- a/src/core/buffers/period/period_buffer.ts
+++ b/src/core/buffers/period/period_buffer.ts
@@ -165,11 +165,11 @@ export default function PeriodBuffer({
const bufferGarbageCollector$ = garbageCollectors.get(qSourceBuffer);
const adaptationBuffer$ = createAdaptationBuffer(adaptation, qSourceBuffer);
- return observableConcat(sourceBuffersStore.onSourceBuffersReady()
- .pipe(ignoreElements()),
- cleanBuffer$,
- observableMerge(adaptationBuffer$,
- bufferGarbageCollector$));
+ return sourceBuffersStore.onSourceBuffersReady().pipe(mergeMap(() => {
+ return observableConcat(cleanBuffer$,
+ observableMerge(adaptationBuffer$,
+ bufferGarbageCollector$));
+ }));
}));
return observableConcat(
From 978a9ba3ae73be2971124fa632bbdb964380e7dd Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Wed, 25 Mar 2020 18:03:27 +0100
Subject: [PATCH 03/72] source-buffers: do not throw when re-disabling a
SourceBuffer
---
src/core/source_buffers/source_buffers_store.ts | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/src/core/source_buffers/source_buffers_store.ts b/src/core/source_buffers/source_buffers_store.ts
index 2295650648..146e70d461 100644
--- a/src/core/source_buffers/source_buffers_store.ts
+++ b/src/core/source_buffers/source_buffers_store.ts
@@ -211,7 +211,12 @@ export default class SourceBuffersStore {
* @param {string}
*/
public disableSourceBuffer(bufferType : IBufferType) : void {
- if (this._initializedSourceBuffers[bufferType] !== undefined) {
+ const currentValue = this._initializedSourceBuffers[bufferType];
+ if (currentValue === null) {
+ log.warn(`SBS: The ${bufferType} SourceBuffer was already disabled.`);
+ return;
+ }
+ if (currentValue !== undefined) {
throw new Error("Cannot disable an active SourceBuffer.");
}
this._initializedSourceBuffers[bufferType] = null;
@@ -340,6 +345,10 @@ export default class SourceBuffersStore {
});
}
+ /**
+ * Returns `true` when we're ready to push and decode contents through our
+ * native SourceBuffers.
+ */
private _areNativeSourceBuffersReady() {
return this._initializedSourceBuffers.audio !== undefined &&
this._initializedSourceBuffers.video !== undefined;
From b0bcefee48765540b23fe10a9ea150ca28a9f3e2 Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Wed, 25 Mar 2020 18:19:21 +0100
Subject: [PATCH 04/72] source-buffers: do not start a content if both audio
and video are disabled
---
src/core/buffers/period/period_buffer.ts | 2 +-
.../source_buffers/source_buffers_store.ts | 50 ++++++++++++-------
2 files changed, 32 insertions(+), 20 deletions(-)
diff --git a/src/core/buffers/period/period_buffer.ts b/src/core/buffers/period/period_buffer.ts
index 3ea5486177..b0acaed1a5 100644
--- a/src/core/buffers/period/period_buffer.ts
+++ b/src/core/buffers/period/period_buffer.ts
@@ -165,7 +165,7 @@ export default function PeriodBuffer({
const bufferGarbageCollector$ = garbageCollectors.get(qSourceBuffer);
const adaptationBuffer$ = createAdaptationBuffer(adaptation, qSourceBuffer);
- return sourceBuffersStore.onSourceBuffersReady().pipe(mergeMap(() => {
+ return sourceBuffersStore.waitForUsableSourceBuffers().pipe(mergeMap(() => {
return observableConcat(cleanBuffer$,
observableMerge(adaptationBuffer$,
bufferGarbageCollector$));
diff --git a/src/core/source_buffers/source_buffers_store.ts b/src/core/source_buffers/source_buffers_store.ts
index 146e70d461..ea0397fa4a 100644
--- a/src/core/source_buffers/source_buffers_store.ts
+++ b/src/core/source_buffers/source_buffers_store.ts
@@ -76,8 +76,9 @@ type INativeSourceBufferType = "audio" | "video";
*
* Only one SourceBuffer per type is allowed at the same time:
*
- * - source buffers for native types (which depends on the native
- * SourceBuffer implementation), are reused if one is re-created.
+ * - source buffers for native types (which are "audio" and "video" and which
+ * depend on the native SourceBuffer implementation) are reused if one is
+ * re-created.
*
* - source buffers for custom types are aborted each time a new one of the
* same type is created.
@@ -86,8 +87,10 @@ type INativeSourceBufferType = "audio" | "video";
* wrap a SourceBuffer implementation to queue all its actions.
*
* To be able to use a native SourceBuffer, you will first need to wait until
- * both of these are either created or disabled. The Observable returned by
- * `onSourceBuffersReady` will emit when that is the case.
+ * it is created - of course - but also until the other one is either created or
+ * disabled.
+ * The Observable returned by `waitForUsableSourceBuffers` will emit when
+ * that is the case.
*
* @class SourceBuffersStore
*/
@@ -127,7 +130,8 @@ export default class SourceBuffersStore {
/**
* Callbacks called when a native SourceBuffers is either created or disabled.
- * Used for example to trigger the `this.onSourceBuffersReady` Observable.
+ * Used for example to trigger the `this.waitForUsableSourceBuffers`
+ * Observable.
*/
private _onNativeSourceBufferAddedOrDisabled : Array<() => void>;
@@ -152,8 +156,9 @@ export default class SourceBuffersStore {
* - "set": A SourceBuffer has been created. You will in this case also have
* a second key, `value`, which will contain the related
* QueuedSourceBuffer instance.
- * Please note that you will need to wait until `this.onSourceBuffersReady()`
- * has emitted before updating a native SourceBuffer.
+ * Please note that you will need to wait until
+ * `this.waitForUsableSourceBuffers()` has emitted before updating a
+ * native SourceBuffer.
*
* - "disabled": The SourceBuffer has been explicitely disabled for this
* type.
@@ -185,17 +190,17 @@ export default class SourceBuffersStore {
* readyState. This can occur if the user agent's media engine
* does not support adding more tracks during playback.
*
- * This function will return an Observable emitting when each of these
- * SourceBuffers is either created or disabled.
+ * This function will return an Observable emitting when any and all native
+ * Source Buffers through this store can be used.
* @return {Observable}
*/
- public onSourceBuffersReady() : Observable {
- if (this._areNativeSourceBuffersReady()) {
+ public waitForUsableSourceBuffers() : Observable {
+ if (this._areNativeSourceBuffersUsable()) {
return observableOf(undefined);
}
return new Observable(obs => {
this._onNativeSourceBufferAddedOrDisabled.push(() => {
- if (this._areNativeSourceBuffersReady()) {
+ if (this._areNativeSourceBuffersUsable()) {
obs.next(undefined);
obs.complete();
}
@@ -206,8 +211,8 @@ export default class SourceBuffersStore {
/**
* Explicitely set no SourceBuffer for a given buffer type.
* A call to this function is needed at least for unused native buffer types
- * ("audio" and "video"), to be able to emit through `onSourceBuffersReady`
- * when both of those are set.
+ * ("audio" and "video"), to be able to emit through
+ * `waitForUsableSourceBuffers` when conditions are met.
* @param {string}
*/
public disableSourceBuffer(bufferType : IBufferType) : void {
@@ -230,7 +235,7 @@ export default class SourceBuffersStore {
* Reuse an already created one if a QueuedSourceBuffer for the given type
* already exists.
*
- * Please note that you will need to wait until `this.onSourceBuffersReady()`
+ * Please note that you will need to wait until `this.waitForUsableSourceBuffers()`
* has emitted before updating a native SourceBuffer.
* @param {string} bufferType
* @param {string} codec
@@ -347,11 +352,18 @@ export default class SourceBuffersStore {
/**
* Returns `true` when we're ready to push and decode contents through our
- * native SourceBuffers.
+ * created native SourceBuffers.
*/
- private _areNativeSourceBuffersReady() {
- return this._initializedSourceBuffers.audio !== undefined &&
- this._initializedSourceBuffers.video !== undefined;
+ private _areNativeSourceBuffersUsable() {
+ if (this._initializedSourceBuffers.audio === undefined ||
+ this._initializedSourceBuffers.video === undefined)
+ {
+ return false;
+ }
+ if (this._initializedSourceBuffers.video === null) {
+ return this._initializedSourceBuffers.audio !== null;
+ }
+ return true;
}
}
From 2c917afaf333a34f75cde74b969e8af756ee16f6 Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Fri, 3 Apr 2020 13:54:03 +0200
Subject: [PATCH 05/72] api: add "codec" property to `preferredAudioTrack` APIs
---
doc/api/index.md | 31 ++++++++++++++------
doc/api/player_options.md | 19 +++++++++++++
src/core/api/track_choice_manager.ts | 42 +++++++++++++++++++++-------
3 files changed, 73 insertions(+), 19 deletions(-)
diff --git a/doc/api/index.md b/doc/api/index.md
index 1239ca3aeb..7d4e9816b8 100644
--- a/doc/api/index.md
+++ b/doc/api/index.md
@@ -1327,6 +1327,25 @@ This method takes an array of objects describing the languages wanted:
}
```
+Optionally, you can ask for tracks having specific codecs by adding a `codec`
+property:
+```js
+// Example: English tracks in Dolby Digital Plus
+{
+ language: "eng",
+ audioDescription: false,
+ codec: {
+ test: /ec-3/, // RegExp validating the codec you want.
+ all: true, // Whether all the profiles (i.e. Representation) in a track
+ // should be checked to have codecs compatible to that RegExp.
+ // If `true`, we will only choose a track if every profiles for
+ // it have a codec that is validated by that RegExp.
+ // If `false`, we will choose a track if we know that at least
+ // a single profile from it has a codec validated by that RegExp.
+ }
+}
+```
+
All elements in that Array should be set in preference order: from the most
preferred to the least preferred.
@@ -1382,15 +1401,9 @@ Returns the current list of preferred audio tracks - by order of preference.
This returns the data in the same format that it was given to either the
`preferredAudioTracks` constructor option or the last `setPreferredAudioTracks`
-if it was called:
-```js
-{
- language: "fra", // {string} The wanted language
- // (ISO 639-1, ISO 639-2 or ISO 639-3 language code)
- audioDescription: false // {Boolean} Whether the audio track should be an
- // audio description for the visually impaired
-}
-```
+if it was called.
+
+It will return an empty Array if none of those two APIs were used until now.
diff --git a/doc/api/player_options.md b/doc/api/player_options.md
index 0e56d0cab2..f2eea17075 100644
--- a/doc/api/player_options.md
+++ b/doc/api/player_options.md
@@ -240,6 +240,25 @@ This option takes an array of objects describing the languages wanted:
}
```
+Optionally, you can ask for tracks having specific codecs by adding a `codec`
+property:
+```js
+// Example: English tracks in Dolby Digital Plus
+{
+ language: "eng",
+ audioDescription: false,
+ codec: {
+ test: /ec-3/, // RegExp validating the codec you want.
+ all: true, // Whether all the profiles (i.e. Representation) in a track
+ // should be checked to have codecs compatible to that RegExp.
+ // If `true`, we will only choose a track if every profiles for
+ // it have a codec that is validated by that RegExp.
+ // If `false`, we will choose a track if we know that at least
+ // a single profile from it has a codec validated by that RegExp.
+ }
+}
+```
+
All elements in that Array should be set in preference order: from the most
preferred to the least preferred.
diff --git a/src/core/api/track_choice_manager.ts b/src/core/api/track_choice_manager.ts
index c5b23267d9..fab239b605 100644
--- a/src/core/api/track_choice_manager.ts
+++ b/src/core/api/track_choice_manager.ts
@@ -38,7 +38,9 @@ import takeFirstSet from "../../utils/take_first_set";
/** Single preference for an audio track Adaptation. */
export type IAudioTrackPreference = null |
{ language : string;
- audioDescription : boolean; };
+ audioDescription : boolean;
+ codec? : { all: boolean;
+ test: RegExp; }; };
/** Single preference for a text track Adaptation. */
export type ITextTrackPreference = null |
@@ -106,7 +108,9 @@ interface ITMPeriodInfos { period : Period;
/** Audio track preference once normalized by the TrackChoiceManager. */
type INormalizedPreferredAudioTrack = null |
{ normalized : string;
- audioDescription : boolean; };
+ audioDescription : boolean;
+ codec? : { all: boolean;
+ test: RegExp; }; };
/** Text track preference once normalized by the TrackChoiceManager. */
type INormalizedPreferredTextTrack = null |
@@ -125,7 +129,8 @@ function normalizeAudioTracks(
return tracks.map(t => t == null ?
t :
{ normalized: normalizeLanguage(t.language),
- audioDescription: t.audioDescription });
+ audioDescription: t.audioDescription,
+ codec: t.codec });
}
/**
@@ -841,13 +846,30 @@ function findFirstOptimalAudioAdaptation(
return null;
}
- const foundAdaptation = arrayFind(audioAdaptations, (audioAdaptation) =>
- takeFirstSet(audioAdaptation.normalizedLanguage,
- "") === preferredAudioTrack.normalized &&
- (preferredAudioTrack.audioDescription ?
- audioAdaptation.isAudioDescription === true :
- audioAdaptation.isAudioDescription !== true)
- );
+ const foundAdaptation = arrayFind(audioAdaptations, (audioAdaptation) => {
+ const language = audioAdaptation.normalizedLanguage ?? "";
+ if (language !== preferredAudioTrack.normalized) {
+ return false;
+ }
+ if (preferredAudioTrack.audioDescription) {
+ if (audioAdaptation.isAudioDescription !== true) {
+ return false;
+ }
+ } else if (audioAdaptation.isAudioDescription === true) {
+ return false;
+ }
+ if (preferredAudioTrack.codec === undefined) {
+ return true;
+ }
+ const regxp = preferredAudioTrack.codec.test;
+ const codecTestingFn = (rep : Representation) =>
+ rep.codec !== undefined && regxp.test(rep.codec);
+
+ if (preferredAudioTrack.codec.all) {
+ return audioAdaptation.representations.every(codecTestingFn);
+ }
+ return audioAdaptation.representations.some(codecTestingFn);
+ });
if (foundAdaptation !== undefined) {
return foundAdaptation;
From 0b5d3d70f350f095efcc3d23a5d4cbfcda8fbd13 Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Fri, 3 Apr 2020 14:43:35 +0200
Subject: [PATCH 06/72] api: all properties in preferredAudioTracks are now
optional
---
doc/api/index.md | 126 ++++++++++++++++++---------
doc/api/player_options.md | 108 ++++++++++++++++-------
src/core/api/track_choice_manager.ts | 29 +++---
3 files changed, 181 insertions(+), 82 deletions(-)
diff --git a/doc/api/index.md b/doc/api/index.md
index 7d4e9816b8..a9cb37fff0 100644
--- a/doc/api/index.md
+++ b/doc/api/index.md
@@ -1315,64 +1315,75 @@ During this period of time:
_arguments_: ``Array.``
-Update the audio language preferences at any time.
+Allows the RxPlayer to choose an initial audio track, based on language
+preferences, codec preferences or both.
-This method takes an array of objects describing the languages wanted:
-```js
-{
- language: "fra", // {string} The wanted language
- // (ISO 639-1, ISO 639-2 or ISO 639-3 language code)
- audioDescription: false // {Boolean} Whether the audio track should be an
- // audio description for the visually impaired
-}
-```
+It is defined as an array of objects, each object describing constraints a
+track should respect.
+
+If the first object - defining the first set of constraints - can not be
+respected under the currently available audio tracks, the RxPlayer will skip
+it and check with the second object and so on.
+As such, this array should be sorted by order of preference: from the most
+wanted constraints to the least.
-Optionally, you can ask for tracks having specific codecs by adding a `codec`
-property:
+Here is all the possible constraints you can set in any one of those objects
+(note that all properties are optional here, only those set will have an effect
+on which tracks will be filtered):
```js
-// Example: English tracks in Dolby Digital Plus
{
- language: "eng",
- audioDescription: false,
- codec: {
- test: /ec-3/, // RegExp validating the codec you want.
- all: true, // Whether all the profiles (i.e. Representation) in a track
- // should be checked to have codecs compatible to that RegExp.
- // If `true`, we will only choose a track if every profiles for
- // it have a codec that is validated by that RegExp.
+ language: "fra", // {string|undefined} The language the track should be in
+ // (in preference as an ISO 639-1, ISO 639-2 or ISO 639-3
+ // language code).
+ // If not set or set to `undefined`, the RxPlayer won't
+ // filter based on the language of the track.
+
+ audioDescription: false // {Boolean|undefined} Whether the audio track should
+ // be an audio description for the visually impaired
+ // or not.
+ // If not set or set to `undefined`, the RxPlayer
+ // won't filter based on that status.
+
+ codec: { // {Object|undefined} Constraints about the codec wanted.
+ // if not set or set to `undefined` we won't filter based on codecs.
+
+ test: /ec-3/, // {RegExp} RegExp validating the type of codec you want.
+
+ all: true, // {Boolean} Whether all the profiles (i.e. Representation) in a
+ // track should be checked against the RegExp given in `test`.
+ // If `true`, we will only choose a track if EVERY profiles for
+ // it have a codec information that is validated by that RegExp.
// If `false`, we will choose a track if we know that at least
- // a single profile from it has a codec validated by that RegExp.
+ // A SINGLE profile from it has codec information validated by
+ // that RegExp.
}
}
```
-All elements in that Array should be set in preference order: from the most
-preferred to the least preferred.
-
-When encountering a new Period or a new content, the RxPlayer will then try to
-choose its audio track by comparing what is available with your current
-preferences (i.e. if the most preferred is not available, it will look if the
-second one is etc.).
+This logic is ran each time a new `Period` with audio tracks is loaded by the
+RxPlayer. This means at the start of the content, but also when [pre-]loading a
+new DASH `Period` or a new MetaPlaylist `content`.
-Please note that those preferences will only apply either to the next loaded
-content or to the next newly encountered Period.
+Please note that those preferences won't be re-applied once the logic was
+already run for a given `Period`.
Simply put, once set this preference will be applied to all contents but:
- the current Period being played (or the current loaded content, in the case
- of Smooth streaming). In that case, the current audio preference will stay
- in place.
+ of single-Period contents such as in Smooth streaming).
+ In that case, the current audio preference will stay in place.
- - the Periods which have already been played in the current loaded content.
- Those will keep the last set audio preference at the time it was played.
+ - the Periods which have already been loaded in the current content.
+ Those will keep their last set audio preferences (e.g. the preferred audio
+ tracks at the time they were first loaded).
To update the current audio track in those cases, you should use the
-`setAudioTrack` method.
+`setAudioTrack` method once they are currently played.
-#### Example
+
+#### Examples
Let's imagine that you prefer to have french or italian over all other audio
-languages. If not found, you want to fallback to english.
-You will thus call ``setPreferredAudioTracks`` that way.
+languages. If not found, you want to fallback to english:
```js
player.setPreferredAudioTracks([
@@ -1382,6 +1393,43 @@ player.setPreferredAudioTracks([
])
```
+Now let's imagine that you want to have in priority a track that contain at
+least one profile in Dolby Digital Plus (ec-3 codec) without caring about the
+language:
+```js
+player.setPreferredAudioTracks([ { codec: { all: false, test: /ec-3/ } ]);
+```
+
+At last, let's combine both examples by preferring french over itialian, italian
+over english while preferring it to be in Dolby Digital Plus:
+```js
+
+player.setPreferredAudioTracks([
+ {
+ language: "fra",
+ audioDescription: false,
+ codec: { all: false, test: /ec-3/ }
+ },
+
+ // We still prefer non-DD+ french over DD+ italian
+ { language: "fra", audioDescription: false },
+
+ {
+ language: "ita",
+ audioDescription: false,
+ codec: { all: false, test: /ec-3/ }
+ },
+ { language: "ita", audioDescription: false },
+
+ {
+ language: "eng",
+ audioDescription: false,
+ codec: { all: false, test: /ec-3/ }
+ },
+ { language: "eng", audioDescription: false }
+]);
+```
+
---
:warning: This option will have no effect in _DirectFile_ mode
diff --git a/doc/api/player_options.md b/doc/api/player_options.md
index f2eea17075..a2f0ff9107 100644
--- a/doc/api/player_options.md
+++ b/doc/api/player_options.md
@@ -224,53 +224,60 @@ mode (see [loadVideo options](./loadVideo_options.md#prop-transport)).
### preferredAudioTracks #######################################################
-_type_: ``Array.``
+_type_: ``Array.``
_defaults_: ``[]``
-Set the initial audio tracks preferences.
+This option allows to help the RxPlayer choose an initial audio track based on
+either language preferences, codec preferences or both.
-This option takes an array of objects describing the languages wanted:
-```js
-{
- language: "fra", // {string} The wanted language
- // (ISO 639-1, ISO 639-2 or ISO 639-3 language code)
- audioDescription: false // {Boolean} Whether the audio track should be an
- // audio description for the visually impaired
-}
-```
+It is defined as an array of objects, each object describing constraints a
+track should respect.
+
+If the first object - defining the first set of constraints - can not be
+respected under the currently available audio tracks, the RxPlayer will skip
+it and check with the second object and so on.
+As such, this array should be sorted by order of preference: from the most
+wanted constraints to the least.
-Optionally, you can ask for tracks having specific codecs by adding a `codec`
-property:
+Here is all the possible constraints you can set in any one of those objects
+(note that all properties are optional here, only those set will have an effect
+on which tracks will be filtered):
```js
-// Example: English tracks in Dolby Digital Plus
{
- language: "eng",
- audioDescription: false,
- codec: {
- test: /ec-3/, // RegExp validating the codec you want.
- all: true, // Whether all the profiles (i.e. Representation) in a track
- // should be checked to have codecs compatible to that RegExp.
- // If `true`, we will only choose a track if every profiles for
- // it have a codec that is validated by that RegExp.
+ language: "fra", // {string|undefined} The language the track should be in
+ // (in preference as an ISO 639-1, ISO 639-2 or ISO 639-3
+ // language code).
+ // If not set or set to `undefined`, the RxPlayer won't
+ // filter based on the language of the track.
+
+ audioDescription: false // {Boolean|undefined} Whether the audio track should
+ // be an audio description for the visually impaired
+ // or not.
+ // If not set or set to `undefined`, the RxPlayer
+ // won't filter based on that status.
+
+ codec: { // {Object|undefined} Constraints about the codec wanted.
+ // if not set or set to `undefined` we won't filter based on codecs.
+
+ test: /ec-3/, // {RegExp} RegExp validating the type of codec you want.
+
+ all: true, // {Boolean} Whether all the profiles (i.e. Representation) in a
+ // track should be checked against the RegExp given in `test`.
+ // If `true`, we will only choose a track if EVERY profiles for
+ // it have a codec information that is validated by that RegExp.
// If `false`, we will choose a track if we know that at least
- // a single profile from it has a codec validated by that RegExp.
+ // A SINGLE profile from it has codec information validated by
+ // that RegExp.
}
}
```
-All elements in that Array should be set in preference order: from the most
-preferred to the least preferred.
-
-When loading a content, the RxPlayer will then try to choose its audio track by
-comparing what is available with your current preferences (i.e. if the most
-preferred is not available, it will look if the second one etc.).
-
This array of preferrences can be updated at any time through the
``setPreferredAudioTracks`` method, documented
[here](./index.md#meth-getPreferredAudioTracks).
-#### Example
+#### Examples
Let's imagine that you prefer to have french or italian over all other audio
languages. If not found, you want to fallback to english:
@@ -285,6 +292,45 @@ const player = new RxPlayer({
});
```
+Now let's imagine that you want to have in priority a track that contain at
+least one profile in Dolby Digital Plus (ec-3 codec) without caring about the
+language:
+```js
+const player = new RxPlayer({
+ preferredAudioTracks: [ { codec: { all: false, test: /ec-3/ } ]
+});
+```
+
+At last, let's combine both examples by preferring french over itialian, italian
+over english while preferring it to be in Dolby Digital Plus:
+```js
+const player = new RxPlayer({
+ preferredAudioTracks: [
+ {
+ language: "fra",
+ audioDescription: false,
+ codec: { all: false, test: /ec-3/ }
+ },
+
+ // We still prefer non-DD+ french over DD+ italian
+ { language: "fra", audioDescription: false },
+
+ {
+ language: "ita",
+ audioDescription: false,
+ codec: { all: false, test: /ec-3/ }
+ },
+ { language: "ita", audioDescription: false },
+
+ {
+ language: "eng",
+ audioDescription: false,
+ codec: { all: false, test: /ec-3/ }
+ },
+ { language: "eng", audioDescription: false }
+ ]
+```
+
---
:warning: This option will have no effect for contents loaded in _DirectFile_
diff --git a/src/core/api/track_choice_manager.ts b/src/core/api/track_choice_manager.ts
index fab239b605..709e990465 100644
--- a/src/core/api/track_choice_manager.ts
+++ b/src/core/api/track_choice_manager.ts
@@ -37,8 +37,8 @@ import takeFirstSet from "../../utils/take_first_set";
/** Single preference for an audio track Adaptation. */
export type IAudioTrackPreference = null |
- { language : string;
- audioDescription : boolean;
+ { language? : string;
+ audioDescription? : boolean;
codec? : { all: boolean;
test: RegExp; }; };
@@ -107,8 +107,8 @@ interface ITMPeriodInfos { period : Period;
/** Audio track preference once normalized by the TrackChoiceManager. */
type INormalizedPreferredAudioTrack = null |
- { normalized : string;
- audioDescription : boolean;
+ { normalized? : string;
+ audioDescription? : boolean;
codec? : { all: boolean;
test: RegExp; }; };
@@ -128,7 +128,8 @@ function normalizeAudioTracks(
) : INormalizedPreferredAudioTrack[] {
return tracks.map(t => t == null ?
t :
- { normalized: normalizeLanguage(t.language),
+ { normalized: t.language === undefined ? undefined :
+ normalizeLanguage(t.language),
audioDescription: t.audioDescription,
codec: t.codec });
}
@@ -847,16 +848,20 @@ function findFirstOptimalAudioAdaptation(
}
const foundAdaptation = arrayFind(audioAdaptations, (audioAdaptation) => {
- const language = audioAdaptation.normalizedLanguage ?? "";
- if (language !== preferredAudioTrack.normalized) {
- return false;
+ if (preferredAudioTrack.normalized !== undefined) {
+ const language = audioAdaptation.normalizedLanguage ?? "";
+ if (language !== preferredAudioTrack.normalized) {
+ return false;
+ }
}
- if (preferredAudioTrack.audioDescription) {
- if (audioAdaptation.isAudioDescription !== true) {
+ if (preferredAudioTrack.audioDescription !== undefined) {
+ if (preferredAudioTrack.audioDescription) {
+ if (audioAdaptation.isAudioDescription !== true) {
+ return false;
+ }
+ } else if (audioAdaptation.isAudioDescription === true) {
return false;
}
- } else if (audioAdaptation.isAudioDescription === true) {
- return false;
}
if (preferredAudioTrack.codec === undefined) {
return true;
From fcb0dc73db9e8a29cf85abfc5e4c38f217810f70 Mon Sep 17 00:00:00 2001
From: Mattias Palmgren
Date: Wed, 8 Apr 2020 09:13:39 +0200
Subject: [PATCH 07/72] manifest: Parse information about sign interpretation
---
src/core/api/track_choice_manager.ts | 13 +++++-
src/manifest/adaptation.ts | 13 +++++-
.../manifest/dash/parse_adaptation_sets.ts | 40 ++++++++++++++++++-
.../metaplaylist/metaplaylist_parser.ts | 1 +
src/parsers/manifest/types.ts | 5 +++
5 files changed, 67 insertions(+), 5 deletions(-)
diff --git a/src/core/api/track_choice_manager.ts b/src/core/api/track_choice_manager.ts
index c5b23267d9..f22deeebb5 100644
--- a/src/core/api/track_choice_manager.ts
+++ b/src/core/api/track_choice_manager.ts
@@ -71,6 +71,7 @@ interface ITMVideoRepresentation { id : string|number;
/** Video track returned by the TrackChoiceManager. */
export interface ITMVideoTrack { id : number|string;
+ signInterpreted?: boolean;
representations: ITMVideoRepresentation[]; }
/** Audio track from a list of audio tracks returned by the TrackChoiceManager. */
@@ -579,9 +580,13 @@ export default class TrackChoiceManager {
if (chosenVideoAdaptation == null) {
return null;
}
- return { id: chosenVideoAdaptation.id,
+ const videoTrack: ITMVideoTrack = { id: chosenVideoAdaptation.id,
representations: chosenVideoAdaptation.representations
.map(parseVideoRepresentation) };
+ if (chosenVideoAdaptation.isSignInterpreted === true) {
+ videoTrack.signInterpreted = true;
+ }
+ return videoTrack;
}
/**
@@ -668,12 +673,16 @@ export default class TrackChoiceManager {
return videoInfos.adaptations
.map((adaptation) => {
- return {
+ const formatted: ITMVideoTrackListItem = {
id: adaptation.id,
active: currentId === null ? false :
currentId === adaptation.id,
representations: adaptation.representations.map(parseVideoRepresentation),
};
+ if (adaptation.isSignInterpreted === true) {
+ formatted.signInterpreted = true;
+ }
+ return formatted;
});
}
diff --git a/src/manifest/adaptation.ts b/src/manifest/adaptation.ts
index aed09fcddd..c098d41b5f 100644
--- a/src/manifest/adaptation.ts
+++ b/src/manifest/adaptation.ts
@@ -56,6 +56,7 @@ export interface IRepresentationInfos { bufferType: IAdaptationType;
isAudioDescription? : boolean;
isClosedCaption? : boolean;
isDub? : boolean;
+ isSignInterpreted?: boolean;
normalizedLanguage? : string; }
/** Type for the `representationFilter` API. */
@@ -90,6 +91,12 @@ export default class Adaptation {
/** Whether this Adaptation contains closed captions for the hard-of-hearing. */
public isClosedCaption? : boolean;
+ /**
+ * If true this Adaptation is sign interpreted: which is a
+ * variant in sign language.
+ */
+ public isSignInterpreted? : boolean;
+
/**
* If `true`, this Adaptation is a "dub", meaning it was recorded in another
* language than the original one.
@@ -161,6 +168,9 @@ export default class Adaptation {
if (parsedAdaptation.isDub !== undefined) {
this.isDub = parsedAdaptation.isDub;
}
+ if (parsedAdaptation.isSignInterpreted !== undefined) {
+ this.isSignInterpreted = parsedAdaptation.isSignInterpreted;
+ }
this.representations = argsRepresentations
.map(representation => new Representation(representation))
@@ -175,7 +185,8 @@ export default class Adaptation {
normalizedLanguage: this.normalizedLanguage,
isClosedCaption: this.isClosedCaption,
isDub: this.isDub,
- isAudioDescription: this.isAudioDescription });
+ isAudioDescription: this.isAudioDescription,
+ isSignInterpreted: this.isSignInterpreted });
});
// for manuallyAdded adaptations (not in the manifest)
diff --git a/src/parsers/manifest/dash/parse_adaptation_sets.ts b/src/parsers/manifest/dash/parse_adaptation_sets.ts
index 6d867ef462..084ee4a837 100644
--- a/src/parsers/manifest/dash/parse_adaptation_sets.ts
+++ b/src/parsers/manifest/dash/parse_adaptation_sets.ts
@@ -103,6 +103,23 @@ function isHardOfHearing(
accessibility.value === "2");
}
+/**
+ * Detect if the accessibility given defines an adaptation for the hearing impaired
+ * Based on DASH-IF 4.3.
+ * @param {Object} accessibility
+ * @returns {Boolean}
+ */
+function isHearingImpaired(
+ accessibility? : { schemeIdUri? : string; value? : string }
+) : boolean {
+ if (accessibility == null) {
+ return false;
+ }
+
+ return (accessibility.schemeIdUri === "urn:mpeg:dash:role:2011" &&
+ accessibility.value === "sign");
+}
+
/**
* Contruct Adaptation ID from the information we have.
* @param {Object} adaptation
@@ -113,7 +130,12 @@ function isHardOfHearing(
function getAdaptationID(
adaptation : IAdaptationSetIntermediateRepresentation,
representations : IParsedRepresentation[],
- infos : { isClosedCaption? : boolean; isAudioDescription? : boolean; type : string }
+ infos : {
+ isClosedCaption? : boolean;
+ isAudioDescription? : boolean;
+ isSignInterpreted?: boolean;
+ type : string;
+ }
) : string {
if (isNonEmptyString(adaptation.attributes.id)) {
return adaptation.attributes.id;
@@ -129,6 +151,9 @@ function getAdaptationID(
if (infos.isAudioDescription === true) {
idString += "-ad";
}
+ if (infos.isSignInterpreted === true) {
+ idString += "-si";
+ }
if (isNonEmptyString(adaptation.attributes.contentType)) {
idString += `-${adaptation.attributes.contentType}`;
}
@@ -268,10 +293,17 @@ export default function parseAdaptationSets(
accessibility != null &&
isVisuallyImpaired(accessibility) ? true :
undefined;
+
+ const isSignInterpreted = type === "video" &&
+ accessibility != null &&
+ isHearingImpaired(accessibility) ? true :
+ undefined;
+
const adaptationID = newID = getAdaptationID(adaptation,
- representations,
+ representations,
{ isClosedCaption,
isAudioDescription,
+ isSignInterpreted,
type });
const parsedAdaptationSet : IParsedAdaptation = { id: adaptationID,
representations,
@@ -289,6 +321,10 @@ export default function parseAdaptationSets(
parsedAdaptationSet.isDub = true;
}
+ if (isSignInterpreted === true) {
+ parsedAdaptationSet.isSignInterpreted = true;
+ }
+
const adaptationsOfTheSameType = parsedAdaptations[type];
if (adaptationsOfTheSameType === undefined) {
parsedAdaptations[type] = [parsedAdaptationSet];
diff --git a/src/parsers/manifest/metaplaylist/metaplaylist_parser.ts b/src/parsers/manifest/metaplaylist/metaplaylist_parser.ts
index 09496e4461..e02c823ec9 100644
--- a/src/parsers/manifest/metaplaylist/metaplaylist_parser.ts
+++ b/src/parsers/manifest/metaplaylist/metaplaylist_parser.ts
@@ -228,6 +228,7 @@ function createManifest(
closedCaption: currentAdaptation.isClosedCaption,
isDub: currentAdaptation.isDub,
language: currentAdaptation.language,
+ isSignInterpreted: currentAdaptation.isSignInterpreted,
});
acc[type] = adaptationsForCurrentType;
}
diff --git a/src/parsers/manifest/types.ts b/src/parsers/manifest/types.ts
index 8260e0886e..cf81854193 100644
--- a/src/parsers/manifest/types.ts
+++ b/src/parsers/manifest/types.ts
@@ -123,6 +123,11 @@ export interface IParsedAdaptation {
* than the original(s) one(s).
*/
isDub? : boolean;
+ /**
+ * If true this Adaptation is in a sign interpreted: which is a variant of the
+ * video with sign language.
+ */
+ isSignInterpreted? : boolean;
/**
* Language the `Adaptation` is in.
* Not set if unknown or if it makes no sense for the current track.
From f5c96e6c684dde81afdd93538da478552ce7597e Mon Sep 17 00:00:00 2001
From: Paul Rosset
Date: Fri, 27 Sep 2019 16:34:18 +0200
Subject: [PATCH 08/72] feat(audio-only): Create an audio only MediaSource
---
demo/standalone/index.html | 30 +++++++++++++++++++++++-
src/core/api/public_api.ts | 20 ++++++++++++++++
src/core/api/track_choice_manager.ts | 15 ++++++++++++
src/core/buffers/period/period_buffer.ts | 16 ++++++++-----
4 files changed, 74 insertions(+), 7 deletions(-)
diff --git a/demo/standalone/index.html b/demo/standalone/index.html
index 5b74b49076..190f04ad5f 100644
--- a/demo/standalone/index.html
+++ b/demo/standalone/index.html
@@ -6,13 +6,41 @@
RxPlayer - CANAL+ (stand-alone demo)
-
+
+ Disable Video Track
diff --git a/src/core/api/public_api.ts b/src/core/api/public_api.ts
index 5cb92c093f..6099648101 100644
--- a/src/core/api/public_api.ts
+++ b/src/core/api/public_api.ts
@@ -1800,6 +1800,26 @@ class Player extends EventEmitter {
return this._priv_preferredTextTracks.next(tracks);
}
+ setPreferredVideoTracks() : void {
+ console.warn("setPreferredVideoTracks");
+ }
+
+ disableVideoTrack() : void {
+ console.warn("CONTENTINFOS", this._priv_contentInfos)
+ if (this._priv_contentInfos === null) {
+ return;
+ }
+ const { currentPeriod } = this._priv_contentInfos;
+ if (this._priv_trackChoiceManager === null || currentPeriod === null) {
+ console.warn("POLOO")
+ return;
+ }
+ console.warn(currentPeriod)
+ return this._priv_trackChoiceManager.disableVideoTrack(currentPeriod);
+ // return this._priv_trackChoiceManager.disableTextTrack(currentPeriod);
+ // console.warn("disableVideoTrack");
+ }
+
/**
* @returns {Array.|null}
* @deprecated
diff --git a/src/core/api/track_choice_manager.ts b/src/core/api/track_choice_manager.ts
index 54e09a1fb0..4e90dfe735 100644
--- a/src/core/api/track_choice_manager.ts
+++ b/src/core/api/track_choice_manager.ts
@@ -498,6 +498,21 @@ export default class TrackChoiceManager {
textInfos.adaptation$.next(null);
}
+ public disableVideoTrack(period : Period) : void {
+ const periodItem = getPeriodItem(this._periods, period);
+ console.warn("ITEM", periodItem);
+ const videoInfos = periodItem && periodItem.video;
+ if (!videoInfos) {
+ throw new Error("TrackManager: Given Period not found.");
+ }
+ const chosenVideoAdaptation = this._videoChoiceMemory.get(period);
+ if (chosenVideoAdaptation === null) {
+ return;
+ }
+ this._videoChoiceMemory.set(period, null);
+ videoInfos.adaptation$.next(null);
+ }
+
/**
* Returns an object describing the chosen audio track for the given audio
* Period.
diff --git a/src/core/buffers/period/period_buffer.ts b/src/core/buffers/period/period_buffer.ts
index 979045e7e7..1cab20c5d1 100644
--- a/src/core/buffers/period/period_buffer.ts
+++ b/src/core/buffers/period/period_buffer.ts
@@ -120,12 +120,16 @@ export default function PeriodBuffer({
let cleanBuffer$ : Observable;
if (sourceBufferStatus.type === "set") {
- log.info(`Buffer: Clearing previous ${bufferType} SourceBuffer`);
- const previousQSourceBuffer = sourceBufferStatus.value;
- cleanBuffer$ = previousQSourceBuffer
- .removeBuffer(period.start,
- period.end == null ? Infinity :
- period.end);
+ // log.info(`Buffer: Clearing previous ${bufferType} SourceBuffer`);
+ // const previousQSourceBuffer = sourceBufferStatus.value;
+ // cleanBuffer$ = previousQSourceBuffer
+ // .removeBuffer(period.start,
+ // period.end == null ? Infinity :
+ // period.end);
+ return observableOf(EVENTS.needsMediaSourceReload({
+ currentTime: 20,
+ isPaused: false,
+ }));
} else {
if (sourceBufferStatus.type === "unset") {
sourceBuffersStore.disableSourceBuffer(bufferType);
From 35fe8ecff7dfa85f05ff3688e7c8f9fe42b786a0 Mon Sep 17 00:00:00 2001
From: Paul Rosset
Date: Tue, 1 Oct 2019 19:48:20 +0200
Subject: [PATCH 09/72] feat(audio-only): Possible to play a content in audio
only while playing
---
demo/standalone/index.html | 19 +++++-----
src/core/api/public_api.ts | 20 ++++++-----
src/core/api/track_choice_manager.ts | 4 +--
.../buffers/adaptation/adaptation_buffer.ts | 7 ++--
src/core/buffers/events_generators.ts | 8 +++--
src/core/buffers/period/period_buffer.ts | 36 +++++++++++++------
src/core/buffers/types.ts | 3 +-
src/core/init/load_on_media_source.ts | 4 +++
8 files changed, 64 insertions(+), 37 deletions(-)
diff --git a/demo/standalone/index.html b/demo/standalone/index.html
index 190f04ad5f..ffc09148c9 100644
--- a/demo/standalone/index.html
+++ b/demo/standalone/index.html
@@ -6,12 +6,13 @@
RxPlayer - CANAL+ (stand-alone demo)
-
+
Disable Video Track
+ Enable Video Track
From a0feba2e69861175facdb9e4c040f25122dd65c4 Mon Sep 17 00:00:00 2001
From: Paul Rosset
Date: Fri, 4 Oct 2019 15:18:27 +0200
Subject: [PATCH 12/72] feat(audio-only): adjust naming and add doc
---
doc/api/index.md | 23 +++++++++++++++++++++++
src/core/api/public_api.ts | 2 +-
src/core/api/track_choice_manager.ts | 1 -
3 files changed, 24 insertions(+), 2 deletions(-)
diff --git a/doc/api/index.md b/doc/api/index.md
index bb4e5adb8c..9490048c96 100644
--- a/doc/api/index.md
+++ b/doc/api/index.md
@@ -63,6 +63,8 @@
- [getPreferredAudioTracks](#meth-getPreferredAudioTracks)
- [setPreferredTextTracks](#meth-setPreferredTextTracks)
- [getPreferredTextTracks](#meth-getPreferredTextTracks)
+ - [disableVideoTrack](#meth-disableVideoTrack)
+ - [disableAudioOnly](#meth-disableAudioOnly)
- [getCurrentAdaptations](#meth-getCurrentAdaptations)
- [getCurrentRepresentations](#meth-getCurrentRepresentations)
- [dispose](#meth-dispose)
@@ -1550,6 +1552,27 @@ it was called:
}
```
+
+### disableVideoTrack #####################################################
+
+_return value_: ``void``
+
+Disable the video track adaptation.
+
+This permits to play in **Audio only** mode.
+
+> Will enter in `RELOADING` state for a short period.
+
+
+### disableAudioOnly #####################################################
+
+_return value_: ``void``
+
+Disable the **Audio only** mode, by switching from audio adaptation only to video/audio adaptations.
+
+This permits to play in **normal** mode with video.
+
+> Will enter in `RELOADING` state for a short period.
### getManifest ################################################################
diff --git a/src/core/api/public_api.ts b/src/core/api/public_api.ts
index e1f410992a..cfbaa44d09 100644
--- a/src/core/api/public_api.ts
+++ b/src/core/api/public_api.ts
@@ -1811,7 +1811,7 @@ class Player extends EventEmitter {
return this._priv_trackChoiceManager.disableVideoTrack(currentPeriod);
}
- enableVideoTrack() {
+ disableAudioOnly() : void {
if (!this._priv_contentInfos) {
return;
}
diff --git a/src/core/api/track_choice_manager.ts b/src/core/api/track_choice_manager.ts
index 8569492503..e67c6cd93e 100644
--- a/src/core/api/track_choice_manager.ts
+++ b/src/core/api/track_choice_manager.ts
@@ -500,7 +500,6 @@ export default class TrackChoiceManager {
public disableVideoTrack(period : Period) : void {
const periodItem = getPeriodItem(this._periods, period);
- console.warn("ITEM", periodItem);
const videoInfos = periodItem && periodItem.video;
if (!videoInfos) {
throw new Error("TrackManager: Given Period not found.");
From ed0a6972e99fb83b3eca709b20c69cd6797c5072 Mon Sep 17 00:00:00 2001
From: grenault73
Date: Thu, 2 Apr 2020 18:40:04 +0200
Subject: [PATCH 13/72] misc: make disapear the 'audio only' notion
---
doc/api/index.md | 15 +-------------
src/core/api/public_api.ts | 11 ----------
src/core/api/track_choice_manager.ts | 10 +++++-----
.../buffers/adaptation/adaptation_buffer.ts | 6 +-----
src/core/buffers/events_generators.ts | 8 +++-----
src/core/buffers/period/period_buffer.ts | 20 ++++---------------
src/core/buffers/types.ts | 3 +--
7 files changed, 15 insertions(+), 58 deletions(-)
diff --git a/doc/api/index.md b/doc/api/index.md
index 9490048c96..ca768fb880 100644
--- a/doc/api/index.md
+++ b/doc/api/index.md
@@ -1557,20 +1557,7 @@ it was called:
_return value_: ``void``
-Disable the video track adaptation.
-
-This permits to play in **Audio only** mode.
-
-> Will enter in `RELOADING` state for a short period.
-
-
-### disableAudioOnly #####################################################
-
-_return value_: ``void``
-
-Disable the **Audio only** mode, by switching from audio adaptation only to video/audio adaptations.
-
-This permits to play in **normal** mode with video.
+Deactivate the current text track, if one.
> Will enter in `RELOADING` state for a short period.
diff --git a/src/core/api/public_api.ts b/src/core/api/public_api.ts
index cfbaa44d09..8608f430e0 100644
--- a/src/core/api/public_api.ts
+++ b/src/core/api/public_api.ts
@@ -1811,17 +1811,6 @@ class Player extends EventEmitter {
return this._priv_trackChoiceManager.disableVideoTrack(currentPeriod);
}
- disableAudioOnly() : void {
- if (!this._priv_contentInfos) {
- return;
- }
- const { currentPeriod } = this._priv_contentInfos;
- if (this._priv_trackChoiceManager === null || currentPeriod === null) {
- return;
- }
- return this._priv_trackChoiceManager.setInitialVideoTrack(currentPeriod, true);
- }
-
/**
* @returns {Array.|null}
* @deprecated
diff --git a/src/core/api/track_choice_manager.ts b/src/core/api/track_choice_manager.ts
index e67c6cd93e..f584fc465e 100644
--- a/src/core/api/track_choice_manager.ts
+++ b/src/core/api/track_choice_manager.ts
@@ -361,7 +361,7 @@ export default class TrackChoiceManager {
* - the last choice for this period, if one
* @param {Period} period - The concerned Period.
*/
- public setInitialVideoTrack(period : Period, isAudioOnly = false) : void {
+ public setInitialVideoTrack(period : Period) : void {
const periodItem = getPeriodItem(this._periods, period);
const videoInfos = periodItem != null ? periodItem.video :
null;
@@ -374,11 +374,11 @@ export default class TrackChoiceManager {
period.adaptations.video;
const chosenVideoAdaptation = this._videoChoiceMemory.get(period);
- if (chosenVideoAdaptation === null && !isAudioOnly) {
+ if (chosenVideoAdaptation === null) {
// If the Period was previously without video, keep it that way
videoInfos.adaptation$.next(null);
} else if (chosenVideoAdaptation === undefined ||
- !arrayIncludes(videoAdaptations, chosenVideoAdaptation)
+ !arrayIncludes(videoAdaptations, chosenVideoAdaptation)
) {
const optimalAdaptation = videoAdaptations[0];
this._videoChoiceMemory.set(period, optimalAdaptation);
@@ -500,8 +500,8 @@ export default class TrackChoiceManager {
public disableVideoTrack(period : Period) : void {
const periodItem = getPeriodItem(this._periods, period);
- const videoInfos = periodItem && periodItem.video;
- if (!videoInfos) {
+ const videoInfos = periodItem?.video;
+ if (videoInfos === undefined) {
throw new Error("TrackManager: Given Period not found.");
}
const chosenVideoAdaptation = this._videoChoiceMemory.get(period);
diff --git a/src/core/buffers/adaptation/adaptation_buffer.ts b/src/core/buffers/adaptation/adaptation_buffer.ts
index a2c4e760c2..4e65ef48ca 100644
--- a/src/core/buffers/adaptation/adaptation_buffer.ts
+++ b/src/core/buffers/adaptation/adaptation_buffer.ts
@@ -196,11 +196,7 @@ export default function AdaptationBuffer({
// To do that properly, we need to reload the MediaSource
if (directManualBitrateSwitching && estimate.manual && i !== 0) {
return clock$.pipe(take(1),
- map(t => EVENTS.needsMediaSourceReload({
- currentTime: t.currentTime,
- isPaused: t.isPaused,
- isAudioOnly: false,
- })));
+ map(t => EVENTS.needsMediaSourceReload(t)));
}
const representationChange$ =
diff --git a/src/core/buffers/events_generators.ts b/src/core/buffers/events_generators.ts
index 05799f1a51..1432e0a793 100644
--- a/src/core/buffers/events_generators.ts
+++ b/src/core/buffers/events_generators.ts
@@ -125,13 +125,11 @@ const EVENTS = {
needsMediaSourceReload(
{ currentTime,
- isPaused,
- isAudioOnly } : { currentTime : number;
- isPaused : boolean,
- isAudioOnly : boolean }
+ isPaused } : { currentTime : number;
+ isPaused : boolean; }
) : INeedsMediaSourceReload {
return { type: "needs-media-source-reload",
- value: { currentTime, isPaused, isAudioOnly } };
+ value: { currentTime, isPaused } };
},
needsDecipherabilityFlush(
diff --git a/src/core/buffers/period/period_buffer.ts b/src/core/buffers/period/period_buffer.ts
index fe89fab7d2..7bbe685446 100644
--- a/src/core/buffers/period/period_buffer.ts
+++ b/src/core/buffers/period/period_buffer.ts
@@ -125,11 +125,7 @@ export default function PeriodBuffer({
.removeBuffer(period.start,
period.end == null ? Infinity :
period.end);
- return clock$.pipe(map(tick => EVENTS.needsMediaSourceReload({
- currentTime: tick.currentTime,
- isPaused: tick.isPaused,
- isAudioOnly: true,
- })));
+ return clock$.pipe(map(tick => EVENTS.needsMediaSourceReload(tick)));
} else {
if (sourceBufferStatus.type === "unset") {
sourceBuffersStore.disableSourceBuffer(bufferType);
@@ -144,13 +140,9 @@ export default function PeriodBuffer({
}
// Check if we are in AudioOnly mode, if yes, revert to normal mode
- const videoStatus = sourceBuffersStore.getStatus('video');
+ const videoStatus = sourceBuffersStore.getStatus("video");
if (videoStatus.type === "disabled" && adaptation.type === "video") {
- return clock$.pipe(map(tick => EVENTS.needsMediaSourceReload({
- currentTime: tick.currentTime,
- isPaused: tick.isPaused,
- isAudioOnly: false,
- })));
+ return clock$.pipe(map(tick => EVENTS.needsMediaSourceReload(tick)));
}
log.info(`Buffer: Updating ${bufferType} adaptation`, adaptation, period);
@@ -167,11 +159,7 @@ export default function PeriodBuffer({
adaptation,
tick);
if (strategy.type === "needs-reload") {
- return observableOf(EVENTS.needsMediaSourceReload({
- currentTime: tick.currentTime,
- isPaused: tick.isPaused,
- isAudioOnly: false,
- }));
+ return observableOf(EVENTS.needsMediaSourceReload(tick));
}
const cleanBuffer$ = strategy.type === "clean-buffer" ?
diff --git a/src/core/buffers/types.ts b/src/core/buffers/types.ts
index bed8c31080..66af9009e2 100644
--- a/src/core/buffers/types.ts
+++ b/src/core/buffers/types.ts
@@ -157,8 +157,7 @@ export interface ICompletedBufferEvent { type: "complete-buffer";
// The MediaSource needs to be reloaded to continue
export interface INeedsMediaSourceReload { type: "needs-media-source-reload";
value: { currentTime : number;
- isPaused : boolean;
- isAudioOnly : boolean }; }
+ isPaused : boolean; }; }
// Emitted after the buffers have been cleaned due to an update of the
// decipherability status of some segment.
From f3dcbcbf18134a124c080e2825bd698dcffc36ea Mon Sep 17 00:00:00 2001
From: grenault73
Date: Fri, 10 Apr 2020 18:03:02 +0200
Subject: [PATCH 14/72] core: better null adaptation handling in period buffer
---
doc/api/index.md | 21 ++++++++--------
src/core/api/public_api.ts | 25 +++++++++++--------
src/core/api/track_choice_manager.ts | 5 ++++
.../buffers/adaptation/adaptation_buffer.ts | 1 +
src/core/buffers/period/period_buffer.ts | 10 +++++---
5 files changed, 37 insertions(+), 25 deletions(-)
diff --git a/doc/api/index.md b/doc/api/index.md
index ca768fb880..0af8017c73 100644
--- a/doc/api/index.md
+++ b/doc/api/index.md
@@ -59,12 +59,11 @@
- [setTextTrack](#meth-setTextTrack)
- [disableTextTrack](#meth-disableTextTrack)
- [setVideoTrack](#meth-setVideoTrack)
+ - [disableVideoTrack](#meth-disableVideoTrack)
- [setPreferredAudioTracks](#meth-setPreferredAudioTracks)
- [getPreferredAudioTracks](#meth-getPreferredAudioTracks)
- [setPreferredTextTracks](#meth-setPreferredTextTracks)
- [getPreferredTextTracks](#meth-getPreferredTextTracks)
- - [disableVideoTrack](#meth-disableVideoTrack)
- - [disableAudioOnly](#meth-disableAudioOnly)
- [getCurrentAdaptations](#meth-getCurrentAdaptations)
- [getCurrentRepresentations](#meth-getCurrentRepresentations)
- [dispose](#meth-dispose)
@@ -1323,6 +1322,16 @@ During this period of time:
---
+
+### disableVideoTrack #####################################################
+
+_return value_: ``void``
+
+Deactivate the current video track, if one.
+
+> Will enter in `RELOADING` state for a short period.
+
+
### setPreferredAudioTracks ####################################################
@@ -1552,14 +1561,6 @@ it was called:
}
```
-
-### disableVideoTrack #####################################################
-
-_return value_: ``void``
-
-Deactivate the current text track, if one.
-
-> Will enter in `RELOADING` state for a short period.
### getManifest ################################################################
diff --git a/src/core/api/public_api.ts b/src/core/api/public_api.ts
index 8608f430e0..c5dd9f2fe6 100644
--- a/src/core/api/public_api.ts
+++ b/src/core/api/public_api.ts
@@ -1760,6 +1760,20 @@ class Player extends EventEmitter {
}
}
+ /**
+ * Disable video track for the current content.
+ */
+ disableVideoTrack() : void {
+ if (this._priv_contentInfos === null) {
+ return;
+ }
+ const { currentPeriod } = this._priv_contentInfos;
+ if (this._priv_trackChoiceManager === null || currentPeriod === null) {
+ return;
+ }
+ return this._priv_trackChoiceManager.disableVideoTrack(currentPeriod);
+ }
+
/**
* Returns the current list of preferred audio tracks, in preference order.
* @returns {Array.}
@@ -1800,17 +1814,6 @@ class Player extends EventEmitter {
return this._priv_preferredTextTracks.next(tracks);
}
- disableVideoTrack() : void {
- if (this._priv_contentInfos === null) {
- return;
- }
- const { currentPeriod } = this._priv_contentInfos;
- if (this._priv_trackChoiceManager === null || currentPeriod === null) {
- return;
- }
- return this._priv_trackChoiceManager.disableVideoTrack(currentPeriod);
- }
-
/**
* @returns {Array.|null}
* @deprecated
diff --git a/src/core/api/track_choice_manager.ts b/src/core/api/track_choice_manager.ts
index f584fc465e..2ebd90c9b9 100644
--- a/src/core/api/track_choice_manager.ts
+++ b/src/core/api/track_choice_manager.ts
@@ -498,6 +498,11 @@ export default class TrackChoiceManager {
textInfos.adaptation$.next(null);
}
+ /**
+ * Disable the current video track for a given period.
+ * @param {Object} period
+ * @throws Error - Throws if the period given has not been added
+ */
public disableVideoTrack(period : Period) : void {
const periodItem = getPeriodItem(this._periods, period);
const videoInfos = periodItem?.video;
diff --git a/src/core/buffers/adaptation/adaptation_buffer.ts b/src/core/buffers/adaptation/adaptation_buffer.ts
index 4e65ef48ca..c87127fee2 100644
--- a/src/core/buffers/adaptation/adaptation_buffer.ts
+++ b/src/core/buffers/adaptation/adaptation_buffer.ts
@@ -124,6 +124,7 @@ export default function AdaptationBuffer({
} : IAdaptationBufferArguments) : Observable> {
const directManualBitrateSwitching = options.manualBitrateSwitchingMode === "direct";
const { manifest, period, adaptation } = content;
+
// The buffer goal ratio limits the wanted buffer ahead to determine the
// buffer goal.
//
diff --git a/src/core/buffers/period/period_buffer.ts b/src/core/buffers/period/period_buffer.ts
index 7bbe685446..7f2936d0f3 100644
--- a/src/core/buffers/period/period_buffer.ts
+++ b/src/core/buffers/period/period_buffer.ts
@@ -125,7 +125,9 @@ export default function PeriodBuffer({
.removeBuffer(period.start,
period.end == null ? Infinity :
period.end);
- return clock$.pipe(map(tick => EVENTS.needsMediaSourceReload(tick)));
+ if (SourceBuffersStore.isNative(bufferType)) {
+ return clock$.pipe(map(tick => EVENTS.needsMediaSourceReload(tick)));
+ }
} else {
if (sourceBufferStatus.type === "unset") {
sourceBuffersStore.disableSourceBuffer(bufferType);
@@ -139,9 +141,9 @@ export default function PeriodBuffer({
);
}
- // Check if we are in AudioOnly mode, if yes, revert to normal mode
- const videoStatus = sourceBuffersStore.getStatus("video");
- if (videoStatus.type === "disabled" && adaptation.type === "video") {
+ if (SourceBuffersStore.isNative(bufferType) &&
+ sourceBuffersStore.getStatus(bufferType).type === "disabled")
+ {
return clock$.pipe(map(tick => EVENTS.needsMediaSourceReload(tick)));
}
From 3a1040f52abd4fdaab2c251181d0ddea5ed262d0 Mon Sep 17 00:00:00 2001
From: grenault73
Date: Tue, 14 Apr 2020 17:12:10 +0200
Subject: [PATCH 15/72] api: media element video track disabling
---
doc/api/index.md | 13 ++++++++++++-
doc/api/player_events.md | 4 +++-
src/core/api/media_element_track_choice_manager.ts | 10 ++++++++++
src/core/api/public_api.ts | 5 ++++-
4 files changed, 29 insertions(+), 3 deletions(-)
diff --git a/doc/api/index.md b/doc/api/index.md
index 0af8017c73..3a1309e377 100644
--- a/doc/api/index.md
+++ b/doc/api/index.md
@@ -1329,7 +1329,18 @@ _return value_: ``void``
Deactivate the current video track, if one.
-> Will enter in `RELOADING` state for a short period.
+Will enter in `RELOADING` state for a short period, if not in _DirectFile_
+
+---
+
+:warning: This option will have no effect in _DirectFile_ mode
+(see [loadVideo options](./loadVideo_options.md#prop-transport)) when either :
+- No audio track API was supported on the current browser
+- The media file tracks are not supported on the browser
+
+/!\ On Safari browser in _DirectFile_, video playback may continue on same track
+even if video tracks are disabled.
+---
diff --git a/doc/api/player_events.md b/doc/api/player_events.md
index 1e06e8b32b..6cabf1252c 100644
--- a/doc/api/player_events.md
+++ b/doc/api/player_events.md
@@ -326,7 +326,9 @@ properties:
- ``frameRate`` (``string|undefined``): The video framerate.
-
+A null payload means that video track has been disabled.
+/!\ On Safari in _DirectFile_, a video track may have been disabled by user but
+still playing on screen.
### availableAudioBitratesChange ###############################################
diff --git a/src/core/api/media_element_track_choice_manager.ts b/src/core/api/media_element_track_choice_manager.ts
index 88b5a54870..c4244b962a 100644
--- a/src/core/api/media_element_track_choice_manager.ts
+++ b/src/core/api/media_element_track_choice_manager.ts
@@ -285,6 +285,16 @@ export default class MediaElementTrackChoiceManager
}
}
+ /**
+ * Disable the currently-active video track, if one.
+ */
+ public disableVideoTrack(): void {
+ for (let i = 0; i < this._videoTracks.length; i++) {
+ const { nativeTrack } = this._videoTracks[i];
+ nativeTrack.selected = false;
+ }
+ }
+
/**
* Update the currently active video track by setting the wanted video track's
* ID property.
diff --git a/src/core/api/public_api.ts b/src/core/api/public_api.ts
index c5dd9f2fe6..c57075cf88 100644
--- a/src/core/api/public_api.ts
+++ b/src/core/api/public_api.ts
@@ -1767,7 +1767,10 @@ class Player extends EventEmitter {
if (this._priv_contentInfos === null) {
return;
}
- const { currentPeriod } = this._priv_contentInfos;
+ const { currentPeriod, isDirectFile } = this._priv_contentInfos;
+ if (isDirectFile && this._priv_mediaElementTrackChoiceManager !== null) {
+ return this._priv_mediaElementTrackChoiceManager.disableVideoTrack();
+ }
if (this._priv_trackChoiceManager === null || currentPeriod === null) {
return;
}
From 1a75165452455c459589713a4804a5527ab49671 Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Thu, 2 Apr 2020 18:45:21 +0200
Subject: [PATCH 16/72] manifest: add the concept of playable Adaptation and
Representation and avoid removing unsupported codecs from the Manifest
structure
---
src/core/api/track_choice_manager.ts | 29 +-
.../buffers/adaptation/adaptation_buffer.ts | 7 +-
src/manifest/__tests__/adaptation.test.ts | 564 +++++++++---------
.../filter_supported_representations.test.ts | 140 -----
src/manifest/__tests__/representation.test.ts | 256 +++++---
src/manifest/adaptation.ts | 97 +--
.../filter_supported_representations.ts | 46 --
src/manifest/index.ts | 2 +-
src/manifest/manifest.ts | 6 +-
src/manifest/period.ts | 17 +-
src/manifest/representation.ts | 13 +-
src/manifest/types.ts | 3 +
.../initialization_segment_cache.test.ts | 2 +
13 files changed, 573 insertions(+), 609 deletions(-)
delete mode 100644 src/manifest/__tests__/filter_supported_representations.test.ts
delete mode 100644 src/manifest/filter_supported_representations.ts
diff --git a/src/core/api/track_choice_manager.ts b/src/core/api/track_choice_manager.ts
index ecbf63212a..8c3039b21e 100644
--- a/src/core/api/track_choice_manager.ts
+++ b/src/core/api/track_choice_manager.ts
@@ -230,10 +230,7 @@ export default class TrackChoiceManager {
adaptation$ : Subject
) : void {
const periodItem = getPeriodItem(this._periods, period);
- let adaptations = period.adaptations[bufferType];
- if (adaptations == null) {
- adaptations = [];
- }
+ const adaptations = period.getPlayableAdaptations(bufferType);
if (periodItem != null) {
if (periodItem[bufferType] != null) {
log.warn(`TrackChoiceManager: ${bufferType} already added for period`, period);
@@ -309,9 +306,7 @@ export default class TrackChoiceManager {
}
const preferredAudioTracks = this._preferredAudioTracks.getValue();
- const audioAdaptations = period.adaptations.audio === undefined ?
- [] :
- period.adaptations.audio;
+ const audioAdaptations = period.getPlayableAdaptations("audio");
const chosenAudioAdaptation = this._audioChoiceMemory.get(period);
if (chosenAudioAdaptation === null) {
@@ -347,9 +342,7 @@ export default class TrackChoiceManager {
}
const preferredTextTracks = this._preferredTextTracks.getValue();
- const textAdaptations = period.adaptations.text === undefined ?
- [] :
- period.adaptations.text;
+ const textAdaptations = period.getPlayableAdaptations("text");
const chosenTextAdaptation = this._textChoiceMemory.get(period);
if (chosenTextAdaptation === null) {
// If the Period was previously without text, keep it that way
@@ -383,9 +376,7 @@ export default class TrackChoiceManager {
}
const preferredVideoTracks = this._preferredVideoTracks.getValue();
- const videoAdaptations = period.adaptations.video === undefined ?
- [] :
- period.adaptations.video;
+ const videoAdaptations = period.getPlayableAdaptations("video");
const chosenVideoAdaptation = this._videoChoiceMemory.get(period);
if (chosenVideoAdaptation === null) {
@@ -745,9 +736,7 @@ export default class TrackChoiceManager {
const { period,
audio: audioItem } = periodItem;
- const audioAdaptations = period.adaptations.audio === undefined ?
- [] :
- period.adaptations.audio;
+ const audioAdaptations = period.getPlayableAdaptations("audio");
const chosenAudioAdaptation = this._audioChoiceMemory.get(period);
if (chosenAudioAdaptation === null ||
@@ -793,9 +782,7 @@ export default class TrackChoiceManager {
const { period,
text: textItem } = periodItem;
- const textAdaptations = period.adaptations.text === undefined ?
- [] :
- period.adaptations.text;
+ const textAdaptations = period.getPlayableAdaptations("text");
const chosenTextAdaptation = this._textChoiceMemory.get(period);
if (chosenTextAdaptation === null ||
@@ -838,9 +825,7 @@ export default class TrackChoiceManager {
}
const { period, video: videoItem, } = periodItem;
- const videoAdaptations = period.adaptations.video === undefined ?
- [] :
- period.adaptations.video;
+ const videoAdaptations = period.getPlayableAdaptations("video");
const chosenVideoAdaptation = this._videoChoiceMemory.get(period);
if (chosenVideoAdaptation === null ||
diff --git a/src/core/buffers/adaptation/adaptation_buffer.ts b/src/core/buffers/adaptation/adaptation_buffer.ts
index de0a5d2da5..f5e25a18b1 100644
--- a/src/core/buffers/adaptation/adaptation_buffer.ts
+++ b/src/core/buffers/adaptation/adaptation_buffer.ts
@@ -185,10 +185,9 @@ export default function AdaptationBuffer({
const requestsEvents$ = new Subject();
const abrEvents$ = observableMerge(bufferEvents$, requestsEvents$);
- const decipherableRepresentations = adaptation.representations
- .filter((representation) => representation.decipherable !== false);
+ const playableRepresentations = adaptation.getPlayableRepresentations();
- if (decipherableRepresentations.length <= 0) {
+ if (playableRepresentations.length <= 0) {
const noRepErr = new MediaError("NO_PLAYABLE_REPRESENTATION",
"No Representation in the chosen " +
"Adaptation can be played");
@@ -196,7 +195,7 @@ export default function AdaptationBuffer({
}
const abr$ : Observable = abrManager.get$(adaptation.type,
- decipherableRepresentations,
+ playableRepresentations,
clock$,
abrEvents$)
.pipe(deferSubscriptions(), share());
diff --git a/src/manifest/__tests__/adaptation.test.ts b/src/manifest/__tests__/adaptation.test.ts
index 3ed3c6f639..a3302bb905 100644
--- a/src/manifest/__tests__/adaptation.test.ts
+++ b/src/manifest/__tests__/adaptation.test.ts
@@ -19,39 +19,38 @@ import {
} from "../adaptation";
import Representation from "../representation";
-const minimalRepresentationIndex = {
- getInitSegment() { return null; },
- getSegments() { return []; },
- shouldRefresh() { return false; },
- getFirstPosition() : undefined { return ; },
- getLastPosition() : undefined { return ; },
- checkDiscontinuity() { return -1; },
- isSegmentStillAvailable() : undefined { return ; },
- canBeOutOfSyncError() : true { return true; },
- isFinished() : true { return true; },
- _replace() { /* noop */ },
- _update() { /* noop */ },
- _addSegments() { /* noop */ },
-};
-
/* tslint:disable no-unsafe-any */
+
+const minimalRepresentationIndex = { getInitSegment() { return null; },
+ getSegments() { return []; },
+ shouldRefresh() { return false; },
+ getFirstPosition() : undefined { return ; },
+ getLastPosition() : undefined { return ; },
+ checkDiscontinuity() { return -1; },
+ isSegmentStillAvailable() : undefined { return ; },
+ canBeOutOfSyncError() : true { return true; },
+ isFinished() : true { return true; },
+ _replace() { /* noop */ },
+ _update() { /* noop */ },
+ _addSegments() { /* noop */ } };
+const defaultRepresentationSpy = jest.fn(arg => {
+ return { bitrate: arg.bitrate,
+ id: arg.id,
+ isSupported: true,
+ index: arg.index };
+});
+
describe("Manifest - Adaptation", () => {
beforeEach(() => {
jest.resetModules();
});
+ afterEach(() => {
+ defaultRepresentationSpy.mockClear();
+ });
it("should be able to create a minimal Adaptation", () => {
- const representationSpy = jest.fn(arg => arg);
- const filterSpy = jest.fn((_type, arg) => arg);
-
- jest.mock("../representation", () => ({
- __esModule: true,
- default: representationSpy,
- }));
- jest.mock("../filter_supported_representations", () => ({
- __esModule: true,
- default: filterSpy,
- }));
+ jest.mock("../representation", () => ({ __esModule: true,
+ default: defaultRepresentationSpy }));
const Adaptation = require("../adaptation").default;
const args = { id: "12", representations: [], type: "video" };
@@ -68,18 +67,17 @@ describe("Manifest - Adaptation", () => {
expect(adaptation.getAvailableBitrates()).toEqual([]);
expect(adaptation.getRepresentation("")).toBe(undefined);
- expect(representationSpy).not.toHaveBeenCalled();
+ expect(defaultRepresentationSpy).not.toHaveBeenCalled();
});
it("should throw if the given adaptation type is not supported", () => {
- const representationSpy = jest.fn(arg => arg);
- const filterSpy = jest.fn((_type, arg) => arg);
-
- jest.mock("../representation", () => ({ default: representationSpy }));
- jest.mock("../filter_supported_representations", () => ({ default: filterSpy }));
+ jest.mock("../representation", () => ({ __esModule: true,
+ default: defaultRepresentationSpy }));
const Adaptation = require("../adaptation").default;
- const args = { id: "12", representations: [], type: "foo" };
+ const args = { id: "12",
+ representations: [],
+ type: "foo" };
let adaptation = null;
let error = null;
try {
@@ -94,30 +92,17 @@ describe("Manifest - Adaptation", () => {
});
it("should normalize a given language", () => {
- const representationSpy = jest.fn(arg => arg);
- const filterSpy = jest.fn((_type, arg) => arg);
+ jest.mock("../representation", () => ({ __esModule: true,
+ default: defaultRepresentationSpy }));
const normalizeSpy = jest.fn((lang : string) => lang + "foo");
-
- jest.mock("../representation", () => ({
- __esModule: true,
- default: representationSpy,
- }));
- jest.mock("../filter_supported_representations", () => ({
- __esModule: true,
- default: filterSpy,
- }));
- jest.mock("../../utils/languages", () => ({
- __esModule: true,
- default: normalizeSpy,
- }));
+ jest.mock("../../utils/languages", () => ({ __esModule: true,
+ default: normalizeSpy }));
const Adaptation = require("../adaptation").default;
- const args1 = {
- id: "12",
- representations: [],
- language: "fr",
- type: "video"as "video",
- };
+ const args1 = { id: "12",
+ representations: [],
+ language: "fr",
+ type: "video"as "video" };
const adaptation1 = new Adaptation(args1);
expect(adaptation1.language).toBe("fr");
expect(adaptation1.normalizedLanguage).toBe("frfoo");
@@ -125,12 +110,10 @@ describe("Manifest - Adaptation", () => {
expect(normalizeSpy).toHaveBeenCalledWith("fr");
normalizeSpy.mockClear();
- const args2 = {
- id: "12",
- representations: [],
- language: "toto",
- type: "video",
- };
+ const args2 = { id: "12",
+ representations: [],
+ language: "toto",
+ type: "video" };
const adaptation2 = new Adaptation(args2);
expect(adaptation2.language).toBe("toto");
expect(adaptation2.normalizedLanguage).toBe("totofoo");
@@ -139,29 +122,16 @@ describe("Manifest - Adaptation", () => {
});
it("should not call normalize if no language is given", () => {
- const representationSpy = jest.fn(arg => arg);
- const filterSpy = jest.fn((_type, arg) => arg);
+ jest.mock("../representation", () => ({ __esModule: true,
+ default: defaultRepresentationSpy }));
const normalizeSpy = jest.fn((lang : string) => lang + "foo");
-
- jest.mock("../representation", () => ({
- __esModule: true,
- default: representationSpy,
- }));
- jest.mock("../filter_supported_representations", () => ({
- __esModule: true,
- default: filterSpy,
- }));
- jest.mock("../../utils/languages", () => ({
- __esModule: true,
- default: normalizeSpy,
- }));
+ jest.mock("../../utils/languages", () => ({ __esModule: true,
+ default: normalizeSpy }));
const Adaptation = require("../adaptation").default;
- const args1 = {
- id: "12",
- representations: [],
- type: "video",
- };
+ const args1 = { id: "12",
+ representations: [],
+ type: "video" };
const adaptation1 = new Adaptation(args1);
expect(adaptation1.language).toBe(undefined);
expect(adaptation1.normalizedLanguage).toBe(undefined);
@@ -169,64 +139,71 @@ describe("Manifest - Adaptation", () => {
});
it("should create and sort the corresponding Representations", () => {
- const representationSpy = jest.fn(arg => arg);
- const filterSpy = jest.fn((_type, arg) => arg);
-
- jest.mock("../representation", () => ({
- __esModule: true,
- default: representationSpy,
- }));
- jest.mock("../filter_supported_representations", () => ({
- __esModule: true,
- default: filterSpy,
- }));
+ jest.mock("../representation", () => ({ __esModule: true,
+ default: defaultRepresentationSpy }));
const Adaptation = require("../adaptation").default;
- const rep1 = { bitrate: 10, id: "rep1", index: minimalRepresentationIndex };
- const rep2 = { bitrate: 30, id: "rep2", index: minimalRepresentationIndex };
- const rep3 = { bitrate: 20, id: "rep3", index: minimalRepresentationIndex };
+ const rep1 = { bitrate: 10,
+ id: "rep1",
+ index: minimalRepresentationIndex };
+ const rep2 = { bitrate: 30,
+ id: "rep2",
+ index: minimalRepresentationIndex };
+ const rep3 = { bitrate: 20,
+ id: "rep3",
+ index: minimalRepresentationIndex };
const representations = [rep1, rep2, rep3];
- const args = { id: "12", representations, type: "text" as "text" };
+ const args = { id: "12",
+ representations,
+ type: "text" as const };
const adaptation = new Adaptation(args);
const parsedRepresentations = adaptation.representations;
- expect(representationSpy).toHaveBeenCalledTimes(3);
- expect(representationSpy).toHaveBeenNthCalledWith(1, rep1);
- expect(representationSpy).toHaveBeenNthCalledWith(2, rep2);
- expect(representationSpy).toHaveBeenNthCalledWith(3, rep3);
- expect(filterSpy).toHaveReturnedTimes(1);
- expect(filterSpy).toHaveBeenCalledWith("text", representations);
+ expect(defaultRepresentationSpy).toHaveBeenCalledTimes(3);
+ expect(defaultRepresentationSpy).toHaveBeenNthCalledWith(1, rep1, { type: "text" });
+ expect(defaultRepresentationSpy).toHaveBeenNthCalledWith(2, rep2, { type: "text" });
+ expect(defaultRepresentationSpy).toHaveBeenNthCalledWith(3, rep3, { type: "text" });
expect(adaptation.parsingErrors).toEqual([]);
expect(parsedRepresentations.length).toBe(3);
- expect(parsedRepresentations[0]).toEqual(new Representation(rep1));
- expect(parsedRepresentations[1]).toEqual(new Representation(rep3));
- expect(parsedRepresentations[2]).toEqual(new Representation(rep2));
+ expect(parsedRepresentations[0].id).toEqual("rep1");
+ expect(parsedRepresentations[1].id).toEqual("rep3");
+ expect(parsedRepresentations[2].id).toEqual("rep2");
expect(adaptation.getAvailableBitrates()).toEqual([10, 20, 30]);
- expect(adaptation.getRepresentation("rep2")).toEqual(new Representation(rep2));
+ expect(adaptation.getRepresentation("rep2").bitrate).toEqual(30);
});
it("should execute the representationFilter if given", () => {
- const representationSpy = jest.fn(arg => arg);
- const filterSpy = jest.fn((_type, arg) => arg);
-
- jest.mock("../representation", () => ({
- __esModule: true,
- default: representationSpy,
- }));
- jest.mock("../filter_supported_representations", () => ({
- __esModule: true,
- default: filterSpy,
- }));
+ const representationSpy = jest.fn(arg => {
+ return { bitrate: arg.bitrate,
+ id: arg.id,
+ isSupported: arg.id !== "rep4",
+ index: arg.index };
+ });
+
+ jest.mock("../representation", () => ({ __esModule: true,
+ default: representationSpy }));
const Adaptation = require("../adaptation").default;
- const rep1 = { bitrate: 10, id: "rep1", index: minimalRepresentationIndex };
- const rep2 = { bitrate: 20, id: "rep2", index: minimalRepresentationIndex };
- const rep3 = { bitrate: 30, id: "rep3", index: minimalRepresentationIndex };
- const rep4 = { bitrate: 40, id: "rep4", index: minimalRepresentationIndex };
- const rep5 = { bitrate: 50, id: "rep5", index: minimalRepresentationIndex };
- const rep6 = { bitrate: 60, id: "rep6", index: minimalRepresentationIndex };
+ const rep1 = { bitrate: 10,
+ id: "rep1",
+ index: minimalRepresentationIndex };
+ const rep2 = { bitrate: 20,
+ id: "rep2",
+ index: minimalRepresentationIndex };
+ const rep3 = { bitrate: 30,
+ id: "rep3",
+ index: minimalRepresentationIndex };
+ const rep4 = { bitrate: 40,
+ id: "rep4",
+ index: minimalRepresentationIndex };
+ const rep5 = { bitrate: 50,
+ id: "rep5",
+ index: minimalRepresentationIndex };
+ const rep6 = { bitrate: 60,
+ id: "rep6",
+ index: minimalRepresentationIndex };
const representations = [rep1, rep2, rep3, rep4, rep5, rep6];
const representationFilter = jest.fn((
@@ -238,7 +215,10 @@ describe("Manifest - Adaptation", () => {
}
return true;
});
- const args = { id: "12", language: "fr", representations, type: "text" as "text" };
+ const args = { id: "12",
+ language: "fr",
+ representations,
+ type: "text" as "text" };
const adaptation = new Adaptation(args, { representationFilter });
const parsedRepresentations = adaptation.representations;
@@ -246,37 +226,39 @@ describe("Manifest - Adaptation", () => {
expect(adaptation.parsingErrors).toEqual([]);
expect(parsedRepresentations.length).toBe(3);
- expect(parsedRepresentations[0]).toEqual(new Representation(rep4));
- expect(parsedRepresentations[1]).toEqual(new Representation(rep5));
- expect(parsedRepresentations[2]).toEqual(new Representation(rep6));
+ expect(parsedRepresentations[0].id).toEqual("rep4");
+ expect(parsedRepresentations[1].id).toEqual("rep5");
+ expect(parsedRepresentations[2].id).toEqual("rep6");
expect(adaptation.getAvailableBitrates()).toEqual([40, 50, 60]);
expect(adaptation.getRepresentation("rep2")).toBe(undefined);
- expect(adaptation.getRepresentation("rep4")).toEqual(new Representation(rep4));
+ expect(adaptation.getRepresentation("rep4").id).toEqual("rep4");
});
it("should set a parsing error if no Representation is supported", () => {
- const representationSpy = jest.fn(arg => arg);
- const filterSpy = jest.fn(() => []);
-
- jest.mock("../representation", () => ({
- __esModule: true,
- default: representationSpy,
- }));
- jest.mock("../filter_supported_representations", () => ({
- __esModule: true,
- default: filterSpy,
- }));
-
+ const representationSpy = jest.fn(arg => {
+ return { bitrate: arg.bitrate,
+ id: arg.id,
+ isSupported: false,
+ index: arg.index };
+ });
+ jest.mock("../representation", () => ({ __esModule: true,
+ default: representationSpy }));
const Adaptation = require("../adaptation").default;
- const rep1 = { bitrate: 10, id: "rep1", index: minimalRepresentationIndex };
- const rep2 = { bitrate: 20, id: "rep2", index: minimalRepresentationIndex };
+ const rep1 = { bitrate: 10,
+ id: "rep1",
+ index: minimalRepresentationIndex };
+ const rep2 = { bitrate: 20,
+ id: "rep2",
+ index: minimalRepresentationIndex };
const representations = [rep1, rep2];
- const args = { id: "12", representations, type: "text" as "text" };
+ const args = { id: "12",
+ representations,
+ type: "text" as "text" };
const adaptation = new Adaptation(args);
const parsedRepresentations = adaptation.representations;
- expect(parsedRepresentations.length).toBe(0);
+ expect(parsedRepresentations.length).toBe(2);
expect(adaptation.parsingErrors).toHaveLength(1);
const error = adaptation.parsingErrors[0];
@@ -285,36 +267,31 @@ describe("Manifest - Adaptation", () => {
});
it("should not set a parsing error if we had no Representation", () => {
- const representationSpy = jest.fn(arg => arg);
- const filterSpy = jest.fn(() => []);
-
- jest.mock("../representation", () => ({
- __esModule: true,
- default: representationSpy,
- }));
- jest.mock("../filter_supported_representations", () => ({
- __esModule: true,
- default: filterSpy,
- }));
+ const representationSpy = jest.fn(arg => {
+ return { bitrate: arg.bitrate,
+ id: arg.id,
+ isSupported: false,
+ index: arg.index };
+ });
+ jest.mock("../representation", () => ({ __esModule: true,
+ default: representationSpy }));
const Adaptation = require("../adaptation").default;
- const args = { id: "12", representations: [], type: "text" as "text" };
+ const args = { id: "12",
+ representations: [],
+ type: "text" as "text" };
const adaptation = new Adaptation(args);
const parsedRepresentations = adaptation.representations;
expect(parsedRepresentations.length).toBe(0);
expect(adaptation.parsingErrors).toEqual([]);
+ expect(representationSpy).not.toHaveBeenCalled();
});
it("should set an isDub value if one", () => {
- const representationSpy = jest.fn(arg => arg);
- const filterSpy = jest.fn((_type, arg) => arg);
- const normalizeSpy = jest.fn((lang : string) => lang + "foo");
-
jest.mock("../representation", () => ({ __esModule: true,
- default: representationSpy }));
- jest.mock("../filter_supported_representations", () => ({ __esModule: true,
- default: filterSpy }));
+ default: defaultRepresentationSpy }));
+ const normalizeSpy = jest.fn((lang : string) => lang + "foo");
jest.mock("../../utils/languages", () => ({ __esModule: true,
default: normalizeSpy }));
@@ -342,43 +319,28 @@ describe("Manifest - Adaptation", () => {
});
it("should set an isClosedCaption value if one", () => {
- const representationSpy = jest.fn(arg => arg);
- const filterSpy = jest.fn((_type, arg) => arg);
+ jest.mock("../representation", () => ({ __esModule: true,
+ default: defaultRepresentationSpy }));
const normalizeSpy = jest.fn((lang : string) => lang + "foo");
-
- jest.mock("../representation", () => ({
- __esModule: true,
- default: representationSpy,
- }));
- jest.mock("../filter_supported_representations", () => ({
- __esModule: true,
- default: filterSpy,
- }));
- jest.mock("../../utils/languages", () => ({
- __esModule: true,
- default: normalizeSpy,
- }));
+ jest.mock("../../utils/languages", () => ({ __esModule: true,
+ default: normalizeSpy }));
const Adaptation = require("../adaptation").default;
- const args1 = {
- id: "12",
- representations: [],
- closedCaption: false,
- type: "video",
- };
+ const args1 = { id: "12",
+ representations: [],
+ closedCaption: false,
+ type: "video" };
const adaptation1 = new Adaptation(args1);
expect(adaptation1.language).toBe(undefined);
expect(adaptation1.normalizedLanguage).toBe(undefined);
expect(adaptation1.isClosedCaption).toEqual(false);
expect(normalizeSpy).not.toHaveBeenCalled();
- const args2 = {
- id: "12",
- representations: [],
- closedCaption: true,
- type: "video",
- };
+ const args2 = { id: "12",
+ representations: [],
+ closedCaption: true,
+ type: "video" };
const adaptation2 = new Adaptation(args2);
expect(adaptation2.language).toBe(undefined);
expect(adaptation2.normalizedLanguage).toBe(undefined);
@@ -387,43 +349,29 @@ describe("Manifest - Adaptation", () => {
});
it("should set an isAudioDescription value if one", () => {
- const representationSpy = jest.fn(arg => arg);
- const filterSpy = jest.fn((_type, arg) => arg);
+ jest.mock("../representation", () => ({ __esModule: true,
+ default: defaultRepresentationSpy }));
const normalizeSpy = jest.fn((lang : string) => lang + "foo");
- jest.mock("../representation", () => ({
- __esModule: true,
- default: representationSpy,
- }));
- jest.mock("../filter_supported_representations", () => ({
- __esModule: true,
- default: filterSpy,
- }));
- jest.mock("../../utils/languages", () => ({
- __esModule: true,
- default: normalizeSpy,
- }));
+ jest.mock("../../utils/languages", () => ({ __esModule: true,
+ default: normalizeSpy }));
const Adaptation = require("../adaptation").default;
- const args1 = {
- id: "12",
- representations: [],
- audioDescription: false,
- type: "video",
- };
+ const args1 = { id: "12",
+ representations: [],
+ audioDescription: false,
+ type: "video" };
const adaptation1 = new Adaptation(args1);
expect(adaptation1.language).toBe(undefined);
expect(adaptation1.normalizedLanguage).toBe(undefined);
expect(adaptation1.isAudioDescription).toEqual(false);
expect(normalizeSpy).not.toHaveBeenCalled();
- const args2 = {
- id: "12",
- representations: [],
- audioDescription: true,
- type: "video",
- };
+ const args2 = { id: "12",
+ representations: [],
+ audioDescription: true,
+ type: "video" };
const adaptation2 = new Adaptation(args2);
expect(adaptation2.language).toBe(undefined);
expect(adaptation2.normalizedLanguage).toBe(undefined);
@@ -432,41 +380,26 @@ describe("Manifest - Adaptation", () => {
});
it("should set a manuallyAdded value if one", () => {
- const representationSpy = jest.fn(arg => arg);
- const filterSpy = jest.fn((_type, arg) => arg);
+ jest.mock("../representation", () => ({ __esModule: true,
+ default: defaultRepresentationSpy }));
const normalizeSpy = jest.fn((lang : string) => lang + "foo");
-
- jest.mock("../representation", () => ({
- __esModule: true,
- default: representationSpy,
- }));
- jest.mock("../filter_supported_representations", () => ({
- __esModule: true,
- default: filterSpy,
- }));
- jest.mock("../../utils/languages", () => ({
- __esModule: true,
- default: normalizeSpy,
- }));
+ jest.mock("../../utils/languages", () => ({ __esModule: true,
+ default: normalizeSpy }));
const Adaptation = require("../adaptation").default;
- const args1 = {
- id: "12",
- representations: [],
- type: "video",
- };
+ const args1 = { id: "12",
+ representations: [],
+ type: "video" };
const adaptation1 = new Adaptation(args1, { isManuallyAdded: false });
expect(adaptation1.language).toBe(undefined);
expect(adaptation1.normalizedLanguage).toBe(undefined);
expect(adaptation1.manuallyAdded).toEqual(false);
expect(normalizeSpy).not.toHaveBeenCalled();
- const args2 = {
- id: "12",
- representations: [],
- type: "video",
- };
+ const args2 = { id: "12",
+ representations: [],
+ type: "video" };
const adaptation2 = new Adaptation(args2, { isManuallyAdded: true });
expect(adaptation2.language).toBe(undefined);
expect(adaptation2.normalizedLanguage).toBe(undefined);
@@ -478,29 +411,26 @@ describe("Manifest - Adaptation", () => {
it("should filter Representation with duplicate bitrates in getAvailableBitrates", () => {
/* tslint:enable:max-line-length */
- const representationSpy = jest.fn(arg => arg);
- const filterSpy = jest.fn((_type, arg) => arg);
+ jest.mock("../representation", () => ({ __esModule: true,
+ default: defaultRepresentationSpy }));
const uniqSpy = jest.fn(() => [45, 92]);
-
- jest.mock("../../utils/uniq", () => ({
- __esModule: true,
- default: uniqSpy,
- }));
- jest.mock("../representation", () => ({
- __esModule: true,
- default: representationSpy,
- }));
- jest.mock("../filter_supported_representations", () => ({
- __esModule: true,
- default: filterSpy,
- }));
+ jest.mock("../../utils/uniq", () => ({ __esModule: true,
+ default: uniqSpy }));
const Adaptation = require("../adaptation").default;
- const rep1 = { bitrate: 10, id: "rep1", index: minimalRepresentationIndex };
- const rep2 = { bitrate: 20, id: "rep2", index: minimalRepresentationIndex };
- const rep3 = { bitrate: 20, id: "rep3", index: minimalRepresentationIndex };
+ const rep1 = { bitrate: 10,
+ id: "rep1",
+ index: minimalRepresentationIndex };
+ const rep2 = { bitrate: 20,
+ id: "rep2",
+ index: minimalRepresentationIndex };
+ const rep3 = { bitrate: 20,
+ id: "rep3",
+ index: minimalRepresentationIndex };
const representations = [rep1, rep2, rep3];
- const args = { id: "12", representations, type: "text" as "text" };
+ const args = { id: "12",
+ representations,
+ type: "text" as "text" };
const adaptation = new Adaptation(args);
const parsedRepresentations = adaptation.representations;
@@ -516,55 +446,111 @@ describe("Manifest - Adaptation", () => {
it("should return the first Representation with the given Id with `getRepresentation`", () => {
/* tslint:enable:max-line-length */
- const representationSpy = jest.fn(arg => arg);
- const filterSpy = jest.fn((_type, arg) => arg);
-
- jest.mock("../representation", () => ({
- __esModule: true,
- default: representationSpy,
- }));
- jest.mock("../filter_supported_representations", () => ({
- __esModule: true,
- default: filterSpy,
- }));
+ jest.mock("../representation", () => ({ __esModule: true,
+ default: defaultRepresentationSpy }));
const Adaptation = require("../adaptation").default;
- const rep1 = { bitrate: 10, id: "rep1", index: minimalRepresentationIndex };
- const rep2 = { bitrate: 20, id: "rep2", index: minimalRepresentationIndex };
- const rep3 = { bitrate: 30, id: "rep2", index: minimalRepresentationIndex };
+ const rep1 = { bitrate: 10,
+ id: "rep1",
+ index: minimalRepresentationIndex };
+ const rep2 = { bitrate: 20,
+ id: "rep2",
+ index: minimalRepresentationIndex };
+ const rep3 = { bitrate: 30,
+ id: "rep2",
+ index: minimalRepresentationIndex };
const representations = [rep1, rep2, rep3];
const args = { id: "12", representations, type: "text" as "text" };
const adaptation = new Adaptation(args);
- expect(adaptation.getRepresentation("rep1")).toBe(rep1);
- expect(adaptation.getRepresentation("rep2")).toBe(rep2);
+ expect(adaptation.getRepresentation("rep1").bitrate).toEqual(10);
+ expect(adaptation.getRepresentation("rep2").bitrate).toEqual(20);
});
/* tslint:disable:max-line-length */
it("should return undefined in `getRepresentation` if no representation is found with this Id", () => {
/* tslint:enable:max-line-length */
- const representationSpy = jest.fn(arg => arg);
- const filterSpy = jest.fn((_type, arg) => arg);
-
- jest.mock("../representation", () => ({
- __esModule: true,
- default: representationSpy,
- }));
- jest.mock("../filter_supported_representations", () => ({
- __esModule: true,
- default: filterSpy,
- }));
+ jest.mock("../representation", () => ({ __esModule: true,
+ default: defaultRepresentationSpy }));
const Adaptation = require("../adaptation").default;
- const rep1 = { bitrate: 10, id: "rep1", index: minimalRepresentationIndex };
- const rep2 = { bitrate: 20, id: "rep2", index: minimalRepresentationIndex };
- const rep3 = { bitrate: 30, id: "rep2", index: minimalRepresentationIndex };
+ const rep1 = { bitrate: 10,
+ id: "rep1",
+ index: minimalRepresentationIndex };
+ const rep2 = { bitrate: 20,
+ id: "rep2",
+ index: minimalRepresentationIndex };
+ const rep3 = { bitrate: 30,
+ id: "rep2",
+ index: minimalRepresentationIndex };
const representations = [rep1, rep2, rep3];
const args = { id: "12", representations, type: "text" as "text" };
const adaptation = new Adaptation(args);
expect(adaptation.getRepresentation("rep5")).toBe(undefined);
});
+
+ /* tslint:disable:max-line-length */
+ it("should return only supported and decipherable representation when calling `getPlayableRepresentations`", () => {
+ /* tslint:enable:max-line-length */
+ const representationSpy = jest.fn(arg => {
+ return { bitrate: arg.bitrate,
+ id: arg.id,
+ isSupported: arg.id !== "rep3" && arg.id !== "rep8",
+ decipherable: arg.id === "rep6" ? false :
+ arg.id === "rep8" ? false :
+ arg.id === "rep4" ? true :
+ undefined,
+ index: arg.index };
+ });
+ jest.mock("../representation", () => ({ __esModule: true,
+ default: representationSpy }));
+
+ const Adaptation = require("../adaptation").default;
+ const rep1 = { bitrate: 10,
+ id: "rep1",
+ index: minimalRepresentationIndex };
+ const rep2 = { bitrate: 20,
+ id: "rep2",
+ index: minimalRepresentationIndex };
+ const rep3 = { bitrate: 30,
+ id: "rep3",
+ index: minimalRepresentationIndex };
+ const rep4 = { bitrate: 40,
+ id: "rep4",
+ index: minimalRepresentationIndex };
+ const rep5 = { bitrate: 50,
+ id: "rep5",
+ index: minimalRepresentationIndex };
+ const rep6 = { bitrate: 60,
+ id: "rep6",
+ index: minimalRepresentationIndex };
+ const rep7 = { bitrate: 70,
+ id: "rep7",
+ index: minimalRepresentationIndex };
+ const rep8 = { bitrate: 80,
+ id: "rep8",
+ index: minimalRepresentationIndex };
+ const representations = [rep1,
+ rep2,
+ rep3,
+ rep4,
+ rep5,
+ rep6,
+ rep7,
+ rep8];
+ const args = { id: "12", representations, type: "text" as "text" };
+ const adaptation = new Adaptation(args);
+
+ const playableRepresentations = adaptation.getPlayableRepresentations();
+ expect(playableRepresentations.length).toEqual(5);
+ expect(playableRepresentations[0].id).toEqual("rep1");
+ expect(playableRepresentations[1].id).toEqual("rep2");
+ expect(playableRepresentations[2].id).toEqual("rep4");
+ expect(playableRepresentations[3].id).toEqual("rep5");
+ expect(playableRepresentations[4].id).toEqual("rep7");
+ expect(adaptation.parsingErrors).toEqual([]);
+ });
});
/* tslint:enable no-unsafe-any */
diff --git a/src/manifest/__tests__/filter_supported_representations.test.ts b/src/manifest/__tests__/filter_supported_representations.test.ts
deleted file mode 100644
index 4283752a53..0000000000
--- a/src/manifest/__tests__/filter_supported_representations.test.ts
+++ /dev/null
@@ -1,140 +0,0 @@
-/**
- * Copyright 2015 CANAL+ Group
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/* tslint:disable no-unsafe-any */
-describe("Manifest - filterSupportedRepresentations", () => {
- beforeEach(() => {
- jest.resetModules();
- });
-
- it("should not filter text adaptations", () => {
- const isCodecSupportedSpy = jest.fn(() => false);
- jest.mock("../../compat", () => ({ isCodecSupported: isCodecSupportedSpy }));
-
- const filterSupportedRepresentations = require("../filter_supported_representations")
- .default;
-
- const rep1 = { id: "54", bitrate: 100 };
- const rep2 = { id: "55", bitrate: 101 };
- expect(filterSupportedRepresentations("text", [rep1, rep2]))
- .toEqual([rep1, rep2]);
-
- expect(isCodecSupportedSpy).not.toHaveBeenCalled();
- });
-
- it("should not filter image adaptations", () => {
- const isCodecSupportedSpy = jest.fn(() => false);
- jest.mock("../../compat", () => ({ isCodecSupported: isCodecSupportedSpy }));
-
- const filterSupportedRepresentations = require("../filter_supported_representations")
- .default;
-
- const rep1 = { id: "54", bitrate: 100 };
- const rep2 = { id: "55", bitrate: 101 };
- expect(filterSupportedRepresentations("image", [rep1, rep2]))
- .toEqual([rep1, rep2]);
-
- expect(isCodecSupportedSpy).not.toHaveBeenCalled();
- });
-
- it("should filter video adaptations based on the MIME type and codec", () => {
- const isCodecSupportedSpy = jest.fn((arg : string) => arg.indexOf("webm") !== -1);
- jest.mock("../../compat", () => ({ isCodecSupported: isCodecSupportedSpy }));
-
- const filterSupportedRepresentations = require("../filter_supported_representations")
- .default;
-
- const rep1 = { id: "54", bitrate: 100, mimeType: "video/mp4", codecs: "avc2" };
- const rep2 = { id: "55", bitrate: 101, mimeType: "video/webm", codecs: "vp9" };
- expect(filterSupportedRepresentations("video", [rep1, rep2]))
- .toEqual([rep2]);
-
- expect(isCodecSupportedSpy).toHaveBeenCalledTimes(2);
- expect(isCodecSupportedSpy).toHaveBeenCalledWith("video/mp4;codecs=\"avc2\"");
- expect(isCodecSupportedSpy).toHaveBeenCalledWith("video/webm;codecs=\"vp9\"");
- });
-
- it("should filter audio adaptations based on the MIME type and codec", () => {
- const isCodecSupportedSpy = jest.fn((arg : string) => arg.indexOf("aac") !== -1);
- jest.mock("../../compat", () => ({ isCodecSupported: isCodecSupportedSpy }));
-
- const filterSupportedRepresentations = require("../filter_supported_representations")
- .default;
-
- const rep1 = { id: "54", bitrate: 100, mimeType: "audio/mp4", codecs: "aac" };
- const rep2 = { id: "55", bitrate: 101, mimeType: "audio/webm", codecs: "ogg" };
- expect(filterSupportedRepresentations("video", [rep1, rep2]))
- .toEqual([rep1]);
-
- expect(isCodecSupportedSpy).toHaveBeenCalledTimes(2);
- expect(isCodecSupportedSpy).toHaveBeenCalledWith("audio/mp4;codecs=\"aac\"");
- expect(isCodecSupportedSpy).toHaveBeenCalledWith("audio/webm;codecs=\"ogg\"");
- });
-
- it("should filter audio adaptations based on the MIME type and codec", () => {
- const isCodecSupportedSpy = jest.fn((arg : string) => arg.indexOf("aac") !== -1);
- jest.mock("../../compat", () => ({ isCodecSupported: isCodecSupportedSpy }));
-
- const filterSupportedRepresentations = require("../filter_supported_representations")
- .default;
-
- const rep1 = { id: "54", bitrate: 100, mimeType: "audio/mp4", codecs: "aac" };
- const rep2 = { id: "55", bitrate: 101, mimeType: "audio/webm", codecs: "ogg" };
- expect(filterSupportedRepresentations("video", [rep1, rep2]))
- .toEqual([rep1]);
-
- expect(isCodecSupportedSpy).toHaveBeenCalledTimes(2);
- expect(isCodecSupportedSpy).toHaveBeenCalledWith("audio/mp4;codecs=\"aac\"");
- expect(isCodecSupportedSpy).toHaveBeenCalledWith("audio/webm;codecs=\"ogg\"");
- });
-
- it("should set default MIME type and codecs for video adaptations", () => {
- const isCodecSupportedSpy = jest.fn(() => false);
- jest.mock("../../compat", () => ({ isCodecSupported: isCodecSupportedSpy }));
-
- const filterSupportedRepresentations = require("../filter_supported_representations")
- .default;
-
- const rep1 = { id: "54", bitrate: 100, mimeType: "video/mp4" };
- const rep2 = { id: "55", bitrate: 101, codecs: "vp9" };
- const rep3 = { id: "55", bitrate: 101 };
- filterSupportedRepresentations("video", [rep1, rep2, rep3]);
-
- expect(isCodecSupportedSpy).toHaveBeenCalledTimes(3);
- expect(isCodecSupportedSpy).toHaveBeenCalledWith("video/mp4;codecs=\"\"");
- expect(isCodecSupportedSpy).toHaveBeenCalledWith(";codecs=\"vp9\"");
- expect(isCodecSupportedSpy).toHaveBeenCalledWith(";codecs=\"\"");
- });
-
- it("should set default MIME type and codecs for audio adaptations", () => {
- const isCodecSupportedSpy = jest.fn(() => false);
- jest.mock("../../compat", () => ({ isCodecSupported: isCodecSupportedSpy }));
-
- const filterSupportedRepresentations = require("../filter_supported_representations")
- .default;
-
- const rep1 = { id: "54", bitrate: 100, mimeType: "audio/mp4" };
- const rep2 = { id: "55", bitrate: 101, codecs: "ogg" };
- const rep3 = { id: "55", bitrate: 101 };
- filterSupportedRepresentations("audio", [rep1, rep2, rep3]);
-
- expect(isCodecSupportedSpy).toHaveBeenCalledTimes(3);
- expect(isCodecSupportedSpy).toHaveBeenCalledWith("audio/mp4;codecs=\"\"");
- expect(isCodecSupportedSpy).toHaveBeenCalledWith(";codecs=\"ogg\"");
- expect(isCodecSupportedSpy).toHaveBeenCalledWith(";codecs=\"\"");
- });
-});
-/* tslint:enable no-unsafe-any */
diff --git a/src/manifest/__tests__/representation.test.ts b/src/manifest/__tests__/representation.test.ts
index 5a51f228cd..83088dcbd3 100644
--- a/src/manifest/__tests__/representation.test.ts
+++ b/src/manifest/__tests__/representation.test.ts
@@ -14,27 +14,38 @@
* limitations under the License.
*/
-import Representation from "../representation";
-
-const minimalIndex = {
- getInitSegment() { return null; },
- getSegments() { return []; },
- shouldRefresh() { return false; },
- getFirstPosition() : undefined { return ; },
- getLastPosition() : undefined { return ; },
- checkDiscontinuity() { return -1; },
- isSegmentStillAvailable() : undefined { return ; },
- canBeOutOfSyncError() : true { return true; },
- isFinished() : true { return true; },
- _replace() { /* noop */ },
- _update() { /* noop */ },
- _addSegments() { /* noop */ },
-};
+/* tslint:disable no-unsafe-any */
+const minimalIndex = { getInitSegment() { return null; },
+ getSegments() { return []; },
+ shouldRefresh() { return false; },
+ getFirstPosition() : undefined { return ; },
+ getLastPosition() : undefined { return ; },
+ checkDiscontinuity() { return -1; },
+ isSegmentStillAvailable() : undefined { return ; },
+ canBeOutOfSyncError() : true { return true; },
+ isFinished() : true { return true; },
+ _replace() { /* noop */ },
+ _update() { /* noop */ },
+ _addSegments() { /* noop */ } };
+
+const defaultIsCodecSupported = jest.fn(() => true);
describe("Manifest - Representation", () => {
+ beforeEach(() => {
+ jest.resetModules();
+ });
+ afterEach(() => {
+ defaultIsCodecSupported.mockClear();
+ });
+
it("should be able to create Representation with the minimum arguments given", () => {
- const args = { bitrate: 12, id: "test", index: minimalIndex };
- const representation = new Representation(args);
+ jest.mock("../../compat", () => ({ __esModule: true,
+ isCodecSupported: defaultIsCodecSupported }));
+ const Representation = require("../representation").default;
+ const args = { bitrate: 12,
+ id: "test",
+ index: minimalIndex };
+ const representation = new Representation(args, { type: "audio" });
expect(representation.id).toBe("test");
expect(representation.bitrate).toBe(12);
expect(representation.index).toBe(minimalIndex);
@@ -44,12 +55,18 @@ describe("Manifest - Representation", () => {
expect(representation.height).toBe(undefined);
expect(representation.mimeType).toBe(undefined);
expect(representation.width).toBe(undefined);
- expect(representation.getMimeTypeString()).toBe("undefined;codecs=\"undefined\"");
+ expect(representation.getMimeTypeString()).toBe(";codecs=\"\"");
+ expect(representation.isSupported).toBe(true);
+ expect(representation.decipherable).toBe(undefined);
+ expect(defaultIsCodecSupported).toHaveBeenCalledTimes(1);
});
it("should be able to add a height attribute", () => {
+ jest.mock("../../compat", () => ({ __esModule: true,
+ isCodecSupported: defaultIsCodecSupported }));
+ const Representation = require("../representation").default;
const args = { bitrate: 12, id: "test", height: 57, index: minimalIndex };
- const representation = new Representation(args);
+ const representation = new Representation(args, { type: "video" });
expect(representation.id).toBe("test");
expect(representation.bitrate).toBe(12);
expect(representation.index).toBe(minimalIndex);
@@ -59,12 +76,18 @@ describe("Manifest - Representation", () => {
expect(representation.height).toBe(57);
expect(representation.mimeType).toBe(undefined);
expect(representation.width).toBe(undefined);
- expect(representation.getMimeTypeString()).toBe("undefined;codecs=\"undefined\"");
+ expect(representation.getMimeTypeString()).toBe(";codecs=\"\"");
+ expect(representation.isSupported).toBe(true);
+ expect(representation.decipherable).toBe(undefined);
+ expect(defaultIsCodecSupported).toHaveBeenCalledTimes(1);
});
it("should be able to add a width attribute", () => {
+ jest.mock("../../compat", () => ({ __esModule: true,
+ isCodecSupported: defaultIsCodecSupported }));
+ const Representation = require("../representation").default;
const args = { bitrate: 12, id: "test", width: 2, index: minimalIndex };
- const representation = new Representation(args);
+ const representation = new Representation(args, { type: "video" });
expect(representation.id).toBe("test");
expect(representation.bitrate).toBe(12);
expect(representation.index).toBe(minimalIndex);
@@ -74,12 +97,21 @@ describe("Manifest - Representation", () => {
expect(representation.height).toBe(undefined);
expect(representation.mimeType).toBe(undefined);
expect(representation.width).toBe(2);
- expect(representation.getMimeTypeString()).toBe("undefined;codecs=\"undefined\"");
+ expect(representation.getMimeTypeString()).toBe(";codecs=\"\"");
+ expect(representation.isSupported).toBe(true);
+ expect(representation.decipherable).toBe(undefined);
+ expect(defaultIsCodecSupported).toHaveBeenCalledTimes(1);
});
it("should be able to add a codecs attribute", () => {
- const args = { bitrate: 12, id: "test", codecs: "vp9", index: minimalIndex };
- const representation = new Representation(args);
+ jest.mock("../../compat", () => ({ __esModule: true,
+ isCodecSupported: defaultIsCodecSupported }));
+ const Representation = require("../representation").default;
+ const args = { bitrate: 12,
+ id: "test",
+ codecs: "vp9",
+ index: minimalIndex };
+ const representation = new Representation(args, { type: "audio" });
expect(representation.id).toBe("test");
expect(representation.bitrate).toBe(12);
expect(representation.index).toBe(minimalIndex);
@@ -89,12 +121,21 @@ describe("Manifest - Representation", () => {
expect(representation.height).toBe(undefined);
expect(representation.mimeType).toBe(undefined);
expect(representation.width).toBe(undefined);
- expect(representation.getMimeTypeString()).toBe("undefined;codecs=\"vp9\"");
+ expect(representation.getMimeTypeString()).toBe(";codecs=\"vp9\"");
+ expect(representation.isSupported).toBe(true);
+ expect(representation.decipherable).toBe(undefined);
+ expect(defaultIsCodecSupported).toHaveBeenCalledTimes(1);
});
it("should be able to add a mimeType attribute", () => {
- const args = { bitrate: 12, id: "test", mimeType: "audio/mp4", index: minimalIndex };
- const representation = new Representation(args);
+ jest.mock("../../compat", () => ({ __esModule: true,
+ isCodecSupported: defaultIsCodecSupported }));
+ const Representation = require("../representation").default;
+ const args = { bitrate: 12,
+ id: "test",
+ mimeType: "audio/mp4",
+ index: minimalIndex };
+ const representation = new Representation(args, { type: "audio" });
expect(representation.id).toBe("test");
expect(representation.bitrate).toBe(12);
expect(representation.index).toBe(minimalIndex);
@@ -104,68 +145,149 @@ describe("Manifest - Representation", () => {
expect(representation.height).toBe(undefined);
expect(representation.mimeType).toBe("audio/mp4");
expect(representation.width).toBe(undefined);
- expect(representation.getMimeTypeString()).toBe("audio/mp4;codecs=\"undefined\"");
+ expect(representation.getMimeTypeString()).toBe("audio/mp4;codecs=\"\"");
+ expect(representation.isSupported).toBe(true);
+ expect(representation.decipherable).toBe(undefined);
+ expect(defaultIsCodecSupported).toHaveBeenCalledTimes(1);
});
it("should be able to add a contentProtections attribute", () => {
- const args = {
- bitrate: 12,
- id: "test",
- index: minimalIndex,
- contentProtections: {
- keyIds: [{ keyId: new Uint8Array([45]) }],
- initData: { cenc: [{ systemId: "EDEF", data: new Uint8Array([78]) }] },
- },
- };
- const representation = new Representation(args);
+ jest.mock("../../compat", () => ({ __esModule: true,
+ isCodecSupported: defaultIsCodecSupported }));
+ const Representation = require("../representation").default;
+ const args = { bitrate: 12,
+ id: "test",
+ index: minimalIndex,
+ mimeType: "video/mp4",
+ codecs: "vp12",
+ contentProtections: { keyIds: [{ keyId: new Uint8Array([45]) }],
+ initData: {
+ cenc: [{
+ systemId: "EDEF",
+ data: new Uint8Array([78]),
+ }],
+ } } };
+ const representation = new Representation(args, { type: "video" });
expect(representation.id).toBe("test");
expect(representation.bitrate).toBe(12);
expect(representation.index).toBe(minimalIndex);
- expect(representation.codec).toBe(undefined);
+ expect(representation.codec).toBe("vp12");
expect(representation.contentProtections).toBe(args.contentProtections);
expect(representation.frameRate).toBe(undefined);
expect(representation.height).toBe(undefined);
- expect(representation.mimeType).toBe(undefined);
+ expect(representation.mimeType).toBe("video/mp4");
expect(representation.width).toBe(undefined);
- expect(representation.getMimeTypeString()).toBe("undefined;codecs=\"undefined\"");
+ expect(representation.getMimeTypeString()).toBe("video/mp4;codecs=\"vp12\"");
+ expect(representation.isSupported).toBe(true);
+ expect(representation.decipherable).toBe(undefined);
+ expect(defaultIsCodecSupported).toHaveBeenCalledTimes(1);
});
it("should be able to add a frameRate attribute", () => {
- const args = { bitrate: 12, id: "test", frameRate: "1/60", index: minimalIndex };
- const representation = new Representation(args);
+ jest.mock("../../compat", () => ({ __esModule: true,
+ isCodecSupported: defaultIsCodecSupported }));
+ const Representation = require("../representation").default;
+ const args = { bitrate: 12,
+ id: "test",
+ frameRate: "1/60",
+ mimeType: "audio/mp4",
+ codecs: "mp4a.40.2",
+ index: minimalIndex };
+ const representation = new Representation(args, { type: "audio" });
expect(representation.id).toBe("test");
expect(representation.bitrate).toBe(12);
expect(representation.index).toBe(minimalIndex);
- expect(representation.codec).toBe(undefined);
+ expect(representation.codec).toBe("mp4a.40.2");
expect(representation.contentProtections).toBe(undefined);
expect(representation.frameRate).toBe("1/60");
expect(representation.height).toBe(undefined);
- expect(representation.mimeType).toBe(undefined);
+ expect(representation.mimeType).toBe("audio/mp4");
expect(representation.width).toBe(undefined);
- expect(representation.getMimeTypeString()).toBe("undefined;codecs=\"undefined\"");
+ expect(representation.getMimeTypeString()).toBe("audio/mp4;codecs=\"mp4a.40.2\"");
+ expect(representation.isSupported).toBe(true);
+ expect(representation.decipherable).toBe(undefined);
+ expect(defaultIsCodecSupported).toHaveBeenCalledTimes(1);
});
it("should be able to return an exploitable codecs + mimeType string", () => {
- const args1 = { bitrate: 12, id: "test", index: minimalIndex };
- expect(new Representation(args1).getMimeTypeString())
- .toBe("undefined;codecs=\"undefined\"");
-
- const args2 = { bitrate: 12, id: "test", mimeType: "foo", index: minimalIndex };
- expect(new Representation(args2).getMimeTypeString())
- .toBe("foo;codecs=\"undefined\"");
-
- const args3 = { bitrate: 12, id: "test", codecs: "bar", index: minimalIndex };
- expect(new Representation(args3).getMimeTypeString())
- .toBe("undefined;codecs=\"bar\"");
-
- const args4 = {
- bitrate: 12,
- id: "test",
- mimeType: "foo",
- codecs: "bar",
- index: minimalIndex,
- };
- expect(new Representation(args4).getMimeTypeString())
+ jest.mock("../../compat", () => ({ __esModule: true,
+ isCodecSupported: defaultIsCodecSupported }));
+ const Representation = require("../representation").default;
+ const args1 = { bitrate: 12,
+ id: "test",
+ index: minimalIndex };
+ expect(new Representation(args1, { type: "audio" }).getMimeTypeString())
+ .toBe(";codecs=\"\"");
+
+ const args2 = { bitrate: 12,
+ id: "test",
+ mimeType: "foo",
+ index: minimalIndex };
+ expect(new Representation(args2, { type: "audio" }).getMimeTypeString())
+ .toBe("foo;codecs=\"\"");
+
+ const args3 = { bitrate: 12,
+ id: "test",
+ codecs: "bar",
+ index: minimalIndex };
+ expect(new Representation(args3, { type: "audio" }).getMimeTypeString())
+ .toBe(";codecs=\"bar\"");
+
+ const args4 = { bitrate: 12,
+ id: "test",
+ mimeType: "foo",
+ codecs: "bar",
+ index: minimalIndex };
+ expect(new Representation(args4, { type: "audio" }).getMimeTypeString())
.toBe("foo;codecs=\"bar\"");
});
+
+ it("should set `isSupported` of non-supported codecs or mime-type to `false`", () => {
+ const notSupportedSpy = jest.fn(() => false);
+ jest.mock("../../compat", () => ({ __esModule: true,
+ isCodecSupported: notSupportedSpy }));
+ const Representation = require("../representation").default;
+ const args = { bitrate: 12,
+ id: "test",
+ frameRate: "1/60",
+ mimeType: "audio/mp4",
+ codecs: "mp4a.40.2",
+ index: minimalIndex };
+ const representation = new Representation(args, { type: "audio" });
+ expect(representation.id).toBe("test");
+ expect(representation.bitrate).toBe(12);
+ expect(representation.index).toBe(minimalIndex);
+ expect(representation.codec).toBe("mp4a.40.2");
+ expect(representation.contentProtections).toBe(undefined);
+ expect(representation.frameRate).toBe("1/60");
+ expect(representation.height).toBe(undefined);
+ expect(representation.mimeType).toBe("audio/mp4");
+ expect(representation.width).toBe(undefined);
+ expect(representation.getMimeTypeString()).toBe("audio/mp4;codecs=\"mp4a.40.2\"");
+ expect(representation.isSupported).toBe(false);
+ expect(representation.decipherable).toBe(undefined);
+ expect(notSupportedSpy).toHaveBeenCalledTimes(1);
+ expect(notSupportedSpy).toHaveBeenCalledWith("audio/mp4;codecs=\"mp4a.40.2\"");
+ });
+
+ it("should not check support for a custom SourceBuffer", () => {
+ const notSupportedSpy = jest.fn(() => false);
+ jest.mock("../../compat", () => ({ __esModule: true,
+ isCodecSupported: notSupportedSpy }));
+ const Representation = require("../representation").default;
+ const args = { bitrate: 12,
+ id: "test",
+ frameRate: "1/60",
+ mimeType: "bip",
+ codecs: "boop",
+ index: minimalIndex };
+ const representation = new Representation(args, { type: "foo" });
+ expect(representation.codec).toBe("boop");
+ expect(representation.mimeType).toBe("bip");
+ expect(representation.getMimeTypeString()).toBe("bip;codecs=\"boop\"");
+ expect(representation.isSupported).toBe(true);
+ expect(representation.decipherable).toBe(undefined);
+ expect(notSupportedSpy).toHaveBeenCalledTimes(0);
+ });
});
+/* tslint:enable no-unsafe-any */
diff --git a/src/manifest/adaptation.ts b/src/manifest/adaptation.ts
index c098d41b5f..ee6eab2d4f 100644
--- a/src/manifest/adaptation.ts
+++ b/src/manifest/adaptation.ts
@@ -22,13 +22,12 @@ import log from "../log";
import { IParsedAdaptation } from "../parsers/manifest";
import arrayFind from "../utils/array_find";
import arrayIncludes from "../utils/array_includes";
+import isNullOrUndefined from "../utils/is_null_or_undefined";
import normalizeLanguage from "../utils/languages";
import uniq from "../utils/uniq";
-import filterSupportedRepresentations from "./filter_supported_representations";
+// import filterSupportedRepresentations from "./filter_supported_representations";
import Representation from "./representation";
-
-/** Every possible value for the Adaptation's `type` property. */
-export type IAdaptationType = "video" | "audio" | "text" | "image";
+import { IAdaptationType } from "./types";
/** List in an array every possible value for the Adaptation's `type` property. */
export const SUPPORTED_ADAPTATIONS_TYPE: IAdaptationType[] = [ "audio",
@@ -91,10 +90,7 @@ export default class Adaptation {
/** Whether this Adaptation contains closed captions for the hard-of-hearing. */
public isClosedCaption? : boolean;
- /**
- * If true this Adaptation is sign interpreted: which is a
- * variant in sign language.
- */
+ /** If true this Adaptation contains sign interpretation. */
public isSignInterpreted? : boolean;
/**
@@ -115,6 +111,16 @@ export default class Adaptation {
*/
public manuallyAdded? : boolean;
+ /**
+ * `false` if from all Representation from this Adaptation, none is decipherable.
+ * `true` if at least one is known to be decipherable.
+ * `undefined` if this is not known for at least a single Representation.
+ */
+ public decipherable? : boolean;
+
+ /** `true` if at least one Representation is in a supported codec. `false` otherwise. */
+ public isSupported : boolean;
+
/**
* Array containing every errors that happened when the Adaptation has been
* created, in the order they have happened.
@@ -142,18 +148,6 @@ export default class Adaptation {
}
this.type = parsedAdaptation.type;
- const hadRepresentations = parsedAdaptation.representations.length !== 0;
- const argsRepresentations =
- filterSupportedRepresentations(parsedAdaptation.type,
- parsedAdaptation.representations);
-
- if (hadRepresentations && argsRepresentations.length === 0) {
- log.warn("Incompatible codecs for adaptation", parsedAdaptation);
- const error = new MediaError("MANIFEST_INCOMPATIBLE_CODECS_ERROR",
- "An Adaptation contains only incompatible codecs.");
- this.parsingErrors.push(error);
- }
-
if (parsedAdaptation.language !== undefined) {
this.language = parsedAdaptation.language;
this.normalizedLanguage = normalizeLanguage(parsedAdaptation.language);
@@ -172,25 +166,47 @@ export default class Adaptation {
this.isSignInterpreted = parsedAdaptation.isSignInterpreted;
}
- this.representations = argsRepresentations
- .map(representation => new Representation(representation))
- .sort((a, b) => a.bitrate - b.bitrate)
- .filter(representation => {
- if (representationFilter == null) {
- return true;
+ const argsRepresentations = parsedAdaptation.representations;
+ const representations : Representation[] = [];
+ let decipherable : boolean | undefined = false;
+ let isSupported : boolean = false;
+ for (let i = 0; i < argsRepresentations.length; i++) {
+ const representation = new Representation(argsRepresentations[i],
+ { type: this.type });
+ const shouldAdd =
+ isNullOrUndefined(representationFilter) ||
+ representationFilter(representation,
+ { bufferType: this.type,
+ language: this.language,
+ normalizedLanguage: this.normalizedLanguage,
+ isClosedCaption: this.isClosedCaption,
+ isDub: this.isDub,
+ isAudioDescription: this.isAudioDescription,
+ isSignInterpreted: this.isSignInterpreted });
+ if (shouldAdd) {
+ representations.push(representation);
+ if (decipherable === false && representation.decipherable !== false) {
+ decipherable = representation.decipherable;
}
- return representationFilter(representation,
- { bufferType: this.type,
- language: this.language,
- normalizedLanguage: this.normalizedLanguage,
- isClosedCaption: this.isClosedCaption,
- isDub: this.isDub,
- isAudioDescription: this.isAudioDescription,
- isSignInterpreted: this.isSignInterpreted });
- });
+ if (!isSupported && representation.isSupported) {
+ isSupported = true;
+ }
+ }
+ }
+ this.representations = representations.sort((a, b) => a.bitrate - b.bitrate);
+
+ this.decipherable = decipherable;
+ this.isSupported = isSupported;
// for manuallyAdded adaptations (not in the manifest)
this.manuallyAdded = isManuallyAdded === true;
+
+ if (this.representations.length > 0 && !isSupported) {
+ log.warn("Incompatible codecs for adaptation", parsedAdaptation);
+ const error = new MediaError("MANIFEST_INCOMPATIBLE_CODECS_ERROR",
+ "An Adaptation contains only incompatible codecs.");
+ this.parsingErrors.push(error);
+ }
}
/**
@@ -208,6 +224,17 @@ export default class Adaptation {
return uniq(bitrates);
}
+ /**
+ * Returns all Representation in this Adaptation that can be played (that is:
+ * not undecipherable and with a supported codec).
+ * @returns {Array.}
+ */
+ getPlayableRepresentations() : Representation[] {
+ return this.representations.filter(rep => {
+ return rep.isSupported && rep.decipherable !== false;
+ });
+ }
+
/**
* Returns the Representation linked to the given ID.
* @param {number|string} wantedId
diff --git a/src/manifest/filter_supported_representations.ts b/src/manifest/filter_supported_representations.ts
deleted file mode 100644
index 307b55d37e..0000000000
--- a/src/manifest/filter_supported_representations.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-/**
- * Copyright 2015 CANAL+ Group
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import { isCodecSupported } from "../compat";
-import { IParsedRepresentation } from "../parsers/manifest";
-
-/**
- * Only keep Representations for which the codec is currently supported.
- * @param {string} adaptationType
- * @param {Array.} representations
- * @returns {Array.}
- */
-export default function filterSupportedRepresentations(
- adaptationType : string,
- representations : IParsedRepresentation[]
-) : IParsedRepresentation[] {
- if (adaptationType === "audio" || adaptationType === "video") {
- return representations
- .filter((representation) => isCodecSupported(getCodec(representation)));
- }
-
- return representations; // TODO for the other types?
-}
-
-/**
- * Construct the codec string from given codecs and mimetype.
- * @param {Object} representation
- * @returns {string}
- */
-function getCodec(representation : IParsedRepresentation) : string {
- const { codecs = "", mimeType = "" } = representation;
- return `${mimeType};codecs="${codecs}"`;
-}
diff --git a/src/manifest/index.ts b/src/manifest/index.ts
index 94d72c6e99..df30784386 100644
--- a/src/manifest/index.ts
+++ b/src/manifest/index.ts
@@ -15,7 +15,6 @@
*/
import Adaptation, {
- IAdaptationType,
IRepresentationFilter,
SUPPORTED_ADAPTATIONS_TYPE,
} from "./adaptation";
@@ -33,6 +32,7 @@ import IRepresentationIndex, {
ISegment,
StaticRepresentationIndex,
} from "./representation_index";
+import { IAdaptationType } from "./types";
export default Manifest;
export {
diff --git a/src/manifest/manifest.ts b/src/manifest/manifest.ts
index 1201f4fcbd..d99f43eeb7 100644
--- a/src/manifest/manifest.ts
+++ b/src/manifest/manifest.ts
@@ -23,7 +23,6 @@ import EventEmitter from "../utils/event_emitter";
import idGenerator from "../utils/id_generator";
import warnOnce from "../utils/warn_once";
import Adaptation, {
- IAdaptationType,
IRepresentationFilter,
} from "./adaptation";
import Period, {
@@ -31,7 +30,10 @@ import Period, {
} from "./period";
import Representation from "./representation";
import { StaticRepresentationIndex } from "./representation_index";
-import { MANIFEST_UPDATE_TYPE } from "./types";
+import {
+ IAdaptationType,
+ MANIFEST_UPDATE_TYPE,
+} from "./types";
import {
replacePeriods,
updatePeriods,
diff --git a/src/manifest/period.ts b/src/manifest/period.ts
index b51ad465b8..93de030a76 100644
--- a/src/manifest/period.ts
+++ b/src/manifest/period.ts
@@ -22,9 +22,9 @@ import { IParsedPeriod } from "../parsers/manifest";
import arrayFind from "../utils/array_find";
import objectValues from "../utils/object_values";
import Adaptation, {
- IAdaptationType,
IRepresentationFilter,
} from "./adaptation";
+import { IAdaptationType } from "./types";
/** Structure listing every `Adaptation` in a Period. */
export type IManifestAdaptations = Partial>;
@@ -164,4 +164,19 @@ export default class Period {
getAdaptation(wantedId : string) : Adaptation|undefined {
return arrayFind(this.getAdaptations(), ({ id }) => wantedId === id);
}
+
+ getPlayableAdaptations(type? : IAdaptationType) {
+ if (type === undefined) {
+ return this.getAdaptations().filter(ada => {
+ return ada.isSupported && ada.decipherable !== false;
+ });
+ }
+ const adaptationsForType = this.adaptations[type];
+ if (adaptationsForType === undefined) {
+ return [];
+ }
+ return adaptationsForType.filter(ada => {
+ return ada.isSupported && ada.decipherable !== false;
+ });
+ }
}
diff --git a/src/manifest/representation.ts b/src/manifest/representation.ts
index f32f850196..3680b259df 100644
--- a/src/manifest/representation.ts
+++ b/src/manifest/representation.ts
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+import { isCodecSupported } from "../compat";
import log from "../log";
import {
IContentProtections,
@@ -22,6 +23,7 @@ import {
import areArraysOfNumbersEqual from "../utils/are_arrays_of_numbers_equal";
import { concat } from "../utils/byte_parsing";
import IRepresentationIndex from "./representation_index";
+import { IAdaptationType } from "./types";
export interface IContentProtectionsInitDataObject {
type : string;
@@ -90,10 +92,13 @@ class Representation {
*/
public decipherable? : boolean;
+ /** `true` if the Representation is in a supported codec, false otherwise. */
+ public isSupported : boolean;
+
/**
* @param {Object} args
*/
- constructor(args : IParsedRepresentation) {
+ constructor(args : IParsedRepresentation, opts : { type : IAdaptationType }) {
this.id = args.id;
this.bitrate = args.bitrate;
this.codec = args.codecs;
@@ -119,6 +124,10 @@ class Representation {
}
this.index = args.index;
+ this.isSupported = opts.type === "audio" ||
+ opts.type === "video" ?
+ isCodecSupported(this.getMimeTypeString()) :
+ true; // TODO for other types
}
/**
@@ -127,7 +136,7 @@ class Representation {
* @returns {string}
*/
getMimeTypeString() : string {
- return `${this.mimeType};codecs="${this.codec}"`;
+ return `${this.mimeType ?? ""};codecs="${this.codec ?? ""}"`;
}
/**
diff --git a/src/manifest/types.ts b/src/manifest/types.ts
index 8967f8d68a..f81bc73cc8 100644
--- a/src/manifest/types.ts
+++ b/src/manifest/types.ts
@@ -21,3 +21,6 @@ export enum MANIFEST_UPDATE_TYPE {
/** Only a shortened version of the Manifest has been downloaded. */
Partial,
}
+
+/** Every possible value for the Adaptation's `type` property. */
+export type IAdaptationType = "video" | "audio" | "text" | "image";
diff --git a/src/utils/__tests__/initialization_segment_cache.test.ts b/src/utils/__tests__/initialization_segment_cache.test.ts
index 4750f2cd27..6137b94a55 100644
--- a/src/utils/__tests__/initialization_segment_cache.test.ts
+++ b/src/utils/__tests__/initialization_segment_cache.test.ts
@@ -20,6 +20,7 @@ const representation1 = {
bitrate: 12,
id: "r1",
getMimeTypeString() : string { return ""; },
+ isSupported: true,
index: {
getInitSegment() : null { return null; },
getSegments() : never { throw new Error("Not implemented"); },
@@ -42,6 +43,7 @@ const representation2 = {
bitrate: 14,
id: "r2",
getMimeTypeString() : string { return ""; },
+ isSupported: true,
index: {
getInitSegment() : null { return null; },
getSegments() : never { throw new Error("Not implemented"); },
From 53c78777792b600dc57389f1c936e290b9e72acb Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Tue, 14 Apr 2020 16:54:44 +0200
Subject: [PATCH 17/72] api: add `preferredVideoTracks` loadVideo options and
the `setPreferredVideoTracks` and `getPreferredVideoTracks` methods
---
doc/api/index.md | 105 +++++++++++++++++-
doc/api/player_options.md | 74 +++++++++++-
src/core/api/__tests__/option_parsers.test.ts | 12 ++
src/core/api/index.ts | 1 +
src/core/api/option_parsers.ts | 16 +++
src/core/api/public_api.ts | 31 +++++-
src/core/api/track_choice_manager.ts | 76 ++++++++++++-
src/public_types.ts | 1 +
8 files changed, 304 insertions(+), 12 deletions(-)
diff --git a/doc/api/index.md b/doc/api/index.md
index 0db59d7f82..f32fd234cf 100644
--- a/doc/api/index.md
+++ b/doc/api/index.md
@@ -64,6 +64,8 @@
- [getPreferredAudioTracks](#meth-getPreferredAudioTracks)
- [setPreferredTextTracks](#meth-setPreferredTextTracks)
- [getPreferredTextTracks](#meth-getPreferredTextTracks)
+ - [setPreferredVideoTracks](#meth-setPreferredVideoTracks)
+ - [getPreferredVideoTracks](#meth-getPreferredVideoTracks)
- [getCurrentAdaptations](#meth-getCurrentAdaptations)
- [getCurrentRepresentations](#meth-getCurrentRepresentations)
- [dispose](#meth-dispose)
@@ -1316,7 +1318,7 @@ During this period of time:
:warning: This option will have no effect in _DirectFile_ mode
(see [loadVideo options](./loadVideo_options.md#prop-transport)) when either :
-- No video track API was supported on the current browser
+- No video track API is supported on the current browser
- The media file tracks are not supported on the browser
---
@@ -1363,7 +1365,7 @@ preferences, codec preferences or both.
It is defined as an array of objects, each object describing constraints a
track should respect.
-If the first object - defining the first set of constraints - can not be
+If the first object - defining the first set of constraints - cannot be
respected under the currently available audio tracks, the RxPlayer will skip
it and check with the second object and so on.
As such, this array should be sorted by order of preference: from the most
@@ -1476,7 +1478,7 @@ player.setPreferredAudioTracks([
:warning: This option will have no effect in _DirectFile_ mode
(see [loadVideo options](./loadVideo_options.md#prop-transport)) when either :
-- No audio track API was supported on the current browser
+- No audio track API is supported on the current browser
- The media file tracks are not supported on the browser
---
@@ -1555,7 +1557,7 @@ player.setPreferredTextTracks([
:warning: This option will have no effect in _DirectFile_ mode
(see [loadVideo options](./loadVideo_options.md#prop-transport)) when either :
-- No text track API was supported on the current browser
+- No text track API is supported on the current browser
- The media file tracks are not supported on the browser
---
@@ -1582,6 +1584,101 @@ it was called:
```
+
+### setPreferredVideoTracks ####################################################
+
+_arguments_: ``Array.``
+
+Allows the RxPlayer to choose an initial video track.
+
+It is defined as an array of objects, each object describing constraints a
+track should respect.
+
+If the first object - defining the first set of constraints - cannot be
+respected under the currently available video tracks, the RxPlayer will skip
+it and check with the second object and so on.
+As such, this array should be sorted by order of preference: from the most
+wanted constraints to the least.
+
+Here is all the possible constraints you can set in any one of those objects
+(note that all properties are optional here, only those set will have an effect
+on which tracks will be filtered):
+```js
+{
+ codec: { // {Object|undefined} Constraints about the codec wanted.
+ // if not set or set to `undefined` we won't filter based on codecs.
+
+ test: /hvc/, // {RegExp} RegExp validating the type of codec you want.
+
+ all: true, // {Boolean} Whether all the profiles (i.e. Representation) in a
+ // track should be checked against the RegExp given in `test`.
+ // If `true`, we will only choose a track if EVERY profiles for
+ // it have a codec information that is validated by that RegExp.
+ // If `false`, we will choose a track if we know that at least
+ // A SINGLE profile from it has codec information validated by
+ // that RegExp.
+ }
+}
+```
+
+This logic is ran each time a new `Period` with video tracks is loaded by the
+RxPlayer. This means at the start of the content, but also when [pre-]loading a
+new DASH `Period` or a new MetaPlaylist `content`.
+
+Please note that those preferences won't be re-applied once the logic was
+already run for a given `Period`.
+Simply put, once set this preference will be applied to all contents but:
+
+ - the current Period being played (or the current loaded content, in the case
+ of single-Period contents such as in Smooth streaming).
+ In that case, the current video track preference will stay in place.
+
+ - the Periods which have already been loaded in the current content.
+ Those will keep their last set video track preferences (e.g. the preferred
+ video tracks at the time they were first loaded).
+
+To update the current video track in those cases, you should use the
+`setVideoTrack` method once they are currently played.
+
+
+#### Examples
+
+Let's imagine that you prefer to have a track which contains only H265
+profiles. You can do:
+```js
+player.setPreferredVideoTracks([ { codec: { all: false, test: /^hvc/ } } ]);
+```
+
+Now let's imagine you want to start without any video track enabled (e.g. to
+start in an audio-only mode). To do that, you can simply do:
+```js
+player.setPreferredVideoTracks([null]);
+```
+
+---
+
+:warning: This option will have no effect in _DirectFile_ mode
+(see [loadVideo options](./loadVideo_options.md#prop-transport)) when either :
+- No video track API is supported on the current browser
+- The media file tracks are not supported on the browser
+
+---
+
+
+
+### getPreferredVideoTracks ####################################################
+
+_return value_: ``Array.``
+
+Returns the current list of preferred video tracks - by order of preference.
+
+This returns the data in the same format that it was given to either the
+`preferredVideoTracks` constructor option or the last `setPreferredVideoTracks`
+if it was called.
+
+It will return an empty Array if none of those two APIs were used until now.
+
+
### getManifest ################################################################
diff --git a/doc/api/player_options.md b/doc/api/player_options.md
index a2f0ff9107..7d26f63b93 100644
--- a/doc/api/player_options.md
+++ b/doc/api/player_options.md
@@ -12,6 +12,7 @@
- [wantedBufferAhead](#prop-wantedBufferAhead)
- [preferredAudioTracks](#prop-preferredAudioTracks)
- [preferredTextTracks](#prop-preferredTextTracks)
+ - [preferredVideoTracks](#prop-preferredVideoTracks)
- [maxBufferAhead](#prop-maxBufferAhead)
- [maxBufferBehind](#prop-maxBufferBehind)
- [limitVideoWidth](#prop-limitVideoWidth)
@@ -234,7 +235,7 @@ either language preferences, codec preferences or both.
It is defined as an array of objects, each object describing constraints a
track should respect.
-If the first object - defining the first set of constraints - can not be
+If the first object - defining the first set of constraints - cannot be
respected under the currently available audio tracks, the RxPlayer will skip
it and check with the second object and so on.
As such, this array should be sorted by order of preference: from the most
@@ -394,6 +395,77 @@ mode (see [loadVideo options](./loadVideo_options.md#prop-transport)).
---
+
+### preferredVideoTracks #######################################################
+
+_type_: ``Array.``
+
+_defaults_: ``[]``
+
+This option allows to help the RxPlayer choose an initial video track.
+
+It is defined as an array of objects, each object describing constraints a
+track should respect.
+
+If the first object - defining the first set of constraints - cannot be
+respected under the currently available video tracks, the RxPlayer will skip
+it and check with the second object and so on.
+As such, this array should be sorted by order of preference: from the most
+wanted constraints to the least.
+
+Here is all the possible constraints you can set in any one of those objects
+(note that all properties are optional here, only those set will have an effect
+on which tracks will be filtered):
+```js
+{
+ codec: { // {Object|undefined} Constraints about the codec wanted.
+ // if not set or set to `undefined` we won't filter based on codecs.
+
+ test: /hvc/, // {RegExp} RegExp validating the type of codec you want.
+
+ all: true, // {Boolean} Whether all the profiles (i.e. Representation) in a
+ // track should be checked against the RegExp given in `test`.
+ // If `true`, we will only choose a track if EVERY profiles for
+ // it have a codec information that is validated by that RegExp.
+ // If `false`, we will choose a track if we know that at least
+ // A SINGLE profile from it has codec information validated by
+ // that RegExp.
+ }
+}
+```
+
+This array of preferrences can be updated at any time through the
+``setPreferredVideoTracks`` method, documented
+[here](./index.md#meth-getPreferredVideoTracks).
+
+#### Examples
+
+Let's imagine that you prefer to have a track which contains at least one H265
+profile.
+
+You can do:
+```js
+const player = new RxPlayer({
+ preferredVideoTracks: [ { codec: { all: false, test: /^hvc/ } } ]
+});
+```
+
+Now let's imagine you want to start without any video track enabled (e.g. to
+start in an audio-only mode). To do that, you can simply do:
+```js
+const player = new RxPlayer({
+ preferredVideoTracks: [null]
+});
+```
+
+---
+
+:warning: This option will have no effect for contents loaded in _DirectFile_
+mode (see [loadVideo options](./loadVideo_options.md#prop-transport)).
+
+---
+
+
### maxBufferAhead #############################################################
diff --git a/src/core/api/__tests__/option_parsers.test.ts b/src/core/api/__tests__/option_parsers.test.ts
index e687343cf9..50bf41f80d 100644
--- a/src/core/api/__tests__/option_parsers.test.ts
+++ b/src/core/api/__tests__/option_parsers.test.ts
@@ -81,6 +81,7 @@ describe("API - parseConstructorOptions", () => {
stopAtEnd: true,
preferredAudioTracks: [],
preferredTextTracks: [],
+ preferredVideoTracks: [],
};
it("should create default values if no option is given", () => {
@@ -337,6 +338,17 @@ describe("API - parseConstructorOptions", () => {
});
});
+ it("should authorize setting a preferredVideoTracks option", () => {
+ const preferredVideoTracks = [
+ { codec: { all: true, test: /hvc/ } },
+ null,
+ ];
+ expect(parseConstructorOptions({ preferredVideoTracks })).toEqual({
+ ...defaultConstructorOptions,
+ preferredVideoTracks,
+ });
+ });
+
it("should throw if the maxBufferAhead given is not a number", () => {
expect(() => parseConstructorOptions({ maxBufferAhead: "a" as any })).toThrow();
expect(() => parseConstructorOptions({ maxBufferAhead: /a/ as any })).toThrow();
diff --git a/src/core/api/index.ts b/src/core/api/index.ts
index 0c61eea752..474ae39ac5 100644
--- a/src/core/api/index.ts
+++ b/src/core/api/index.ts
@@ -40,5 +40,6 @@ export {
IAudioTrackPreference,
ITextTrackPreference,
+ IVideoTrackPreference,
} from "./track_choice_manager";
export default Player;
diff --git a/src/core/api/option_parsers.ts b/src/core/api/option_parsers.ts
index e58be7df28..5cb6c8add2 100644
--- a/src/core/api/option_parsers.ts
+++ b/src/core/api/option_parsers.ts
@@ -37,6 +37,7 @@ import { IKeySystemOption } from "../eme";
import {
IAudioTrackPreference,
ITextTrackPreference,
+ IVideoTrackPreference,
} from "./track_choice_manager";
const { DEFAULT_AUTO_PLAY,
@@ -192,6 +193,7 @@ export interface IConstructorOptions { maxBufferAhead? : number;
preferredAudioTracks? : IAudioTrackPreference[];
preferredTextTracks? : ITextTrackPreference[];
+ preferredVideoTracks? : IVideoTrackPreference[];
videoElement? : HTMLMediaElement;
initialVideoBitrate? : number;
@@ -212,6 +214,7 @@ export interface IParsedConstructorOptions {
preferredAudioTracks : IAudioTrackPreference[];
preferredTextTracks : ITextTrackPreference[];
+ preferredVideoTracks : IVideoTrackPreference[];
videoElement : HTMLMediaElement;
initialVideoBitrate : number;
@@ -311,6 +314,7 @@ function parseConstructorOptions(
let preferredAudioTracks : IAudioTrackPreference[];
let preferredTextTracks : ITextTrackPreference[];
+ let preferredVideoTracks : IVideoTrackPreference[];
let videoElement : HTMLMediaElement;
let initialVideoBitrate : number;
@@ -392,6 +396,17 @@ function parseConstructorOptions(
preferredAudioTracks = [];
}
+ if (options.preferredVideoTracks !== undefined) {
+ if (!Array.isArray(options.preferredVideoTracks)) {
+ warnOnce("Invalid `preferredVideoTracks` option, it should be an Array");
+ preferredVideoTracks = [];
+ } else {
+ preferredVideoTracks = options.preferredVideoTracks;
+ }
+ } else {
+ preferredVideoTracks = [];
+ }
+
if (options.videoElement == null) {
videoElement = document.createElement("video");
} else if (options.videoElement instanceof HTMLMediaElement) {
@@ -454,6 +469,7 @@ function parseConstructorOptions(
throttleVideoBitrateWhenHidden,
preferredAudioTracks,
preferredTextTracks,
+ preferredVideoTracks,
initialAudioBitrate,
initialVideoBitrate,
maxAudioBitrate,
diff --git a/src/core/api/public_api.ts b/src/core/api/public_api.ts
index c57075cf88..721dacd4cd 100644
--- a/src/core/api/public_api.ts
+++ b/src/core/api/public_api.ts
@@ -122,7 +122,8 @@ import TrackChoiceManager, {
ITMTextTrack,
ITMTextTrackListItem,
ITMVideoTrack,
- ITMVideoTrackListItem
+ ITMVideoTrackListItem,
+ IVideoTrackPreference,
} from "./track_choice_manager";
const { DEFAULT_UNMUTED_VOLUME } = config;
@@ -354,6 +355,9 @@ class Player extends EventEmitter {
/** List of favorite text tracks, in preference order. */
private _priv_preferredTextTracks : BehaviorSubject;
+ /** List of favorite video tracks, in preference order. */
+ private _priv_preferredVideoTracks : BehaviorSubject;
+
/**
* TrackChoiceManager instance linked to the current content.
* `null` if no content has been loaded or if the current content loaded
@@ -440,6 +444,7 @@ class Player extends EventEmitter {
maxVideoBitrate,
preferredAudioTracks,
preferredTextTracks,
+ preferredVideoTracks,
throttleWhenHidden,
throttleVideoBitrateWhenHidden,
videoElement,
@@ -539,6 +544,7 @@ class Player extends EventEmitter {
this._priv_preferredAudioTracks = new BehaviorSubject(preferredAudioTracks);
this._priv_preferredTextTracks = new BehaviorSubject(preferredTextTracks);
+ this._priv_preferredVideoTracks = new BehaviorSubject(preferredVideoTracks);
}
/**
@@ -1793,6 +1799,14 @@ class Player extends EventEmitter {
return this._priv_preferredTextTracks.getValue();
}
+ /**
+ * Returns the current list of preferred text tracks, in preference order.
+ * @returns {Array.}
+ */
+ getPreferredVideoTracks() : IVideoTrackPreference[] {
+ return this._priv_preferredVideoTracks.getValue();
+ }
+
/**
* Set the list of preferred audio tracks, in preference order.
* @param {Array.} tracks
@@ -1817,6 +1831,18 @@ class Player extends EventEmitter {
return this._priv_preferredTextTracks.next(tracks);
}
+ /**
+ * Set the list of preferred text tracks, in preference order.
+ * @param {Array.} tracks
+ */
+ setPreferredVideoTracks(tracks : IVideoTrackPreference[]) : void {
+ if (!Array.isArray(tracks)) {
+ throw new Error("Invalid `setPreferredVideoTracks` argument. " +
+ "Should have been an Array.");
+ }
+ return this._priv_preferredVideoTracks.next(tracks);
+ }
+
/**
* @returns {Array.|null}
* @deprecated
@@ -2064,6 +2090,9 @@ class Player extends EventEmitter {
preferredTextTracks: initialTextTrack === undefined ?
this._priv_preferredTextTracks :
new BehaviorSubject([initialTextTrack]),
+ preferredVideoTracks: initialTextTrack === undefined ?
+ this._priv_preferredVideoTracks :
+ new BehaviorSubject([] as IVideoTrackPreference[]),
});
fromEvent(manifest, "manifestUpdate")
diff --git a/src/core/api/track_choice_manager.ts b/src/core/api/track_choice_manager.ts
index 2ebd90c9b9..1b1ce26953 100644
--- a/src/core/api/track_choice_manager.ts
+++ b/src/core/api/track_choice_manager.ts
@@ -47,6 +47,11 @@ export type ITextTrackPreference = null |
{ language : string;
closedCaption : boolean; };
+/** Single preference for a video track Adaptation. */
+export type IVideoTrackPreference = null |
+ { codec? : { all: boolean;
+ test: RegExp; }; };
+
/** Audio track returned by the TrackChoiceManager. */
export interface ITMAudioTrack { language : string;
normalized : string;
@@ -163,7 +168,7 @@ export default class TrackChoiceManager {
private _periods : SortedList;
/**
- * Array of preferred languages for audio tracks.
+ * Array of preferred settings for audio tracks.
* Sorted by order of preference descending.
*/
private _preferredAudioTracks : BehaviorSubject;
@@ -174,6 +179,12 @@ export default class TrackChoiceManager {
*/
private _preferredTextTracks : BehaviorSubject;
+ /**
+ * Array of preferred settings for video tracks.
+ * Sorted by order of preference descending.
+ */
+ private _preferredVideoTracks : BehaviorSubject;
+
/** Memoization of the previously-chosen audio Adaptation for each Period. */
private _audioChoiceMemory : WeakMap;
@@ -192,16 +203,17 @@ export default class TrackChoiceManager {
constructor(defaults : {
preferredAudioTracks : BehaviorSubject;
preferredTextTracks : BehaviorSubject;
+ preferredVideoTracks : BehaviorSubject;
}) {
- const { preferredAudioTracks, preferredTextTracks } = defaults;
this._periods = new SortedList((a, b) => a.period.start - b.period.start);
this._audioChoiceMemory = new WeakMap();
this._textChoiceMemory = new WeakMap();
this._videoChoiceMemory = new WeakMap();
- this._preferredAudioTracks = preferredAudioTracks;
- this._preferredTextTracks = preferredTextTracks;
+ this._preferredAudioTracks = defaults.preferredAudioTracks;
+ this._preferredTextTracks = defaults.preferredTextTracks;
+ this._preferredVideoTracks = defaults.preferredVideoTracks;
}
/**
@@ -369,6 +381,7 @@ export default class TrackChoiceManager {
throw new Error("TrackChoiceManager: Given Period not found.");
}
+ const preferredVideoTracks = this._preferredVideoTracks.getValue();
const videoAdaptations = period.adaptations.video === undefined ?
[] :
period.adaptations.video;
@@ -380,7 +393,8 @@ export default class TrackChoiceManager {
} else if (chosenVideoAdaptation === undefined ||
!arrayIncludes(videoAdaptations, chosenVideoAdaptation)
) {
- const optimalAdaptation = videoAdaptations[0];
+ const optimalAdaptation = findFirstOptimalVideoAdaptation(videoAdaptations,
+ preferredVideoTracks);
this._videoChoiceMemory.set(period, optimalAdaptation);
videoInfos.adaptation$.next(optimalAdaptation);
} else {
@@ -808,6 +822,7 @@ export default class TrackChoiceManager {
}
private _updateVideoTrackChoices() {
+ const preferredVideoTracks = this._preferredVideoTracks.getValue();
const recursiveUpdateVideoTrack = (index : number) : void => {
if (index >= this._periods.length()) {
// we did all video Buffers, exit
@@ -838,7 +853,8 @@ export default class TrackChoiceManager {
return;
}
- const optimalAdaptation = videoAdaptations[0];
+ const optimalAdaptation = findFirstOptimalVideoAdaptation(videoAdaptations,
+ preferredVideoTracks);
this._videoChoiceMemory.set(period, optimalAdaptation);
videoItem.adaptation$.next(optimalAdaptation);
@@ -955,6 +971,54 @@ function findFirstOptimalTextAdaptation(
return null;
}
+/**
+ * Find an optimal video adaptation given their list and the array of preferred
+ * video tracks sorted from the most preferred to the least preferred.
+ *
+ * `null` if the most optimal video adaptation is no video adaptation.
+ * @param {Array.} videoAdaptations
+ * @param {Array.} preferredvideoTracks
+ * @returns {Adaptation|null}
+ */
+function findFirstOptimalVideoAdaptation(
+ videoAdaptations : Adaptation[],
+ preferredVideoTracks : IVideoTrackPreference[]
+) : Adaptation|null {
+ if (videoAdaptations.length === 0) {
+ return null;
+ }
+
+ for (let i = 0; i < preferredVideoTracks.length; i++) {
+ const preferredVideoTrack = preferredVideoTracks[i];
+
+ if (preferredVideoTrack === null) {
+ return null;
+ }
+
+ const foundAdaptation = arrayFind(videoAdaptations, (videoAdaptation) => {
+ if (preferredVideoTrack.codec === undefined) {
+ return true;
+ }
+ const regxp = preferredVideoTrack.codec.test;
+ const codecTestingFn = (rep : Representation) =>
+ rep.codec !== undefined && regxp.test(rep.codec);
+
+ if (preferredVideoTrack.codec.all) {
+ return videoAdaptation.representations.every(codecTestingFn);
+ }
+ return videoAdaptation.representations.some(codecTestingFn);
+ });
+
+ if (foundAdaptation !== undefined) {
+ return foundAdaptation;
+ }
+
+ }
+
+ // no optimal adaptation, just return the first one
+ return videoAdaptations[0];
+}
+
/**
* Returns the index of the given `period` in the given `periods`
* SortedList.
diff --git a/src/public_types.ts b/src/public_types.ts
index 4543e4a834..aa86ae6605 100644
--- a/src/public_types.ts
+++ b/src/public_types.ts
@@ -43,4 +43,5 @@ export {
IAudioTrackPreference,
ITextTrackPreference,
+ IVideoTrackPreference,
} from "./core/api";
From cc685f3d5eaee0c2fcc24431aef14e9d24776bb2 Mon Sep 17 00:00:00 2001
From: grenault73
Date: Wed, 15 Apr 2020 12:13:09 +0200
Subject: [PATCH 18/72] doc: fixed disableVideoTrack doc
---
doc/api/index.md | 10 +++++-----
src/core/buffers/period/period_buffer.ts | 6 +++---
2 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/doc/api/index.md b/doc/api/index.md
index 3a1309e377..7597748907 100644
--- a/doc/api/index.md
+++ b/doc/api/index.md
@@ -1323,23 +1323,23 @@ During this period of time:
-### disableVideoTrack #####################################################
+### disableVideoTrack ##########################################################
_return value_: ``void``
Deactivate the current video track, if one.
-Will enter in `RELOADING` state for a short period, if not in _DirectFile_
+Might enter in `RELOADING` state for a short period, if not in _DirectFile_
---
:warning: This option will have no effect in _DirectFile_ mode
(see [loadVideo options](./loadVideo_options.md#prop-transport)) when either :
-- No audio track API was supported on the current browser
+- No video track API was supported on the current browser
- The media file tracks are not supported on the browser
-/!\ On Safari browser in _DirectFile_, video playback may continue on same track
-even if video tracks are disabled.
+:warning: On Safari browser in _DirectFile_, video playback may continue on same
+track even if video tracks are disabled.
---
diff --git a/src/core/buffers/period/period_buffer.ts b/src/core/buffers/period/period_buffer.ts
index 7f2936d0f3..04139d194b 100644
--- a/src/core/buffers/period/period_buffer.ts
+++ b/src/core/buffers/period/period_buffer.ts
@@ -121,13 +121,13 @@ export default function PeriodBuffer({
if (sourceBufferStatus.type === "set") {
log.info(`Buffer: Clearing previous ${bufferType} SourceBuffer`);
+ if (SourceBuffersStore.isNative(bufferType)) {
+ return clock$.pipe(map(tick => EVENTS.needsMediaSourceReload(tick)));
+ }
cleanBuffer$ = sourceBufferStatus.value
.removeBuffer(period.start,
period.end == null ? Infinity :
period.end);
- if (SourceBuffersStore.isNative(bufferType)) {
- return clock$.pipe(map(tick => EVENTS.needsMediaSourceReload(tick)));
- }
} else {
if (sourceBufferStatus.type === "unset") {
sourceBuffersStore.disableSourceBuffer(bufferType);
From f0abb3412fe6014b029a7eb55e7cbe0bf5771e45 Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Tue, 14 Apr 2020 18:50:03 +0200
Subject: [PATCH 19/72] doc: add documentation on the `signInterpreted`
property on video tracks
---
doc/api/index.md | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/doc/api/index.md b/doc/api/index.md
index 1ebe44e91f..bb4e5adb8c 100644
--- a/doc/api/index.md
+++ b/doc/api/index.md
@@ -1102,6 +1102,12 @@ Each of the objects in the returned array have the following properties:
- ``frameRate`` (``string|undefined``): The video framerate.
+ - ``signInterpreted`` (``Boolean|undefined``): If set to `true`, the track is
+ known to contain an interpretation in sign language.
+ If set to `false`, the track is known to not contain that type of content.
+ If not set or set to undefined we don't know whether that video track
+ contains an interpretation in sign language.
+
In _DirectFile_ mode
(see [loadVideo options](./loadVideo_options.md#prop-transport)), if there are
@@ -1193,7 +1199,6 @@ The track is an object with the following properties:
- ``id`` (``string``): The id used to identify the track. Use it for setting
the track via ``setVideoTrack``.
-
- ``representations`` (``Array.``):
[Representations](../terms.md#representation) of this video track, with
attributes:
@@ -1212,6 +1217,12 @@ The track is an object with the following properties:
- ``frameRate`` (``string|undefined``): The video framerate.
+ - ``signInterpreted`` (``Boolean|undefined``): If set to `true`, the track is
+ known to contain an interpretation in sign language.
+ If set to `false`, the track is known to not contain that type of content.
+ If not set or set to undefined we don't know whether that video track
+ contains an interpretation in sign language.
+
``undefined`` if no content has been loaded yet.
From 6622cef9f7bfe49aeaf0fc791743965c4e009d5a Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Thu, 2 Apr 2020 18:46:50 +0200
Subject: [PATCH 20/72] parsers/manifest/metaplaylist: update documentation
style
---
.../metaplaylist/representation_index.ts | 78 ++++++++++++++++++-
1 file changed, 74 insertions(+), 4 deletions(-)
diff --git a/src/parsers/manifest/metaplaylist/representation_index.ts b/src/parsers/manifest/metaplaylist/representation_index.ts
index 5f03138100..23b0e8c865 100644
--- a/src/parsers/manifest/metaplaylist/representation_index.ts
+++ b/src/parsers/manifest/metaplaylist/representation_index.ts
@@ -23,20 +23,40 @@ import {
import objectAssign from "../../../utils/object_assign";
/**
- * The MetaRepresentationIndex is wrapper for all kind of indexes (dash, smooth, etc)
+ * The MetaRepresentationIndex is wrapper for all kind of RepresentationIndex (from
+ * dash, smooth, etc)
*
- * It wraps methods from origin indexes, while taking into account of the offset induced
- * by metaplaylist. It makes a bridge between the metaplaylist timeline, and the original
+ * It wraps methods from original RepresentationIndex, while taking into account
+ * the time offset introduced by the MetaPlaylist content.
+ *
+ * It makes a bridge between the MetaPlaylist timeline, and the original
* timeline of content. (e.g. the segment whose "meta" time is 1500, is actually a
* segment whose original time is 200, played with an offset of 1300)
+ * @class MetaRepresentationIndex
*/
export default class MetaRepresentationIndex implements IRepresentationIndex {
+ /** Real underlying RepresentationIndex implementation. */
protected _wrappedIndex : IRepresentationIndex;
+ /** Offset time to add to the start of the Representation, in seconds. */
private _timeOffset : number;
+ /** Absolute end of the Representation, in the seconds. */
private _contentEnd : number | undefined;
+ /** Underlying transport for the Representation (e.g. "dash" or "smooth"). */
private _transport : string;
+ /** Various information about the real underlying Representation. */
private _baseContentInfos : IBaseContentInfos;
+ /**
+ * Create a new `MetaRepresentationIndex`.
+ * @param {Object} wrappedIndex - "Real" RepresentationIndex implementation of
+ * the concerned Representation.
+ * @param {Array.} contentBounds - Start time and end time
+ * the Representation will be played between, in seconds.
+ * @param {string} transport - Transport for the "real" RepresentationIndex
+ * (e.g. "dash" or "smooth").
+ * @param {Object} baseContentInfos - Various information about the "real"
+ * Representation.
+ */
constructor(
wrappedIndex: IRepresentationIndex,
contentBounds: [number, number|undefined],
@@ -50,6 +70,9 @@ export default class MetaRepresentationIndex implements IRepresentationIndex {
this._baseContentInfos = baseContentInfos;
}
+ /**
+ * Returns information about the initialization segment.
+ */
public getInitSegment() {
const segment = this._wrappedIndex.getInitSegment();
if (segment === null) {
@@ -65,6 +88,12 @@ export default class MetaRepresentationIndex implements IRepresentationIndex {
return segment;
}
+ /**
+ * Returns information about the segments asked.
+ * @param {number} up - Starting time wanted, in seconds.
+ * @param {Number} duration - Amount of time wanted, in seconds
+ * @returns {Array.}
+ */
public getSegments(up : number, duration : number) : ISegment[] {
return this._wrappedIndex.getSegments(up - this._timeOffset, duration)
.map((segment) => {
@@ -80,22 +109,45 @@ export default class MetaRepresentationIndex implements IRepresentationIndex {
});
}
- public shouldRefresh() : boolean {
+ /**
+ * Whether this RepresentationIndex should be refreshed now.
+ * Returns `false` as MetaPlaylist contents do not support underlying live
+ * contents yet.
+ * @returns {Boolean}
+ */
+ public shouldRefresh() : false {
return false;
}
+ /**
+ * Returns first possible position the first segment plays at, in seconds.
+ * `undefined` if we do not know this value.
+ * @return {Number|undefined}
+ */
public getFirstPosition(): number|undefined {
const wrappedFirstPosition = this._wrappedIndex.getFirstPosition();
return wrappedFirstPosition != null ? wrappedFirstPosition + this._timeOffset :
undefined;
}
+ /**
+ * Returns last possible position the last segment plays at, in seconds.
+ * `undefined` if we do not know this value.
+ * @return {Number|undefined}
+ */
public getLastPosition(): number|undefined {
const wrappedLastPosition = this._wrappedIndex.getLastPosition();
return wrappedLastPosition != null ? wrappedLastPosition + this._timeOffset :
undefined;
}
+ /**
+ * Returns `false` if that segment is not currently available in the Manifest
+ * (e.g. it corresponds to a segment which is before the current buffer
+ * depth).
+ * @param {Object} segment
+ * @returns {boolean|undefined}
+ */
public isSegmentStillAvailable(segment : ISegment) : boolean | undefined {
const offset = this._timeOffset * segment.timescale;
const updatedSegment = objectAssign({},
@@ -112,14 +164,25 @@ export default class MetaRepresentationIndex implements IRepresentationIndex {
return this._wrappedIndex.canBeOutOfSyncError(error);
}
+ /**
+ *
+ * @param {Number} time
+ * @returns {Number}
+ */
public checkDiscontinuity(time: number): number {
return this._wrappedIndex.checkDiscontinuity(time - this._timeOffset);
}
+ /**
+ * @returns {Boolean}
+ */
public isFinished() : boolean {
return this._wrappedIndex.isFinished();
}
+ /**
+ * @param {Object} newIndex
+ */
public _replace(newIndex : IRepresentationIndex): void {
if (!(newIndex instanceof MetaRepresentationIndex)) {
throw new Error("A MetaPlaylist can only be replaced with another MetaPlaylist");
@@ -127,6 +190,9 @@ export default class MetaRepresentationIndex implements IRepresentationIndex {
this._wrappedIndex._replace(newIndex._wrappedIndex);
}
+ /**
+ * @param {Object} newIndex
+ */
public _update(newIndex : IRepresentationIndex): void {
if (!(newIndex instanceof MetaRepresentationIndex)) {
throw new Error("A MetaPlaylist can only be updated with another MetaPlaylist");
@@ -134,6 +200,10 @@ export default class MetaRepresentationIndex implements IRepresentationIndex {
this._wrappedIndex._update(newIndex._wrappedIndex);
}
+ /**
+ * @param {Array.} nextSegments
+ * @param {Object} currentSegment
+ */
public _addSegments(
nextSegments : Array<{ time : number;
duration : number;
From 9ad6cf8a3d2ea2a83e5a10ed5c5d36627f05ff22 Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Wed, 15 Apr 2020 19:43:05 +0200
Subject: [PATCH 21/72] doc: update `Period` term definition
---
doc/terms.md | 17 ++++++++++++++---
1 file changed, 14 insertions(+), 3 deletions(-)
diff --git a/doc/terms.md b/doc/terms.md
index 0d723d76f4..b742f2055b 100644
--- a/doc/terms.md
+++ b/doc/terms.md
@@ -169,9 +169,19 @@ content a single time.
### Period #####################################################################
A Period is an element of the [Manifest](#manifest) which describes the media
-to play at a certain points in time.
+to play at a given point in time.
-They are directly a DASH' concept, also called _Period_.
+Depending on the transport used, they correspond to different concepts:
+ - for DASH contents, it is linked to an MPD's Period element
+ - for "local" contents, it corresponds to a single object from the `periods`
+ array.
+ - for "MetaPlaylist" contents, it corresponds to all the Period elements we
+ retrieved bwhen parsing the corresponding [Manifest](#manifest) from the
+ elements of the `contents` array.
+ - any other transport will have a single Period, describing the whole content.
+
+Despite having a different source depending on the transport used, a Period is
+a single concept in the RxPlayer.
Simply put, it allows to set various types of content successively in the same
manifest.
@@ -195,7 +205,8 @@ This can be done by putting each in a different Period:
TV Show Italian Film American Blockbuster
```
-Each of these Periods will be linked to different audio, video and text tracks.
+Each of these Periods will be linked to different audio, video and text tracks,
+themselves linked to different qualities.
From 3a32a65661d1d40615877853b21e2faa95b2029c Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Tue, 14 Apr 2020 17:08:38 +0200
Subject: [PATCH 22/72] doc: add notion about setting the preferredVideoTracks
to null
---
doc/api/index.md | 6 +++++-
doc/api/player_options.md | 4 ++++
2 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/doc/api/index.md b/doc/api/index.md
index f32fd234cf..d25059f0c8 100644
--- a/doc/api/index.md
+++ b/doc/api/index.md
@@ -1587,7 +1587,7 @@ it was called:
### setPreferredVideoTracks ####################################################
-_arguments_: ``Array.``
+_arguments_: ``Array.``
Allows the RxPlayer to choose an initial video track.
@@ -1600,6 +1600,10 @@ it and check with the second object and so on.
As such, this array should be sorted by order of preference: from the most
wanted constraints to the least.
+When the next encountered constraint is set to `null`, the player will simply
+disable the video track. If you want to disable the video track by default,
+you can just set `null` as the first element of this array (e.g. `[null]`).
+
Here is all the possible constraints you can set in any one of those objects
(note that all properties are optional here, only those set will have an effect
on which tracks will be filtered):
diff --git a/doc/api/player_options.md b/doc/api/player_options.md
index 7d26f63b93..29caa63e72 100644
--- a/doc/api/player_options.md
+++ b/doc/api/player_options.md
@@ -413,6 +413,10 @@ it and check with the second object and so on.
As such, this array should be sorted by order of preference: from the most
wanted constraints to the least.
+When the next encountered constraint is set to `null`, the player will simply
+disable the video track. If you want to disable the video track by default,
+you can just set `null` as the first element of this array (e.g. `[null]`).
+
Here is all the possible constraints you can set in any one of those objects
(note that all properties are optional here, only those set will have an effect
on which tracks will be filtered):
From e06df12235d4911cdafbb07f21ff9d694ac5b0e0 Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Thu, 2 Apr 2020 18:49:09 +0200
Subject: [PATCH 23/72] manifest: canBeOutOfSyncError now also takes the
corresponding segment in argument
---
src/core/buffers/representation/representation_buffer.ts | 2 +-
src/manifest/representation_index/types.ts | 8 +++++---
src/parsers/manifest/metaplaylist/representation_index.ts | 5 +++--
3 files changed, 9 insertions(+), 6 deletions(-)
diff --git a/src/core/buffers/representation/representation_buffer.ts b/src/core/buffers/representation/representation_buffer.ts
index 87e958428d..f3a2b99635 100644
--- a/src/core/buffers/representation/representation_buffer.ts
+++ b/src/core/buffers/representation/representation_buffer.ts
@@ -451,7 +451,7 @@ export default function RepresentationBuffer({
const { index } = representation;
if (index.isSegmentStillAvailable(retriedSegment) === false) {
reCheckNeededSegments$.next();
- } else if (index.canBeOutOfSyncError(evt.value.error)) {
+ } else if (index.canBeOutOfSyncError(evt.value.error, retriedSegment)) {
return observableOf(EVENTS.manifestMightBeOufOfSync());
}
return EMPTY;
diff --git a/src/manifest/representation_index/types.ts b/src/manifest/representation_index/types.ts
index 8fa3ad1062..b025031106 100644
--- a/src/manifest/representation_index/types.ts
+++ b/src/manifest/representation_index/types.ts
@@ -184,14 +184,16 @@ export default interface IRepresentationIndex {
isSegmentStillAvailable(segment : ISegment) : boolean | undefined;
/**
- * Returns true if the Error given can indicate that the local index became
- * "unsynchronized" with the server.
+ * Returns true if the `error` given following the request of `segment` can
+ * indicate that the index became "unsynchronized" with the server.
* Some transport cannot become unsynchronized and can return false directly.
* Note: This API assumes that the user first checked that the segment is
* still available through `isSegmentStillAvailable`.
+ * @param {Error} error
+ * @param {Object} segment
* @returns {Boolean}
*/
- canBeOutOfSyncError(error : ICustomError) : boolean;
+ canBeOutOfSyncError(error : ICustomError, segment : ISegment) : boolean;
/**
* Checks if the given time - in seconds - is in a discontinuity. That is:
diff --git a/src/parsers/manifest/metaplaylist/representation_index.ts b/src/parsers/manifest/metaplaylist/representation_index.ts
index 23b0e8c865..13558e2f1e 100644
--- a/src/parsers/manifest/metaplaylist/representation_index.ts
+++ b/src/parsers/manifest/metaplaylist/representation_index.ts
@@ -158,10 +158,11 @@ export default class MetaRepresentationIndex implements IRepresentationIndex {
/**
* @param {Error} error
+ * @param {Object} segment
* @returns {Boolean}
*/
- public canBeOutOfSyncError(error : ICustomError) : boolean {
- return this._wrappedIndex.canBeOutOfSyncError(error);
+ public canBeOutOfSyncError(error : ICustomError, segment : ISegment) : boolean {
+ return this._wrappedIndex.canBeOutOfSyncError(error, segment);
}
/**
From 43ed73441364c7233bec27bc6d43b89175b0b78c Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Wed, 15 Apr 2020 19:43:28 +0200
Subject: [PATCH 24/72] doc: update api documentation landing page to be easier
to read
---
doc/api/index.md | 1891 ++++++++++++++++++++++++++++------------------
1 file changed, 1149 insertions(+), 742 deletions(-)
diff --git a/doc/api/index.md b/doc/api/index.md
index 2ca82a5b22..6c38792eff 100644
--- a/doc/api/index.md
+++ b/doc/api/index.md
@@ -10,9 +10,8 @@
- [ErrorTypes](#static-ErrorTypes)
- [ErrorCodes](#static-ErrorCodes)
- [LogLevel](#static-LogLevel)
-- [Methods](#meth)
+- [Basic methods](#meth-group-basic)
- [loadVideo](#meth-loadVideo)
- - [getVideoElement](#meth-getVideoElement)
- [getPlayerState](#meth-getPlayerState)
- [addEventListener](#meth-addEventListener)
- [removeEventListener](#meth-removeEventListener)
@@ -21,40 +20,29 @@
- [stop](#meth-stop)
- [getPosition](#meth-getPosition)
- [getWallClockTime](#meth-getWallClockTime)
+ - [seekTo](#meth-seekTo)
+ - [getMinimumPosition](#meth-getMinimumPosition)
+ - [getMaximumPosition](#meth-getMaximumPosition)
- [getVideoDuration](#meth-getVideoDuration)
- - [getVolume](#meth-getVolume)
- [getError](#meth-getError)
- - [seekTo](#meth-seekTo)
- - [isLive](#meth-isLive)
- - [getUrl](#meth-getUrl)
- - [getAvailableVideoBitrates](#meth-getAvailableVideoBitrates)
- - [getAvailableAudioBitrates](#meth-getAvailableAudioBitrates)
- - [getVideoBitrate](#meth-getVideoBitrate)
- - [getAudioBitrate](#meth-getAudioBitrate)
- - [getMaxVideoBitrate](#meth-getMaxVideoBitrate)
- - [getMaxAudioBitrate](#meth-getMaxAudioBitrate)
- - [setVideoBitrate](#meth-setVideoBitrate)
- - [setAudioBitrate](#meth-setAudioBitrate)
- - [getManualVideoBitrate](#meth-getManualVideoBitrate)
- - [getManualAudioBitrate](#meth-getManualAudioBitrate)
- - [setWantedBufferAhead](#meth-setWantedBufferAhead)
- - [getWantedBufferAhead](#meth-getWantedBufferAhead)
- - [setMaxBufferBehind](#meth-setMaxBufferBehind)
- - [getMaxBufferBehind](#meth-getMaxBufferBehind)
- - [setMaxBufferAhead](#meth-setMaxBufferAhead)
- - [getMaxBufferAhead](#meth-getMaxBufferAhead)
- - [setMaxVideoBitrate](#meth-setMaxVideoBitrate)
- - [setMaxAudioBitrate](#meth-setMaxAudioBitrate)
+ - [getVideoElement](#meth-getVideoElement)
+ - [dispose](#meth-dispose)
+ - [Speed control](#meth-group-speed-control)
+ - [setPlaybackRate](#meth-setPlaybackRate)
+ - [getPlaybackRate](#meth-getPlaybackRate)
+ - [Volume control](#meth-group-volume-control)
- [setVolume](#meth-setVolume)
+ - [getVolume](#meth-getVolume)
- [mute](#meth-mute)
- [unMute](#meth-unMute)
- [isMute](#meth-isMute)
- - [getAvailableAudioTracks](#meth-getAvailableAudioTracks)
- - [getAvailableTextTracks](#meth-getAvailableTextTracks)
- - [getAvailableVideoTracks](#meth-getAvailableVideoTracks)
+ - [Track selection](#meth-track-selection)
- [getAudioTrack](#meth-getAudioTrack)
- [getTextTrack](#meth-getTextTrack)
- [getVideoTrack](#meth-getVideoTrack)
+ - [getAvailableAudioTracks](#meth-getAvailableAudioTracks)
+ - [getAvailableTextTracks](#meth-getAvailableTextTracks)
+ - [getAvailableVideoTracks](#meth-getAvailableVideoTracks)
- [setAudioTrack](#meth-setAudioTrack)
- [setTextTrack](#meth-setTextTrack)
- [disableTextTrack](#meth-disableTextTrack)
@@ -66,17 +54,38 @@
- [getPreferredTextTracks](#meth-getPreferredTextTracks)
- [setPreferredVideoTracks](#meth-setPreferredVideoTracks)
- [getPreferredVideoTracks](#meth-getPreferredVideoTracks)
- - [getCurrentAdaptations](#meth-getCurrentAdaptations)
- - [getCurrentRepresentations](#meth-getCurrentRepresentations)
- - [dispose](#meth-dispose)
+ - [Bitrate selection](#meth-group-bitrate-selection)
+ - [getAvailableVideoBitrates](#meth-getAvailableVideoBitrates)
+ - [getAvailableAudioBitrates](#meth-getAvailableAudioBitrates)
+ - [getVideoBitrate](#meth-getVideoBitrate)
+ - [getAudioBitrate](#meth-getAudioBitrate)
+ - [setMaxVideoBitrate](#meth-setMaxVideoBitrate)
+ - [setMaxAudioBitrate](#meth-setMaxAudioBitrate)
+ - [getMaxVideoBitrate](#meth-getMaxVideoBitrate)
+ - [getMaxAudioBitrate](#meth-getMaxAudioBitrate)
+ - [setVideoBitrate](#meth-setVideoBitrate)
+ - [setAudioBitrate](#meth-setAudioBitrate)
+ - [getManualVideoBitrate](#meth-getManualVideoBitrate)
+ - [getManualAudioBitrate](#meth-getManualAudioBitrate)
+ - [Buffer control](#meth-group-buffer-control)
+ - [setWantedBufferAhead](#meth-setWantedBufferAhead)
+ - [getWantedBufferAhead](#meth-getWantedBufferAhead)
+ - [setMaxBufferBehind](#meth-setMaxBufferBehind)
+ - [getMaxBufferBehind](#meth-getMaxBufferBehind)
+ - [setMaxBufferAhead](#meth-setMaxBufferAhead)
+ - [getMaxBufferAhead](#meth-getMaxBufferAhead)
+ - [Buffer information](#meth-group-buffer-info)
- [getVideoLoadedTime](#meth-getVideoLoadedTime)
- [getVideoPlayedTime](#meth-getVideoPlayedTime)
- [getVideoBufferGap](#meth-getVideoBufferGap)
- - [getPlaybackRate](#meth-getPlaybackRate)
- - [setPlaybackRate](#meth-setPlaybackRate)
+ - [Content information](#meth-group-content-info)
+ - [isLive](#meth-isLive)
+ - [getUrl](#meth-getUrl)
+ - [getManifest](#meth-getManifest)
+ - [getCurrentAdaptations](#meth-getCurrentAdaptations)
+ - [getCurrentRepresentations](#meth-getCurrentRepresentations)
- [getCurrentKeySystem](#meth-getCurrentKeySystem)
- - [getMinimumPosition](#meth-getMinimumPosition)
- - [getMaximumPosition](#meth-getMaximumPosition)
+ - [Deprecated](#meth-group-deprecated)
- [getImageTrackData (deprecated)](#meth-getImageTrackData)
- [setFullscreen (deprecated)](#meth-setFullscreen)
- [exitFullscreen (deprecated)](#meth-exitFullscreen)
@@ -133,6 +142,9 @@ page](./player_options.md).
## Static properties ###########################################################
+This chapter documents the static properties that can be found on the RxPlayer
+class.
+
### version ####################################################################
@@ -190,6 +202,7 @@ If the value set to this property is different than those, it will be
automatically set to ``"NONE"``.
#### Example
+
```js
import RxPlayer from "rx-player";
RxPlayer.LogLevel = "WARNING";
@@ -198,7 +211,10 @@ RxPlayer.LogLevel = "WARNING";
-## Methods #####################################################################
+## Basic methods ###############################################################
+
+In this chapter, we will go through the basic methods you will need to use when
+playing a content through the RxPlayer.
### loadVideo ##################################################################
@@ -206,12 +222,16 @@ RxPlayer.LogLevel = "WARNING";
_arguments_:
- _options_ (``Object``)
-Loads a new video described in the argument.
+Loads the content described in the argument.
+This is the central method to use when you want to play a new content.
The options possible as arguments are all defined in [this
page](./loadVideo_options.md).
+Despite its name, this method can also load audio-only content.
+
#### Example
+
```js
player.loadVideo({
url: "http://vm2.dashif.org/livesim-dev/segtimeline_1/testpic_6s/Manifest.mpd",
@@ -221,28 +241,12 @@ player.loadVideo({
```
-
-### getVideoElement ############################################################
-
-_return value_: ``HTMLMediaElement``
-
-Returns the video element used by the player.
-
-You're not encouraged to use its API, you should always prefer the Player's API.
-
-#### Example
-```js
-const videoElement = player.getVideoElement();
-videoElement.className = "my-video-element";
-```
-
-
### getPlayerState #############################################################
_return value_: ``string``
-The current player's state.
+The "state" the player is currently in.
Can be either one of those strings:
- ``"STOPPED"``: The player is idle. No content is loading nor is loaded.
@@ -278,6 +282,7 @@ As it is a central part of our API and can be difficult concept to understand,
we have a special [page of documentation on player states](./states.md).
#### Example
+
```js
switch (player.getPlayerState()) {
case "STOPPED":
@@ -328,10 +333,15 @@ _arguments_:
Add an event listener to trigger a callback as it happens. The callback will
have the event payload as a single argument.
+The RxPlayer API is heavily event-based. As an example: to know when a content
+is loaded, the most straightforward way is to add an event listener for the
+`"playerStateChange"` event. This can be done only through this method.
+
To have the complete list of player events, consult the [Player events
page](./player_events.md).
#### Example
+
```js
player.addEventListener("Error", function(err) {
console.log(`The player crashed: ${err.message}`);
@@ -347,14 +357,16 @@ _arguments_:
- _callback_ (optional) (``Function``): The callback given when calling the
corresponding ``addEventListener`` API.
-Remove an event listener. That is, stop your registered callback (with
-``addEventListener``) to be called as events happen and free up ressources.
+Remove an event listener.
+That is, remove a callback previously registered with ``addEventListener`` from
+being triggered on the corresponding event. This also free-up the corresponding
+ressources.
The callback given is optional: if not given, _every_ registered callback to
-that event will be removed. That's why using both arguments is recommended for
-most usecase.
+that event will be removed.
#### Example
+
```js
player.removeEventListener("playerStateChange", listenerCallback);
```
@@ -367,6 +379,10 @@ _return value_: ``Promise.``
Play/resume the current video. Equivalent to a video element's play method.
+You might want to call that method either to start playing (when the content is
+in the `"LOADED"` state and auto-play has not been enabled in the last
+`loadVideo` call) or to resume when the content has been paused.
+
The returned Promise informs you on the result:
- if playback succeeds, the Promise is fulfilled
@@ -386,6 +402,7 @@ implementation has the exact same implementation than [ES2015
Promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
#### Example
+
```js
const resumeContent = () => {
player.play();
@@ -402,6 +419,7 @@ Note that a content can be paused even if its current state is ``BUFFERING`` or
``SEEKING``.
#### Example
+
```js
const pauseContent = () => {
player.pause();
@@ -414,7 +432,11 @@ const pauseContent = () => {
Stop playback of the current content if one.
+This will totaly un-load the current content. To re-start playing the same
+content, you will need to call `loadVideo` again.
+
#### Example
+
```js
const stopVideo = () => {
player.stop();
@@ -427,12 +449,24 @@ const stopVideo = () => {
_return value_: ``Number``
-Returns the video element's current position, in seconds.
+Returns the current media element's playing position, in seconds.
+
+For live contents, the returned position will not be re-scaled to correspond to
+a live timestamp. If you want that behavior, you can call `getWallClockTime`
+instead.
+
+This is the only difference between the two. Generally, you can follow the
+following rule:
-The difference with the ``getWallClockTime`` method is that for live contents
-the position is not re-calculated to match a live timestamp.
+ - if you want to use that current position to use it with the other APIs
+ (like `seekTo`, `getMinimumPosition`, `getMaximumPosition`
+ etc.) use `getPosition`.
+
+ - if you want to display the current position to the viewer/listener, use
+ `getWallClockTime` instead.
#### Example
+
```js
const pos = player.getPosition();
console.log(`The video element's current position is: ${pos} second(s)`);
@@ -444,15 +478,21 @@ console.log(`The video element's current position is: ${pos} second(s)`);
_return value_: ``Number``
-Returns the wall-clock-time of the current position in seconds.
+Returns the current "wall-clock" playing position in seconds.
That is:
- - for live content, get a timestamp in seconds of the current position.
- - for static content, returns the position from beginning, also in seconds.
+
+ - for live contents, this is the current position scaled to correspond to a
+ live timestamp, in seconds.
+
+ - for non-live contents, returns the position from the absolute beginning time
+ of the content, also in seconds. In the absolute majority of cases this will
+ be equal to the value returned by `getPosition`.
Use this method to display the current position to the user.
#### Example
+
```js
const wallClockTime = player.getWallClockTime();
const nowInSeconds = Date.now() / 1000;
@@ -466,75 +506,12 @@ if (delta < 5) { // (5 seconds of margin)
```
-
-### getVideoDuration ###########################################################
-
-_return value_: ``Number``
-
-Returns the duration of the current video, directly from the video element.
-
-#### Example
-```js
-const pos = player.getPosition();
-const dur = player.getVideoDuration();
-
-console.log(`current position: ${pos} / ${dur}`);
-```
-
-
-
-### getVolume ##################################################################
-
-_return value_: ``Number``
-
-Current volume of the player, from 0 (no sound) to 1 (maximum sound). 0 if muted
-(different than videoElement.muted).
-
-#### Example
-```js
-const volume = player.getVolume();
-
-if (volume === 1) {
- console.log("You're playing at maximum volume");
-} else if (volume === 0) {
- console.log("You're playing at no volume");
-} else if (volume > 0.5) {
- console.log("You're playing at a high volume");
-} else {
- console.log("You're playing at a low volume");
-}
-```
-
-
-
-### getError ###################################################################
-
-_return value_: ``Error|null``
-
-Returns the fatal error if it happened. null otherwise.
-
-See [the Player Error documentation](./errors.md) for more information.
-
-#### Example
-```js
-const error = player.getError();
-
-if (!error) {
- console.log("The player did not crash");
-} else if (error.code === "PIPELINE_LOAD_ERROR") {
- console.error("The player crashed due to a failing request");
-} else {
- console.error(`The player crashed: ${error.code}`);
-}
-```
-
-
### seekTo #####################################################################
_arguments_: ``Object|Number``
-Seek in the current content.
+Seek in the current content (i.e. change the current position).
The argument can be an object with a single ``Number`` property, either:
@@ -549,7 +526,8 @@ The argument can be an object with a single ``Number`` property, either:
The argument can also just be a ``Number`` property, which will have the same
effect than the ``position`` property (absolute position).
-#### Example
+#### Examples
+
```js
// seeking to 54 seconds from the start of the content
player.seekTo({ position: 54 });
@@ -568,434 +546,456 @@ player.seekTo({ wallClockTime: Date.now() / 1000 });
```
-
-### isLive #####################################################################
+
+### getMinimumPosition #########################################################
-_return value_: ``Boolean``
+_return value_: ``Number|null``
-Returns ``true`` if the content is a "live" content (e.g. a live TV Channel).
-``false`` otherwise.
+The minimum seekable player position. ``null`` if no content is loaded.
-Also ``false`` if no content is loaded yet.
+This is useful for live contents, where server-side buffer size are often not
+infinite. This method allows thus to seek at the earliest possible time.
#### Example
+
```js
-if (player.isLive()) {
- console.log("We're playing a live content");
-}
+// seeking to the earliest position possible (beginning of the buffer for live
+// contents, position '0' for non-live contents).
+player.seekTo({ position: player.getMinimumPosition() });
```
-
-### getUrl #####################################################################
-
-_return value_: ``string|undefined``
+
+### getMaximumPosition #########################################################
-Returns the URL of the downloaded [Manifest](../terms.md#manifest).
+_return value_: ``Number|null``
-In _DirectFile_ mode (see [loadVideo
-options](./loadVideo_options.md#prop-transport)), returns the URL of the content
-being played.
+The maximum seekable player position. ``null`` if no content is loaded.
-Returns ``undefined`` if no content is loaded yet.
+This is useful for live contents, where the buffer end updates continously.
+This method allows thus to seek directly at the live edge of the content.
#### Example
+
```js
-const url = player.getUrl();
-if (url) {
- console.log("We are playing the following content:", url);
-}
+// seeking to the end
+player.seekTo({
+ position: player.getMaximumPosition()
+});
```
-
-### getAvailableVideoBitrates ##################################################
+
+### getVideoDuration ###########################################################
-_return value_: ``Array.``
+_return value_: ``Number``
-The different bitrates available for the current video
-[Adaptation](../terms.md#adaptation), in bits per seconds.
+Returns the duration of the current video as taken from the video element.
+
+:warning: This duration is in fact the maximum position possible for the
+content. As such, for contents not starting at `0`, this value will not be equal
+to the difference between the maximum and minimum possible position, as would
+normally be expected from a property named "duration".
-In _DirectFile_ mode (see [loadVideo
-options](./loadVideo_options.md#prop-transport)), returns an empty Array.
#### Example
+
+
```js
-const videoBitrates = player.getAvailableVideoBitrates();
-if (videoBitrates.length) {
- console.log(
- "The current video is available in the following bitrates",
- videoBitrates.join(", ")
- );
-}
+const pos = player.getPosition();
+const dur = player.getVideoDuration();
+
+console.log(`current position: ${pos} / ${dur}`);
```
-
-### getAvailableAudioBitrates ##################################################
+
+### getError ###################################################################
-_return value_: ``Array.``
+_return value_: ``Error|null``
-The different bitrates available for the current audio
-[Adaptation](../terms.md#adaptation), in bits per seconds.
+Returns the current "fatal error" if one happenned for the last loaded content.
-In _DirectFile_ mode (see [loadVideo
-options](./loadVideo_options.md#prop-transport)), returns an empty Array.
+Returns `null` otherwise.
+
+A "fatal error" is an error which led the current loading/loaded content to
+completely stop.
+Such errors are usually also sent through the `"error"` event when they happen.
+
+See [the Player Error documentation](./errors.md) for more information.
#### Example
+
```js
-const audioBitrates = player.getAvailableAudioBitrates();
-if (audioBitrates.length) {
- console.log(
- "The current audio is available in the following bitrates",
- audioBitrates.join(", ")
- );
+const error = player.getError();
+
+if (!error) {
+ console.log("The player did not crash");
+} else if (error.code === "PIPELINE_LOAD_ERROR") {
+ console.error("The player crashed due to a failing request");
+} else {
+ console.error(`The player crashed: ${error.code}`);
}
```
-
-### getVideoBitrate ############################################################
-
-_return value_: ``Number|undefined``
+
+### getVideoElement ############################################################
-Returns the video bitrate of the last downloaded video segment, in bits per
-seconds.
+_return value_: ``HTMLMediaElement``
-Returns ``undefined`` if no content is loaded.
+Returns the media element used by the RxPlayer.
-In _DirectFile_ mode (see [loadVideo
-options](./loadVideo_options.md#prop-transport)), returns ``undefined``.
+You're not encouraged to use its APIs as they can enter in conflict with the
+RxPlayer's API.
+Despite its name, this method can also return an audio element if the RxPlayer
+was instantiated with one.
-
-### getAudioBitrate ############################################################
+#### Example
-_return value_: ``Number|undefined``
+```js
+const videoElement = player.getVideoElement();
+videoElement.className = "my-video-element";
+```
-Returns the audio bitrate of the last downloaded audio segment, in bits per
-seconds.
-Returns ``undefined`` if no content is loaded.
+
+### dispose ####################################################################
-In _DirectFile_ mode (see [loadVideo
-options](./loadVideo_options.md#prop-transport)), returns ``undefined``.
+Free the ressources used by the player.
+You can call this method if you know you won't need the RxPlayer anymore.
-
-### getMaxVideoBitrate #########################################################
+:warning: The player won't work correctly after calling this method.
-_return value_: ``Number|undefined``
-Returns the maximum set video bitrate to which switching is possible, in bits
-per seconds.
-This only affects adaptive strategies (you can bypass this limit by calling
-``setVideoBitrate``), and is set to ``Infinity`` when no limit has been set.
+
+## Speed control ###############################################################
+The following methods allows to update the current speed of playback (also
+called the "playback rate").
-
-### getMaxAudioBitrate #########################################################
-_return value_: ``Number"undefined``
+
+### setPlaybackRate ############################################################
-Returns the maximum set audio bitrate to which switching is possible, in bits
-per seconds.
+_arguments_: ``Number``
-This only affects adaptive strategies (you can bypass this limit by calling
-``setAudioBitrate``), and is set to ``Infinity`` when no limit has been set.
+Updates the current playback rate.
+Setting that value to `1` reset the playback rate to its "normal" rythm.
-
-### setVideoBitrate ############################################################
+Setting it to `2` allows to play at a speed multiplied by 2 relatively to
+regular playback.
-_arguments_: ``Number``
+Setting it to `0.5` allows to play at half the speed relatively to regular
+playback.
-Force the current video track to be of a certain bitrate.
+etc.
-If an video [Representation](../terms.md#representation) (in the current video
-[Adaptation](../terms.md#adaptation)) is found with the exact same bitrate, this
-Representation will be set.
+#### Example
-If no video Representation is found with the exact same bitrate, either:
+```js
+// plays three times faster than normal
+player.setPlaybackRate(3);
+```
- - the video Representation immediately inferior to it will be chosen instead
- (the closest inferior)
- - if no video Representation has a bitrate lower than that value, the video
- Representation with the lowest bitrate will be chosen instead.
+
+### getPlaybackRate ############################################################
+_return value_: ``Number``
-Set to ``-1`` to deactivate (and thus re-activate adaptive streaming for video
-tracks).
+Returns the current video playback rate. ``1`` for normal playback, ``2`` when
+playing at double the speed, etc.
-When active (called with a positive value), adaptive streaming for video tracks
-will be disabled to stay in the chosen Representation.
+#### Example
-You can use ``getAvailableVideoBitrates`` to get the list of available bitrates
-you can set on the current content.
+```js
+const currentPlaybackRate = player.getPlaybackRate();
+console.log(`Playing at a x${currentPlaybackRate}} speed`);
+```
-Note that the value set is persistent between ``loadVideo`` calls.
-As such, this method can also be called when no content is playing (the same
-rules apply for future contents).
----
-:warning: This option will have no effect for contents loaded in _DirectFile_
-mode (see [loadVideo options](./loadVideo_options.md#prop-transport)).
+
+## Volume control ##############################################################
----
+Those methods allows to have control over the current audio volume of playing
+contents.
-
-### setAudioBitrate ############################################################
+
+### setVolume ##################################################################
_arguments_: ``Number``
-Force the current audio track to be of a certain bitrate.
+Set the current volume, from 0 (no sound) to 1 (the maximum sound level).
+
+Note that the volume set here is persisted even when loading another content.
+As such, this method can also be called when no content is currently playing.
-If an audio [Representation](../terms.md#representation) (in the current audio
-[Adaptation](../terms.md#adaptation)) is found with the exact same bitrate, this
-Representation will be set.
+#### Example
+
+```js
+// set the full volume
+player.setVolume(1);
+```
-If no audio Representation is found with the exact same bitrate, either:
- - the audio Representation immediately inferior to it will be chosen instead
- (the closest inferior)
+
+### getVolume ##################################################################
- - if no audio Representation has a bitrate lower than that value, the audio
- Representation with the lowest bitrate will be chosen instead.
+_return value_: ``Number``
+Current volume of the player, from 0 (no sound) to 1 (maximum sound).
+0 if muted through the `mute` API.
-Set to ``-1`` to deactivate (and thus re-activate adaptive streaming for audio
-tracks).
+As the volume is not dependent on a single content (it is persistent), this
+method can also be called when no content is playing.
-When active (called with a positive value), adaptive streaming for audio tracks
-will be disabled to stay in the chosen Representation.
+#### Example
-You can use ``getAvailableAudioBitrates`` to get the list of available bitrates
-you can set on the current content.
+```js
+const volume = player.getVolume();
-Note that the value set is persistent between ``loadVideo`` calls.
-As such, this method can also be called when no content is playing (the same
-rules apply for future contents).
+if (volume === 1) {
+ console.log("You're playing at maximum volume");
+} else if (volume === 0) {
+ console.log("You're playing at no volume");
+} else if (volume > 0.5) {
+ console.log("You're playing at a high volume");
+} else {
+ console.log("You're playing at a low volume");
+}
+```
----
-:warning: This option will have no effect for contents loaded in _DirectFile_
-mode (see [loadVideo options](./loadVideo_options.md#prop-transport)).
+
+### mute #######################################################################
----
+Mute the volume.
+Basically set the volume to 0 while keeping in memory the previous volume to
+reset it at the next `unMute` call.
+As the volume is not dependent on a single content (it is persistent), this
+method can also be called when no content is playing.
-
-### getManualVideoBitrate ######################################################
+#### Example
-_arguments_: ``Number``
+```js
+// mute the current volume
+player.mute();
+```
-Get the last video bitrate manually set. Either via ``setVideoBitrate`` or via
-the ``initialVideoBitrate`` constructor option.
-This value can be different than the one returned by ``getVideoBitrate``:
- - ``getManualVideoBitrate`` returns the last bitrate set manually by the user
- - ``getVideoBitrate`` returns the actual bitrate of the current video track
+
+### unMute #####################################################################
-``-1`` when no video bitrate is forced.
+When muted, restore the volume to the one previous to the last ``mute`` call.
+When the volume is already superior to `0`, this call won't do anything.
-
-### getManualAudioBitrate ######################################################
+As the volume is not dependent on a single content (it is persistent), this
+method can also be called when no content is playing.
-_arguments_: ``Number``
+#### Example
-Get the last audio bitrate manually set. Either via ``setAudioBitrate`` or via
-the ``initialAudioBitrate`` constructor option.
+```js
+// mute the current volume
+player.mute();
-This value can be different than the one returned by ``getAudioBitrate``:
- - ``getManualAudioBitrate`` returns the last bitrate set manually by the user
- - ``getAudioBitrate`` returns the actual bitrate of the current audio track
+// unmute and restore the previous volume
+player.unMute();
+```
-``-1`` when no audio bitrate is forced.
+
+### isMute #####################################################################
-
-### setMaxVideoBitrate #########################################################
+_returns_: ``Boolean``
-_arguments_: ``Number``
+Returns true if the volume is set to `0`.
-Set the maximum video bitrate reachable through adaptive streaming. The player
-will never automatically switch to a video
-[Representation](../terms.md#representation) with a higher bitrate.
+#### Example
-This limit can be removed by setting it to ``Infinity``:
```js
-// remove video bitrate limit
-player.setMaxVideoBitrate(Infinity);
+if (player.isMute()) {
+ console.log("The content plays with no sound.");
+}
```
-This only affects adaptive strategies (you can bypass this limit by calling
-``setVideoBitrate``).
-
----
-:warning: This option will have no effect for contents loaded in _DirectFile_
-mode (see [loadVideo options](./loadVideo_options.md#prop-transport)).
----
+
+## Track selection #############################################################
+The following methods allows to choose the right video audio or text track and
+to obtain information about the currently playing tracks.
-
-### setMaxAudioBitrate #########################################################
-_arguments_: ``Number``
+
+### getAudioTrack ##############################################################
-Set the maximum audio bitrate reachable through adaptive streaming. The player
-will never automatically switch to a audio
-[Representation](../terms.md#representation) with a higher bitrate.
+_returns_: ``Object|null|undefined``
-This limit can be removed by setting it to ``Infinity``:
-```js
-// remove audio bitrate limit
-player.setMaxAudioBitrate(Infinity);
-```
+Get information about the audio track currently set.
+``null`` if no audio track is enabled right now.
-This only affects adaptive strategies (you can bypass this limit by calling
-``setAudioBitrate``).
+If an audio track is set and information about it is known, this method will
+return an object with the following properties:
----
+ - ``id`` (``Number|string``): The id used to identify this track. No other
+ audio track for the same [Period](../terms.md#period) will have the
+ same `id`.
-:warning: This option will have no effect for contents loaded in _DirectFile_
-mode (see [loadVideo options](./loadVideo_options.md#prop-transport)).
+ This can be useful when setting the track through the `setAudioTrack`
+ method.
----
+ - ``language`` (``string``): The language the audio track is in, as set in the
+ [Manifest](../terms.md#manifest).
+ - ``normalized`` (``string``): An attempt to translate the ``language``
+ property into an ISO 639-3 language code (for now only support translations
+ from ISO 639-1 and ISO 639-3 language codes). If the translation attempt
+ fails (no corresponding ISO 639-3 language code is found), it will equal the
+ value of ``language``
-
-### setWantedBufferAhead #######################################################
+ - ``audioDescription`` (``Boolean``): Whether the track is an audio
+ description (for the visually impaired or not).
-_arguments_: ``Number``
+ - ``dub`` (``Boolean|undefined``): If set to `true`, this audio track is a
+ "dub", meaning it was recorded in another language than the original.
+ If set to `false`, we know that this audio track is in an original language.
+ This property is `undefined` if we do not known whether it is in an original
+ language.
-Set the buffering goal, as a duration ahead of the current position, in seconds.
-Once this size of buffer reached, the player won't try to download new video
-segments anymore.
----
+``undefined`` if no audio content has been loaded yet or if its information is
+unknown.
-:warning: This option will have no effect for contents loaded in _DirectFile_
-mode (see [loadVideo options](./loadVideo_options.md#prop-transport)).
+--
----
+Note for multi-Period contents:
+This method will only return the chosen audio track for the
+[Period](../terms.md#period) that is currently playing.
-
-### getWantedBufferAhead #######################################################
+__
-_return value_: ``Number``
-_defaults_: ``30``
+In _DirectFile_ mode
+(see [loadVideo options](./loadVideo_options.md#prop-transport)), if there is
+no audio tracks API in the browser, this method will return ``undefined``.
-returns the buffering goal, as a duration ahead of the current position, in
-seconds.
+
+### getTextTrack ###############################################################
-
-### setMaxBufferBehind #########################################################
+_returns_: ``Object|null|undefined``
-_arguments_: ``Number``
+Get information about the text track currently set.
+``null`` if no audio track is enabled right now.
-Set the maximum kept past buffer, in seconds.
-Everything before that limit (``currentPosition - maxBufferBehind``) will be
-automatically garbage collected.
+If a text track is set and information about it is known, this method will
+return an object with the following properties:
-This feature is not necessary as the browser is already supposed to deallocate
-memory from old segments if/when the memory is scarce.
+ - ``id`` (``Number|string``): The id used to identify this track. No other
+ text track for the same [Period](../terms.md#period) will have the same
+ `id`.
-However on some custom targets, or just to better control the memory imprint
-of the player, you might want to set this limit. You can set it to
-``Infinity`` to remove any limit and just let the browser do this job.
+ This can be useful when setting the track through the `setTextTrack` method.
----
+ - ``language`` (``string``): The language the text track is in, as set in the
+ [Manifest](../terms.md#manifest).
-:warning: This option will have no effect for contents loaded in _DirectFile_
-mode (see [loadVideo options](./loadVideo_options.md#prop-transport)).
+ - ``normalized`` (``string``): An attempt to translate the ``language``
+ property into an ISO 639-3 language code (for now only support translations
+ from ISO 639-1 and ISO 639-3 language codes). If the translation attempt
+ fails (no corresponding ISO 639-3 language code is found), it will equal the
+ value of ``language``
----
+ - ``closedCaption`` (``Boolean``): Whether the track is specially adapted for
+ the hard of hearing or not.
-
-### getMaxBufferBehind #########################################################
+``undefined`` if no text content has been loaded yet or if its information is
+unknown.
-_return value_: ``Number``
-_defaults_: ``Infinity``
+--
-Returns the maximum kept past buffer, in seconds.
+Note for multi-Period contents:
+This method will only return the chosen text track for the
+[Period](../terms.md#period) that is currently playing.
-
-### setMaxBufferAhead ##########################################################
+__
-_arguments_: ``Number``
+In _DirectFile_ mode
+(see [loadVideo options](./loadVideo_options.md#prop-transport)), if there is
+no text tracks API in the browser, this method will return ``undefined``.
-Set the maximum kept buffer ahead of the current position, in seconds.
-Everything superior to that limit (``currentPosition + maxBufferAhead``) will
-be automatically garbage collected. This feature is not necessary as
-the browser is already supposed to deallocate memory from old segments if/when
-the memory is scarce.
-However on some custom targets, or just to better control the memory imprint of
-the player, you might want to set this limit. You can set it to ``Infinity`` to
-remove any limit and just let the browser do this job.
+
+### getVideoTrack ##############################################################
-The minimum value between this one and the one returned by
-``getWantedBufferAhead`` will be considered when downloading new segments.
+_returns_: ``Object|null|undefined``
-:warning: Bear in mind that a too-low configuration there (e.g. inferior to
-``10``) might prevent the browser to play the content at all.
+Get information about the video track currently set.
+``null`` if no audio track is enabled right now.
----
+If a video track is set and information about it is known, this method will
+return an object with the following properties:
-:warning: This option will have no effect for contents loaded in _DirectFile_
-mode (see [loadVideo options](./loadVideo_options.md#prop-transport)).
+ - ``id`` (``Number|string``): The id used to identify this track. No other
+ video track for the same [Period](../terms.md#period) will have the same
+ `id`.
----
+ This can be useful when setting the track through the `setVideoTrack`
+ method.
+ - ``representations`` (``Array.``):
+ [Representations](../terms.md#representation) of this video track, with
+ attributes:
-
-### getMaxBufferAhead ##########################################################
+ - ``id`` (``string``): The id used to identify this Representation.
+ No other Representation from this track will have the same `id`.
-_return value_: ``Number``
-_defaults_: ``Infinity``
+ - ``bitrate`` (``Number``): The bitrate of this Representation, in bits per
+ seconds.
-Returns the maximum kept buffer ahead of the current position, in seconds.
+ - ``width`` (``Number|undefined``): The width of video, in pixels.
+ - ``height`` (``Number|undefined``): The height of video, in pixels.
-
-### setVolume ##################################################################
+ - ``codec`` (``string|undefined``): The codec given in standard MIME type
+ format.
-_arguments_: ``Number``
+ - ``frameRate`` (``string|undefined``): The video frame rate.
-Set the new volume, from 0 (no sound) to 1 (the maximum sound level).
+ - ``signInterpreted`` (``Boolean|undefined``): If set to `true`, the track is
+ known to contain an interpretation in sign language.
+ If set to `false`, the track is known to not contain that type of content.
+ If not set or set to undefined we don't know whether that video track
+ contains an interpretation in sign language.
-
-### mute #######################################################################
+``undefined`` if no video content has been loaded yet or if its information is
+unknown.
-Cut the volume. Basically set the volume to 0 while keeping in memory the
-previous volume.
+--
+Note for multi-Period contents:
-
-### unMute #####################################################################
+This method will only return the chosen video track for the
+[Period](../terms.md#period) that is currently playing.
-Restore the volume when it has been muted, to the one previous the ``mute``
-call.
+--
-
-
-### isMute #####################################################################
-
-_returns_: ``Boolean``
-
-Returns true if the volume is muted i.e., set to 0.
+In _DirectFile_ mode
+(see [loadVideo options](./loadVideo_options.md#prop-transport)), if there is
+no video tracks API in the browser, this method will return ``undefined``.
@@ -1008,7 +1008,7 @@ Returns the list of available audio tracks for the current content.
Each of the objects in the returned array have the following properties:
- ``active`` (``Boolean``): Whether the track is the one currently active or
- not.
+ not. Only maximum one audio track can be active at a time.
- ``id`` (``string``): The id used to identify the track. Use it for
setting the track via ``setAudioTrack``.
@@ -1031,10 +1031,19 @@ Each of the objects in the returned array have the following properties:
This property is `undefined` if we do not known whether it is in an original
language.
+--
-In _DirectFile_ mode
-(see [loadVideo options](./loadVideo_options.md#prop-transport)), if there are
-no supported tracks in the file or no track management API : returns an empty
+Note for multi-Period contents:
+
+This method will only return the available tracks of the
+[Period](../terms.md#period) that is currently playing.
+
+--
+
+
+In _DirectFile_ mode (see [loadVideo
+options](./loadVideo_options.md#prop-transport)), if there are no supported
+tracks in the file or no track management API this method will return an empty
Array.
@@ -1065,10 +1074,19 @@ Each of the objects in the returned array have the following properties:
- ``active`` (``Boolean``): Whether the track is the one currently active or
not.
+--
-In _DirectFile_ mode
-(see [loadVideo options](./loadVideo_options.md#prop-transport)), if there are
-no supported tracks in the file or no track management API : returns an empty
+Note for multi-Period contents:
+
+This method will only return the available tracks of the
+[Period](../terms.md#period) that is currently playing.
+
+--
+
+
+In _DirectFile_ mode (see [loadVideo
+options](./loadVideo_options.md#prop-transport)), if there are no supported
+tracks in the file or no track management API this method will return an empty
Array.
@@ -1111,137 +1129,46 @@ Each of the objects in the returned array have the following properties:
If not set or set to undefined we don't know whether that video track
contains an interpretation in sign language.
+--
-In _DirectFile_ mode
-(see [loadVideo options](./loadVideo_options.md#prop-transport)), if there are
-no supported tracks in the file or no track management API : returns an empty
-Array.
-
-
-
-### getAudioTrack ##############################################################
-
-_returns_: ``Object|null|undefined``
-
-Get the audio track currently set. ``null`` if no audio track is enabled right
-now.
-
-The track is an object with the following properties:
-
- - ``id`` (``Number|string``): The id used to identify the track. Use it for
- setting the track via ``setAudioTrack``.
-
- - ``language`` (``string``): The language the audio track is in, as set in the
- [Manifest](../terms.md#manifest).
-
- - ``normalized`` (``string``): An attempt to translate the ``language``
- property into an ISO 639-3 language code (for now only support translations
- from ISO 639-1 and ISO 639-3 language codes). If the translation attempt
- fails (no corresponding ISO 639-3 language code is found), it will equal the
- value of ``language``
-
- - ``audioDescription`` (``Boolean``): Whether the track is an audio
- description (for the visually impaired or not).
-
- - ``dub`` (``Boolean|undefined``): If set to `true`, this audio track is a
- "dub", meaning it was recorded in another language than the original.
- If set to `false`, we know that this audio track is in an original language.
- This property is `undefined` if we do not known whether it is in an original
- language.
-
-
-``undefined`` if no content has been loaded yet.
-
-In _DirectFile_ mode
-(see [loadVideo options](./loadVideo_options.md#prop-transport)), if there is
-no audio tracks API in the browser, return ``undefined``.
-
-
-### getTextTrack ###############################################################
-
-_returns_: ``Object|null``
-
-Get the audio track currently set. ``null`` if no text track is enabled right
-now.
-
-The track is an object with the following properties:
-
- - ``id`` (``Number|string``): The id used to identify the track. Use it for
- setting the track via ``setTextTrack``.
-
- - ``language`` (``string``): The language the text track is in, as set in the
- [Manifest](../terms.md#manifest).
-
- - ``normalized`` (``string``): An attempt to translate the ``language``
- property into an ISO 639-3 language code (for now only support translations
- from ISO 639-1 and ISO 639-3 language codes). If the translation attempt
- fails (no corresponding ISO 639-3 language code is found), it will equal the
- value of ``language``
-
- - ``closedCaption`` (``Boolean``): Whether the track is specially adapted for
- the hard of hearing or not.
-
-
-``undefined`` if no content has been loaded yet.
-
-In _DirectFile_ mode
-(see [loadVideo options](./loadVideo_options.md#prop-transport)), if there is
-no text tracks API in the browser, return ``undefined``.
-
-
-
-### getVideoTrack ##############################################################
-
-_returns_: ``Object|null|undefined``
-
-Get the video track currently set. ``null`` if no video track is enabled right
-now.
-
-The track is an object with the following properties:
-
- - ``id`` (``string``): The id used to identify the track. Use it for setting
- the track via ``setVideoTrack``.
-
- - ``representations`` (``Array.``):
- [Representations](../terms.md#representation) of this video track, with
- attributes:
-
- - ``id`` (``string``): The id used to identify this Representation.
-
- - ``bitrate`` (``Number``): The bitrate of this Representation, in bits per
- seconds.
-
- - ``width`` (``Number|undefined``): The width of video, in pixels.
+Note for multi-Period contents:
- - ``height`` (``Number|undefined``): The height of video, in pixels.
+This method will only return the available tracks of the
+[Period](../terms.md#period) that is currently playing.
- - ``codec`` (``string|undefined``): The codec given in standard MIME type
- format.
+--
- - ``frameRate`` (``string|undefined``): The video framerate.
+In _DirectFile_ mode (see [loadVideo
+options](./loadVideo_options.md#prop-transport)), if there are no supported
+tracks in the file or no track management API this method will return an empty
+Array.
- - ``signInterpreted`` (``Boolean|undefined``): If set to `true`, the track is
- known to contain an interpretation in sign language.
- If set to `false`, the track is known to not contain that type of content.
- If not set or set to undefined we don't know whether that video track
- contains an interpretation in sign language.
-``undefined`` if no content has been loaded yet.
+
+### setAudioTrack ##############################################################
+_arguments_: ``string|Number``
-In _DirectFile_ mode
-(see [loadVideo options](./loadVideo_options.md#prop-transport)), if there is
-no video tracks API in the browser, return ``undefined``.
+Change the current audio track.
+The argument to this method is the wanted track's `id` property. This `id` can
+for example be obtained on the corresponding track object returned by the
+``getAvailableAudioTracks`` method.
-
-### setAudioTrack ##############################################################
+--
-_arguments_: ``string|Number``
+Note for multi-Period contents:
-Set a new audio track from its id, recuperated from ``getAvailableAudioTracks``.
+This method will only have an effect on the [Period](../terms.md#period) that is
+currently playing.
+If you want to update the track for other Periods as well, you might want to
+either:
+ - update the preferred audio tracks through the
+ [setPreferredAudioTracks](#meth-setPreferredAudioTracks) method.
+ - update the current audio track once a `"periodChange"` event has been
+ received.
----
+--
:warning: If used on Safari, in _DirectFile_ mode, the track change may change
the track on other track type (e.g. changing video track may change subtitle
@@ -1251,10 +1178,9 @@ This has two potential reasons :
- Safari may decide to enable a track for accessibility or user language
convenience (e.g. Safari may switch subtitle to your OS language if you pick
another audio language)
-The user may know through the [audioTrackChange]
-(./player_events.md#events-audioTrackChange) event that the track has changed.
+You can know if another track has changed by listening to the corresponding
+events that the tracks have changed.
----
@@ -1262,9 +1188,27 @@ The user may know through the [audioTrackChange]
_arguments_: ``string``
-Set a new text track from its id, recuperated from ``getAvailableTextTracks``.
+Change the current text (subtitles) track.
----
+The argument to this method is the wanted track's `id` property. This `id` can
+for example be obtained on the corresponding track object returned by the
+``getAvailableTextTracks`` method.
+
+--
+
+Note for multi-Period contents:
+
+This method will only have an effect on the [Period](../terms.md#period) that is
+currently playing.
+If you want to update the track for other Periods as well, you might want to
+either:
+ - update the preferred text tracks through the
+ [setPreferredTextTracks](#meth-setPreferredTextTracks) method.
+ - update the current text track once a `"periodChange"` event has been
+ received.
+
+
+--
:warning: If used on Safari, in _DirectFile_ mode, the track change may change
the track on other track type (e.g. changing video track may change subtitle
@@ -1274,16 +1218,30 @@ This has two potential reasons :
- Safari may decide to enable a track for accessibility or user language
convenience (e.g. Safari may switch subtitle to your OS language if you pick
another audio language)
-The user may know through the [textTrackChange]
-(./player_events.md#events-textTrackChange) event that the track has changed.
-
----
+You can know if another track has changed by listening to the corresponding
+events that the tracks have changed.
### disableTextTrack ###########################################################
-Deactivate the current text track, if one.
+Disable the current text track, if one.
+
+After calling that method, no subtitles track will be displayed until
+`setTextTrack` is called.
+
+--
+
+Note for multi-Period contents:
+
+This method will only have an effect on the [Period](../terms.md#period) that is
+currently playing.
+If you want to disable the text track for other Periods as well, you might want
+to either:
+ - update the preferred text tracks through the
+ [setPreferredTextTracks](#meth-setPreferredTextTracks) method to include
+ `null` (which meaning that you want no text track).
+ - call `disableTextTrack` once a `"periodChange"` event has been received.
@@ -1291,7 +1249,11 @@ Deactivate the current text track, if one.
_arguments_: ``string|Number``
-Set a new video track from its id, recuperated from ``getAvailableVideoTracks``.
+Change the current video track.
+
+The argument to this method is the wanted track's `id` property. This `id` can
+for example be obtained on the corresponding track object returned by the
+``getAvailableAudioTracks`` method.
Setting a new video track when a previous one was already playing can lead the
rx-player to "reload" this content.
@@ -1314,26 +1276,50 @@ During this period of time:
- ``setAudioTrack`` will throw
- ``setTextTrack`` will throw
----
+--
+
+Note for multi-Period contents:
+
+This method will only have an effect on the [Period](../terms.md#period) that is
+currently playing.
+If you want to update the track for other Periods as well, you might want to
+either:
+ - update the preferred video tracks through the
+ [setPreferredVideoTracks](#meth-setPreferredVideoTracks) method.
+ - update the current video track once a `"periodChange"` event has been
+ received.
+
+--
:warning: This option will have no effect in _DirectFile_ mode
(see [loadVideo options](./loadVideo_options.md#prop-transport)) when either :
- No video track API is supported on the current browser
- The media file tracks are not supported on the browser
----
-
### disableVideoTrack ##########################################################
_return value_: ``void``
-Deactivate the current video track, if one.
+Disable the current video track, if one.
Might enter in `RELOADING` state for a short period after calling this API.
----
+--
+
+Note for multi-Period contents:
+
+This method will only have an effect on the [Period](../terms.md#period) that is
+currently playing.
+If you want to disable the video track for other Periods as well, you might want
+to either:
+ - update the preferred video tracks through the
+ [setPreferredVideoTracks](#meth-setPreferredVideoTracks) method to include
+ `null` (which meaning that you want no video track).
+ - call `disableVideoTrack` once a `"periodChange"` event has been received.
+
+--
:warning: This option may have no effect in _DirectFile_ mode
(see [loadVideo options](./loadVideo_options.md#prop-transport)).
@@ -1351,8 +1337,6 @@ Due to this fact, we do not recommend using this API in directfile mode for
now. You might even receive a reassuring `videoTrackChange` event (with a `null`
payload) while the video track is still actually active.
----
-
### setPreferredAudioTracks ####################################################
@@ -1421,7 +1405,7 @@ Simply put, once set this preference will be applied to all contents but:
tracks at the time they were first loaded).
To update the current audio track in those cases, you should use the
-`setAudioTrack` method once they are currently played.
+`setAudioTrack` method once they are currently playing.
#### Examples
@@ -1437,149 +1421,653 @@ player.setPreferredAudioTracks([
])
```
-Now let's imagine that you want to have in priority a track that contain at
-least one profile in Dolby Digital Plus (ec-3 codec) without caring about the
-language:
-```js
-player.setPreferredAudioTracks([ { codec: { all: false, test: /ec-3/ } ]);
-```
+Now let's imagine that you want to have in priority a track that contain at
+least one profile in Dolby Digital Plus (ec-3 codec) without caring about the
+language:
+```js
+player.setPreferredAudioTracks([ { codec: { all: false, test: /ec-3/ } ]);
+```
+
+At last, let's combine both examples by preferring french over itialian, italian
+over english while preferring it to be in Dolby Digital Plus:
+```js
+
+player.setPreferredAudioTracks([
+ {
+ language: "fra",
+ audioDescription: false,
+ codec: { all: false, test: /ec-3/ }
+ },
+
+ // We still prefer non-DD+ french over DD+ italian
+ { language: "fra", audioDescription: false },
+
+ {
+ language: "ita",
+ audioDescription: false,
+ codec: { all: false, test: /ec-3/ }
+ },
+ { language: "ita", audioDescription: false },
+
+ {
+ language: "eng",
+ audioDescription: false,
+ codec: { all: false, test: /ec-3/ }
+ },
+ { language: "eng", audioDescription: false }
+]);
+```
+
+--
+
+:warning: This option will have no effect in _DirectFile_ mode
+(see [loadVideo options](./loadVideo_options.md#prop-transport)) when either :
+- No audio track API is supported on the current browser
+- The media file tracks are not supported on the browser
+
+
+
+### getPreferredAudioTracks ####################################################
+
+_return value_: ``Array.``
+
+Returns the current list of preferred audio tracks - by order of preference.
+
+This returns the data in the same format that it was given to either the
+`preferredAudioTracks` constructor option or the last `setPreferredAudioTracks`
+if it was called.
+
+It will return an empty Array if none of those two APIs were used until now.
+
+
+
+### setPreferredTextTracks #####################################################
+
+_arguments_: ``Array.``
+
+Update the text track (or subtitles) preferences at any time.
+
+This method takes an array of objects describing the languages wanted:
+```js
+{
+ language: "fra", // {string} The wanted language
+ // (ISO 639-1, ISO 639-2 or ISO 639-3 language code)
+ closedCaption: false // {Boolean} Whether the text track should be a closed
+ // caption for the hard of hearing
+}
+```
+
+All elements in that Array should be set in preference order: from the most
+preferred to the least preferred. You can set `null` for no subtitles.
+
+When encountering a new Period or a new content, the RxPlayer will then try to
+choose its text track by comparing what is available with your current
+preferences (i.e. if the most preferred is not available, it will look if the
+second one is etc.).
+
+Please note that those preferences will only apply either to the next loaded
+content or to the next newly encountered Period.
+Simply put, once set this preference will be applied to all contents but:
+
+ - the current Period being played (or the current loaded content, in the case
+ of Smooth streaming). In that case, the current text track preference will
+ stay in place.
+
+ - the Periods which have already been played in the current loaded content.
+ Those will keep the last set text track preference at the time it was
+ played.
+
+To update the current text track in those cases, you should use the
+`setTextTrack` method.
+
+#### Example
+
+Let's imagine that you prefer to have french or italian subtitles.If not found,
+you want no subtitles at all.
+
+You will thus call ``setPreferredTextTracks`` that way.
+
+```js
+player.setPreferredTextTracks([
+ { language: "fra", closedCaption: false },
+ { language: "ita", closedCaption: false },
+ null
+])
+```
+
+--
+
+:warning: This option will have no effect in _DirectFile_ mode
+(see [loadVideo options](./loadVideo_options.md#prop-transport)) when either :
+- No text track API is supported on the current browser
+- The media file tracks are not supported on the browser
+
+
+
+### getPreferredTextTracks #####################################################
+
+_return value_: ``Array.``
+
+Returns the current list of preferred text tracks - by order of preference.
+
+This returns the data in the same format that it was given to either the
+`preferredTextTracks` constructor option or the last `setPreferredTextTracks` if
+it was called:
+
+```js
+{
+ language: "fra", // {string} The wanted language
+ // (ISO 639-1, ISO 639-2 or ISO 639-3 language code)
+ closedCaption: false // {Boolean} Whether the text track should be a closed
+ // caption for the hard of hearing
+}
+```
+
+
+
+
+## Bitrate selection ###########################################################
+
+The following methods allows to choose a given bitrate for audio or video
+content. It can also enable or disable an adaptive bitrate logic or influence
+it.
+
+
+
+### getAvailableVideoBitrates ##################################################
+
+_return value_: ``Array.``
+
+The different bitrates available for the current video track in bits per
+seconds.
+
+--
+
+Note for multi-Period contents:
+
+This method will only return the available video bitrates of the
+[Period](../terms.md#period) that is currently playing.
+
+--
+
+In _DirectFile_ mode (see [loadVideo
+options](./loadVideo_options.md#prop-transport)), returns an empty Array.
+
+#### Example
+
+```js
+const videoBitrates = player.getAvailableVideoBitrates();
+if (videoBitrates.length) {
+ console.log(
+ "The current video is available in the following bitrates",
+ videoBitrates.join(", ")
+ );
+}
+```
+
+
+
+### getAvailableAudioBitrates ##################################################
+
+_return value_: ``Array.``
+
+The different bitrates available for the current audio track in bits per
+seconds.
+
+--
+
+Note for multi-Period contents:
+
+This method will only return the available audio bitrates of the
+[Period](../terms.md#period) that is currently playing.
+
+--
+
+In _DirectFile_ mode (see [loadVideo
+options](./loadVideo_options.md#prop-transport)), returns an empty Array.
+
+#### Example
+
+```js
+const audioBitrates = player.getAvailableAudioBitrates();
+if (audioBitrates.length) {
+ console.log(
+ "The current audio is available in the following bitrates",
+ audioBitrates.join(", ")
+ );
+}
+```
+
+
+
+### getVideoBitrate ############################################################
+
+_return value_: ``Number|undefined``
+
+Returns the bitrate of the video quality currently set, in bits per second.
+
+Returns ``undefined`` if no content is loaded.
+
+--
+
+Note for multi-Period contents:
+
+This method will only return the chosen video bitrate for the
+[Period](../terms.md#period) that is currently playing.
+
+--
+
+In _DirectFile_ mode (see [loadVideo
+options](./loadVideo_options.md#prop-transport)), returns ``undefined``.
+
+
+
+### getAudioBitrate ############################################################
+
+_return value_: ``Number|undefined``
+
+Returns the bitrate of the audio quality currently set, in bits per second.
+
+Returns ``undefined`` if no content is loaded.
+
+--
+
+Note for multi-Period contents:
+
+This method will only return the chosen audio bitrate for the
+[Period](../terms.md#period) that is currently playing.
+
+--
+
+In _DirectFile_ mode (see [loadVideo
+options](./loadVideo_options.md#prop-transport)), returns ``undefined``.
+
+
+
+### setMaxVideoBitrate #########################################################
+
+_arguments_: ``Number``
+
+Set the maximum video bitrate reachable through adaptive streaming. The player
+will never automatically switch to a video quality with a higher bitrate.
+
+This limit can be removed by setting it to ``Infinity``:
+```js
+// remove video bitrate limit
+player.setMaxVideoBitrate(Infinity);
+```
+
+The effect of this method is persisted from content to content. As such, it can
+even be called when no content is currently loaded.
+
+Note that this only affects adaptive strategies (you can bypass this limit by
+calling ``setVideoBitrate``).
+
+--
+
+:warning: This option will have no effect for contents loaded in _DirectFile_
+mode (see [loadVideo options](./loadVideo_options.md#prop-transport)).
+
+
+
+### setMaxAudioBitrate #########################################################
+
+_arguments_: ``Number``
+
+Set the maximum audio bitrate reachable through adaptive streaming. The player
+will never automatically switch to a audio
+[Representation](../terms.md#representation) with a higher bitrate.
+
+This limit can be removed by setting it to ``Infinity``:
+```js
+// remove audio bitrate limit
+player.setMaxAudioBitrate(Infinity);
+```
+
+The effect of this method is persisted from content to content. As such, it can
+even be called when no content is currently loaded.
+
+Note that this only affects adaptive strategies (you can bypass this limit by
+calling ``setAudioBitrate``).
+
+--
+
+:warning: This option will have no effect for contents loaded in _DirectFile_
+mode (see [loadVideo options](./loadVideo_options.md#prop-transport)).
+
+
+
+### getMaxVideoBitrate #########################################################
+
+_return value_: ``Number``
+
+Returns the maximum video bitrate reachable through adaptive streaming, in bits
+per seconds.
+
+This limit can be updated by calling the
+[setMaxVideoBitrate](#meth-setMaxVideoBitrate) method.
+
+This limit only affects adaptive strategies (you can bypass this limit by
+calling ``setVideoBitrate``), and is set to ``Infinity`` when no limit has been
+set.
+
+
+
+### getMaxAudioBitrate #########################################################
+
+_return value_: ``Number``
+
+Returns the maximum audio bitrate reachable through adaptive streaming, in bits
+per seconds.
+
+This limit can be updated by calling the
+[setMaxAudioBitrate](#meth-setMaxAudioBitrate) method.
+
+This limit only affects adaptive strategies (you can bypass this limit by
+calling ``setAudioBitrate``), and is set to ``Infinity`` when no limit has been
+set.
+
+
+
+### setVideoBitrate ############################################################
+
+_arguments_: ``Number``
+
+Force the current video track to be of a certain bitrate.
+
+If an video quality in the current track is found with the exact same bitrate,
+this quality will be set.
+
+If no video quality is found with the exact same bitrate, either:
+
+ - the video quality with the closest bitrate inferior to that value will be
+ chosen.
+
+ - if no video quality has a bitrate lower than that value, the video
+ quality with the lowest bitrate will be chosen instead.
+
+By calling this method with an argument set to ``-1``, this setting will be
+disabled and the RxPlayer will chose the right quality according to its adaptive
+logic.
+
+You can use ``getAvailableVideoBitrates`` to get the list of available bitrates
+for the current video track.
+
+Note that the value set is persistent between ``loadVideo`` calls.
+As such, this method can also be called when no content is playing (the same
+rules apply for future contents).
+
+--
+
+:warning: This option will have no effect for contents loaded in _DirectFile_
+mode (see [loadVideo options](./loadVideo_options.md#prop-transport)).
+
+
+
+### setAudioBitrate ############################################################
+
+_arguments_: ``Number``
+
+Force the current audio track to be of a certain bitrate.
+
+If an audio quality in the current track is found with the exact same bitrate,
+this quality will be set.
+
+If no audio quality is found with the exact same bitrate, either:
+
+ - the audio quality with the closest bitrate inferior to that value will be
+ chosen.
+
+ - if no audio quality has a bitrate lower than that value, the audio
+ quality with the lowest bitrate will be chosen instead.
+
+By calling this method with an argument set to ``-1``, this setting will be
+disabled and the RxPlayer will chose the right quality according to its adaptive
+logic.
+
+You can use ``getAvailableAudioBitrates`` to get the list of available bitrates
+for the current audio track.
+
+Note that the value set is persistent between ``loadVideo`` calls.
+As such, this method can also be called when no content is playing (the same
+rules apply for future contents).
+
+--
+
+:warning: This option will have no effect for contents loaded in _DirectFile_
+mode (see [loadVideo options](./loadVideo_options.md#prop-transport)).
+
+
+
+### getManualVideoBitrate ######################################################
+
+_arguments_: ``Number``
+
+Get the last video bitrate manually set. Either via ``setVideoBitrate`` or via
+the ``initialVideoBitrate`` constructor option.
+
+This value can be different than the one returned by ``getVideoBitrate``:
+ - ``getManualVideoBitrate`` returns the last bitrate set manually by the user
+ - ``getVideoBitrate`` returns the actual bitrate of the current video track
+
+``-1`` when no video bitrate is forced.
+
+
+
+### getManualAudioBitrate ######################################################
+
+_arguments_: ``Number``
+
+Get the last audio bitrate manually set. Either via ``setAudioBitrate`` or via
+the ``initialAudioBitrate`` constructor option.
+
+This value can be different than the one returned by ``getAudioBitrate``:
+ - ``getManualAudioBitrate`` returns the last bitrate set manually by the user
+ - ``getAudioBitrate`` returns the actual bitrate of the current audio track
+
+``-1`` when no audio bitrate is forced.
+
+
+
+
+## Buffer control ##############################################################
+
+The methods in this chapter allow to get and set limits on how the current
+buffer can grow.
+
+
+
+### setWantedBufferAhead #######################################################
+
+_arguments_: ``Number``
+
+Set the buffering goal, as a duration ahead of the current position, in seconds.
+
+Once this size of buffer reached, the player won't try to download new segments
+anymore.
+
+--
+
+:warning: This option will have no effect for contents loaded in _DirectFile_
+mode (see [loadVideo options](./loadVideo_options.md#prop-transport)).
+
+
+
+### getWantedBufferAhead #######################################################
+
+_return value_: ``Number``
+_defaults_: ``30``
+
+returns the buffering goal, as a duration ahead of the current position, in
+seconds.
+
+
+
+### setMaxBufferBehind #########################################################
+
+_arguments_: ``Number``
+
+Set the maximum kept buffer before the current position, in seconds.
+
+Everything before that limit (``currentPosition - maxBufferBehind``) will be
+automatically garbage collected.
+
+This feature is not necessary as the browser should by default correctly
+remove old segments from memory if/when the memory is scarce.
+
+However on some custom targets, or just to better control the memory footprint
+of the player, you might want to set this limit.
+
+You can set it to ``Infinity`` to remove this limit and just let the browser do
+this job instead.
+
+--
+
+:warning: This option will have no effect for contents loaded in _DirectFile_
+mode (see [loadVideo options](./loadVideo_options.md#prop-transport)).
+
+
+
+### getMaxBufferBehind #########################################################
+
+_return value_: ``Number``
+_defaults_: ``Infinity``
+
+Returns the maximum kept buffer before the current position, in seconds.
+
+This setting can be updated either by:
+ - calling the `setMaxBufferBehind` method.
+ - instanciating an RxPlayer with a `maxBufferBehind` property set.
+
+
+
+### setMaxBufferAhead ##########################################################
+
+_arguments_: ``Number``
+
+Set the maximum kept buffer ahead of the current position, in seconds.
+
+Everything superior to that limit (``currentPosition + maxBufferAhead``) will
+be automatically garbage collected.
+
+This feature is not necessary as the browser should by default correctly
+remove old segments from memory if/when the memory is scarce.
+
+However on some custom targets, or just to better control the memory footprint
+of the player, you might want to set this limit.
+
+You can set it to ``Infinity`` to remove any limit and just let the browser do
+this job instead.
+
+The minimum value between this one and the one returned by
+``getWantedBufferAhead`` will be considered when downloading new segments.
+
+:warning: Bear in mind that a too-low configuration there (e.g. inferior to
+``10``) might prevent the browser to play the content at all.
+
+--
+
+:warning: This option will have no effect for contents loaded in _DirectFile_
+mode (see [loadVideo options](./loadVideo_options.md#prop-transport)).
+
-At last, let's combine both examples by preferring french over itialian, italian
-over english while preferring it to be in Dolby Digital Plus:
-```js
+
+### getMaxBufferAhead ##########################################################
-player.setPreferredAudioTracks([
- {
- language: "fra",
- audioDescription: false,
- codec: { all: false, test: /ec-3/ }
- },
+_return value_: ``Number``
+_defaults_: ``Infinity``
- // We still prefer non-DD+ french over DD+ italian
- { language: "fra", audioDescription: false },
+Returns the maximum kept buffer ahead of the current position, in seconds.
- {
- language: "ita",
- audioDescription: false,
- codec: { all: false, test: /ec-3/ }
- },
- { language: "ita", audioDescription: false },
+This setting can be updated either by:
+ - calling the `setMaxBufferAhead` method.
+ - instanciating an RxPlayer with a `maxBufferAhead` property set.
- {
- language: "eng",
- audioDescription: false,
- codec: { all: false, test: /ec-3/ }
- },
- { language: "eng", audioDescription: false }
-]);
-```
----
-:warning: This option will have no effect in _DirectFile_ mode
-(see [loadVideo options](./loadVideo_options.md#prop-transport)) when either :
-- No audio track API is supported on the current browser
-- The media file tracks are not supported on the browser
+
+### Buffer information #########################################################
----
+The methods in this chapter allows to retrieve information about what is
+currently buffered.
-
-### getPreferredAudioTracks ####################################################
+
+### getVideoLoadedTime #########################################################
-_return value_: ``Array.``
+_return value_: ``Number``
-Returns the current list of preferred audio tracks - by order of preference.
+Returns in seconds the difference between:
+ - the start of the current contiguous loaded range.
+ - the end of it.
-This returns the data in the same format that it was given to either the
-`preferredAudioTracks` constructor option or the last `setPreferredAudioTracks`
-if it was called.
-It will return an empty Array if none of those two APIs were used until now.
+
+### getVideoPlayedTime #########################################################
+_return value_: ``Number``
-
-### setPreferredTextTracks #####################################################
+Returns in seconds the difference between:
+ - the start of the current contiguous loaded range.
+ - the current time.
-_arguments_: ``Array.``
-Update the text track (or subtitles) preferences at any time.
+
+### getVideoBufferGap ##########################################################
-This method takes an array of objects describing the languages wanted:
-```js
-{
- language: "fra", // {string} The wanted language
- // (ISO 639-1, ISO 639-2 or ISO 639-3 language code)
- closedCaption: false // {Boolean} Whether the text track should be a closed
- // caption for the hard of hearing
-}
-```
+_return value_: ``Number``
-All elements in that Array should be set in preference order: from the most
-preferred to the least preferred. You can set `null` for no subtitles.
+Returns in seconds the difference between:
+ - the current time.
+ - the end of the current contiguous loaded range.
-When encountering a new Period or a new content, the RxPlayer will then try to
-choose its text track by comparing what is available with your current
-preferences (i.e. if the most preferred is not available, it will look if the
-second one is etc.).
-Please note that those preferences will only apply either to the next loaded
-content or to the next newly encountered Period.
-Simply put, once set this preference will be applied to all contents but:
- - the current Period being played (or the current loaded content, in the case
- of Smooth streaming). In that case, the current text track preference will
- stay in place.
+
+## Content information #########################################################
- - the Periods which have already been played in the current loaded content.
- Those will keep the last set text track preference at the time it was
- played.
+The methods documented in this chapter allows to obtain general information
+about the current loaded content.
-To update the current text track in those cases, you should use the
-`setTextTrack` method.
-#### Example
+
+### isLive #####################################################################
-Let's imagine that you prefer to have french or italian subtitles.If not found,
-you want no subtitles at all.
+_return value_: ``Boolean``
-You will thus call ``setPreferredTextTracks`` that way.
+Returns ``true`` if the content is a "live" content (e.g. a live TV Channel).
+``false`` otherwise.
+
+Also ``false`` if no content is loaded yet.
+
+#### Example
```js
-player.setPreferredTextTracks([
- { language: "fra", closedCaption: false },
- { language: "ita", closedCaption: false },
- null
-])
+if (player.isLive()) {
+ console.log("We're playing a live content");
+}
```
----
-
-:warning: This option will have no effect in _DirectFile_ mode
-(see [loadVideo options](./loadVideo_options.md#prop-transport)) when either :
-- No text track API is supported on the current browser
-- The media file tracks are not supported on the browser
----
+
+### getUrl #####################################################################
+_return value_: ``string|undefined``
-
-### getPreferredTextTracks #####################################################
+Returns the URL of the downloaded [Manifest](../terms.md#manifest).
-_return value_: ``Array.``
+In _DirectFile_ mode (see [loadVideo
+options](./loadVideo_options.md#prop-transport)), returns the URL of the content
+being played.
-Returns the current list of preferred text tracks - by order of preference.
+Returns ``undefined`` if no content is loaded yet.
-This returns the data in the same format that it was given to either the
-`preferredTextTracks` constructor option or the last `setPreferredTextTracks` if
-it was called:
+#### Example
```js
-{
- language: "fra", // {string} The wanted language
- // (ISO 639-1, ISO 639-2 or ISO 639-3 language code)
- closedCaption: false // {Boolean} Whether the text track should be a closed
- // caption for the hard of hearing
+const url = player.getUrl();
+if (url) {
+ console.log("We are playing the following content:", url);
}
```
@@ -1770,61 +2258,6 @@ An Representation object structure is relatively complex and is described in the
options](./loadVideo_options.md#prop-transport)).
-
-### dispose ####################################################################
-
-Free the ressources used by the player.
-
-!warning!: The player won't work correctly after calling this method.
-
-
-
-### getVideoLoadedTime #########################################################
-
-_return value_: ``Number``
-
-Returns in seconds the difference between:
- - the start of the current contiguous loaded range.
- - the end of it.
-
-
-
-### getVideoPlayedTime #########################################################
-
-_return value_: ``Number``
-
-Returns in seconds the difference between:
- - the start of the current contiguous loaded range.
- - the current time.
-
-
-
-### getVideoBufferGap ##########################################################
-
-_return value_: ``Number``
-
-Returns in seconds the difference between:
- - the current time.
- - the end of the current contiguous loaded range.
-
-
-
-### getPlaybackRate ############################################################
-
-_return value_: ``Number``
-
-Returns the current video normal playback rate (speed when playing). ``1`` for
-normal playback, ``2`` when playing *2, etc.
-
-
-
-### setPlaybackRate ############################################################
-
-_arguments_: ``Number``
-
-Updates the "normal" (when playing) playback rate for the video.
-
-
### getCurrentKeySystem ########################################################
@@ -1833,54 +2266,23 @@ _return value_: ``string|undefined``
Returns the type of keySystem used for DRM-protected contents.
-
-### getMinimumPosition #########################################################
-
-_return value_: ``Number|null``
-
-The minimum seekable player position. ``null`` if no content is loaded.
-
-This is useful for live contents, where server-side buffer size are often not
-infinite. This method allows thus to seek at the earliest possible time.
-
-#### Example
-```js
-// seeking to the earliest position possible (beginning of the buffer for live
-// contents, position '0' for non-live contents).
-player.seekTo({
- position: player.getMinimumPosition()
-});
-```
-
-
-
-### getMaximumPosition #########################################################
-
-_return value_: ``Number|null``
-
-The maximum seekable player position. ``null`` if no content is loaded.
-This is useful for live contents, where the buffer end updates continously.
-This method allows thus to seek directly at the live edge of the content.
+
+## Deprecated ##################################################################
-#### Example
-```js
-// seeking to the end
-player.seekTo({
- position: player.getMaximumPosition()
-});
-```
+The following methods are deprecated. They are still supported but we advise
+users to not use those as they might become not supported in the future.
### getImageTrackData ##########################################################
----
+--
:warning: This method is deprecated, it will disappear in the next major
release ``v4.0.0`` (see [Deprecated APIs](./deprecated.md)).
----
+--
_return value_: ``Array.|null``
@@ -1897,12 +2299,12 @@ options](./loadVideo_options.md#prop-transport)).
### setFullscreen ##############################################################
----
+--
:warning: This method is deprecated, it will disappear in the next major
release ``v4.0.0`` (see [Deprecated APIs](./deprecated.md)).
----
+--
_arguments_: ``Boolean``
@@ -1921,12 +2323,12 @@ prefer to implement your own method to include your controls in the final UI.
### exitFullscreen #############################################################
----
+--
:warning: This method is deprecated, it will disappear in the next major
release ``v4.0.0`` (see [Deprecated APIs](./deprecated.md)).
----
+--
Exit fullscreen mode. Same than ``setFullscreen(false)``.
@@ -1934,12 +2336,12 @@ Exit fullscreen mode. Same than ``setFullscreen(false)``.
### isFullscreen ###############################################################
----
+--
:warning: This method is deprecated, it will disappear in the next major
release ``v4.0.0`` (see [Deprecated APIs](./deprecated.md)).
----
+--
_return value_: ``Boolean``
@@ -1947,6 +2349,7 @@ Returns ``true`` if the video element is in fullscreen mode, ``false``
otherwise.
#### Example
+
```js
if (player.isFullscreen()) {
console.log("The player is in fullscreen mode");
@@ -1957,12 +2360,12 @@ if (player.isFullscreen()) {
### getNativeTextTrack #########################################################
----
+--
:warning: This method is deprecated, it will disappear in the next major
release ``v4.0.0`` (see [Deprecated APIs](./deprecated.md)).
----
+--
_return value_: ``TextTrack|null``
@@ -1979,17 +2382,21 @@ const textTrack = el.textTracks.length ? el.textTracks[0] : null;
## Tools #######################################################################
+The RxPlayer has several "tools", which are utils which can be imported without
+importing the whole RxPlayer itself.
+
+They are all documented here.
### MediaCapabilitiesProber ####################################################
----
+--
:warning: This tool is experimental. This only means that its API can change at
any new RxPlayer version (with all the details in the corresponding release
note).
----
+--
An experimental tool to probe browser media capabilities:
@@ -2004,13 +2411,13 @@ You can find its documentation [here](./mediaCapabilitiesProber.md).
### TextTrackRenderer ##########################################################
----
+--
:warning: This tool is experimental. This only means that its API can change at
any new RxPlayer version (with all the details in the corresponding release
note).
----
+--
The TextTrackRenderer allows to easily render subtitles synchronized to a video
element.
@@ -2024,13 +2431,13 @@ This tool is documented [here](./TextTrackRenderer.md).
### parseBifThumbnails #########################################################
----
+--
:warning: This tool is experimental. This only means that its API can change at
any new RxPlayer version (with all the details in the corresponding release
note).
----
+--
The `parseBifThumbnails` function parses BIF files, which is a format created by
Canal+ to declare thumbnails linked to a given content.
@@ -2040,13 +2447,13 @@ This tool is documented [here](./parseBifThumbnails.md).
### createMetaplaylist #########################################################
----
+--
:warning: This tool is experimental. This only means that its API can change at
any new RxPlayer version (with all the details in the corresponding release
note).
----
+--
The `createMetaplaylist` function build a metaplaylist object from given
informations about contents.
From 53deb9c5fb7278e3cc6d4c35ebf02e23690f52a8 Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Tue, 14 Apr 2020 17:27:20 +0200
Subject: [PATCH 25/72] api: add `signInterpreted` to the
`preferredVideoTracks`
---
doc/api/index.md | 36 ++++++++++++++++++++++--
doc/api/player_options.md | 42 ++++++++++++++++++++++++----
src/core/api/track_choice_manager.ts | 8 +++++-
3 files changed, 78 insertions(+), 8 deletions(-)
diff --git a/doc/api/index.md b/doc/api/index.md
index d25059f0c8..2ca82a5b22 100644
--- a/doc/api/index.md
+++ b/doc/api/index.md
@@ -1622,6 +1622,13 @@ on which tracks will be filtered):
// A SINGLE profile from it has codec information validated by
// that RegExp.
}
+ signInterpreted: true, // {Boolean|undefined} If set to `true`, only tracks
+ // which are known to contains a sign language
+ // interpretation will be considered.
+ // If set to `false`, only tracks which are known
+ // to not contain it will be considered.
+ // if not set or set to `undefined` we won't filter
+ // based on that status.
}
```
@@ -1653,8 +1660,33 @@ profiles. You can do:
player.setPreferredVideoTracks([ { codec: { all: false, test: /^hvc/ } } ]);
```
-Now let's imagine you want to start without any video track enabled (e.g. to
-start in an audio-only mode). To do that, you can simply do:
+With that same constraint, let's no consider that the current user prefer in any
+case to have a sign language interpretation on screen:
+```js
+player.setPreferredVideoTracks([
+ // first let's consider the best case: H265 + sign language interpretation
+ {
+ codec: { all: false, test: /^hvc/ }
+ signInterpreted: true,
+ },
+
+ // If not available, we still prefer a sign interpreted track without H265
+ { signInterpreted: true },
+
+ // If not available either, we would prefer an H265 content
+ { codec: { all: false, test: /^hvc/ } },
+
+ // Note: If this is also available, we will here still have a video track
+ // but which do not respect any of the constraints set here.
+]);
+would thus prefer the video to contain a sign language interpretation.
+We could set both the previous and that new constraint that way:
+
+---
+
+For a totally different example, let's imagine you want to start without any
+video track enabled (e.g. to start in an audio-only mode). To do that, you can
+simply do:
```js
player.setPreferredVideoTracks([null]);
```
diff --git a/doc/api/player_options.md b/doc/api/player_options.md
index 29caa63e72..2790d21e3e 100644
--- a/doc/api/player_options.md
+++ b/doc/api/player_options.md
@@ -435,6 +435,13 @@ on which tracks will be filtered):
// A SINGLE profile from it has codec information validated by
// that RegExp.
}
+ signInterpreted: true, // {Boolean|undefined} If set to `true`, only tracks
+ // which are known to contains a sign language
+ // interpretation will be considered.
+ // If set to `false`, only tracks which are known
+ // to not contain it will be considered.
+ // if not set or set to `undefined` we won't filter
+ // based on that status.
}
```
@@ -445,17 +452,42 @@ This array of preferrences can be updated at any time through the
#### Examples
Let's imagine that you prefer to have a track which contains at least one H265
-profile.
-
-You can do:
+profile. You can do:
```js
const player = new RxPlayer({
preferredVideoTracks: [ { codec: { all: false, test: /^hvc/ } } ]
});
```
-Now let's imagine you want to start without any video track enabled (e.g. to
-start in an audio-only mode). To do that, you can simply do:
+With that same constraint, let's no consider that the current user is deaf and
+would thus prefer the video to contain a sign language interpretation.
+We could set both the previous and that new constraint that way:
+```js
+const player = new RxPlayer({
+ preferredVideoTracks: [
+ // first let's consider the best case: H265 + sign language interpretation
+ {
+ codec: { all: false, test: /^hvc/ }
+ signInterpreted: true,
+ },
+
+ // If not available, we still prefer a sign interpreted track without H265
+ { signInterpreted: true },
+
+ // If not available either, we would prefer an H265 content
+ { codec: { all: false, test: /^hvc/ } },
+
+ // Note: If this is also available, we will here still have a video track
+ // but which do not respect any of the constraints set here.
+ ]
+});
+```
+
+---
+
+For a totally different example, let's imagine you want to start without any
+video track enabled (e.g. to start in an audio-only mode). To do that, you can
+simply do:
```js
const player = new RxPlayer({
preferredVideoTracks: [null]
diff --git a/src/core/api/track_choice_manager.ts b/src/core/api/track_choice_manager.ts
index 1b1ce26953..ecbf63212a 100644
--- a/src/core/api/track_choice_manager.ts
+++ b/src/core/api/track_choice_manager.ts
@@ -50,7 +50,8 @@ export type ITextTrackPreference = null |
/** Single preference for a video track Adaptation. */
export type IVideoTrackPreference = null |
{ codec? : { all: boolean;
- test: RegExp; }; };
+ test: RegExp; };
+ signInterpreted? : boolean; };
/** Audio track returned by the TrackChoiceManager. */
export interface ITMAudioTrack { language : string;
@@ -996,6 +997,11 @@ function findFirstOptimalVideoAdaptation(
}
const foundAdaptation = arrayFind(videoAdaptations, (videoAdaptation) => {
+ if (preferredVideoTrack.signInterpreted !== undefined &&
+ preferredVideoTrack.signInterpreted !== videoAdaptation.isSignInterpreted)
+ {
+ return false;
+ }
if (preferredVideoTrack.codec === undefined) {
return true;
}
From 038577f4480757519d3c68ba550a576e7ee2f466 Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Wed, 15 Apr 2020 15:49:35 +0200
Subject: [PATCH 26/72] doc: add warnings for video tracks API in directfile
mode on Safari
---
doc/api/index.md | 7 +++++--
doc/api/player_events.md | 7 +++++--
2 files changed, 10 insertions(+), 4 deletions(-)
diff --git a/doc/api/index.md b/doc/api/index.md
index 7597748907..2d66caf9c6 100644
--- a/doc/api/index.md
+++ b/doc/api/index.md
@@ -1338,8 +1338,11 @@ Might enter in `RELOADING` state for a short period, if not in _DirectFile_
- No video track API was supported on the current browser
- The media file tracks are not supported on the browser
-:warning: On Safari browser in _DirectFile_, video playback may continue on same
-track even if video tracks are disabled.
+:warning: On Safari, and only for contents played in the _DirectFile_ mode (see
+[loadVideo options](./loadVideo_options.md#prop-transport)), a video track may
+have been disabled but still displaying on screen.
+Based on our understanding of the standard, we consider this to be a Safari bug.
+
---
diff --git a/doc/api/player_events.md b/doc/api/player_events.md
index 6cabf1252c..1ca0cbb321 100644
--- a/doc/api/player_events.md
+++ b/doc/api/player_events.md
@@ -327,8 +327,11 @@ properties:
- ``frameRate`` (``string|undefined``): The video framerate.
A null payload means that video track has been disabled.
-/!\ On Safari in _DirectFile_, a video track may have been disabled by user but
-still playing on screen.
+
+:warning: On Safari, and only for contents played in the _DirectFile_ mode (see
+[loadVideo options](./loadVideo_options.md#prop-transport)), a video track may
+have been disabled but still displaying on screen.
+Based on our understanding of the standard, we consider this to be a Safari bug.
### availableAudioBitratesChange ###############################################
From 41839b7d535e5a0da2f98bb983a18a0172e550a2 Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Thu, 2 Apr 2020 18:50:49 +0200
Subject: [PATCH 27/72] transports: always request DASH and Smooth Manifest as
texts to have a better knowledge of their parsing time vs loading time
---
src/transports/dash/pipelines.ts | 2 +-
src/transports/smooth/pipelines.ts | 2 +-
.../{document_manifest_loader.ts => text_manifest_loader.ts} | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
rename src/transports/utils/{document_manifest_loader.ts => text_manifest_loader.ts} (97%)
diff --git a/src/transports/dash/pipelines.ts b/src/transports/dash/pipelines.ts
index 110f07e6a7..8a6e1a31cc 100644
--- a/src/transports/dash/pipelines.ts
+++ b/src/transports/dash/pipelines.ts
@@ -18,7 +18,7 @@ import {
ITransportOptions,
ITransportPipelines,
} from "../types";
-import generateManifestLoader from "../utils/document_manifest_loader";
+import generateManifestLoader from "../utils/text_manifest_loader";
import {
imageLoader,
imageParser,
diff --git a/src/transports/smooth/pipelines.ts b/src/transports/smooth/pipelines.ts
index a4b5cf3dbe..051909183c 100644
--- a/src/transports/smooth/pipelines.ts
+++ b/src/transports/smooth/pipelines.ts
@@ -57,7 +57,7 @@ import {
ITransportPipelines,
} from "../types";
import checkISOBMFFIntegrity from "../utils/check_isobmff_integrity";
-import generateManifestLoader from "../utils/document_manifest_loader";
+import generateManifestLoader from "../utils/text_manifest_loader";
import extractTimingsInfos from "./extract_timings_infos";
import { patchSegment } from "./isobmff";
import generateSegmentLoader from "./segment_loader";
diff --git a/src/transports/utils/document_manifest_loader.ts b/src/transports/utils/text_manifest_loader.ts
similarity index 97%
rename from src/transports/utils/document_manifest_loader.ts
rename to src/transports/utils/text_manifest_loader.ts
index 3f6f355007..81dbdf0818 100644
--- a/src/transports/utils/document_manifest_loader.ts
+++ b/src/transports/utils/text_manifest_loader.ts
@@ -34,7 +34,7 @@ function regularManifestLoader(
if (url === undefined) {
throw new Error("Cannot perform HTTP(s) request. URL not known");
}
- return request({ url, responseType: "document" });
+ return request({ url, responseType: "text" });
}
/**
From 4c1f31ce51a6354d7ebbe13e0547bc1f4b6049b5 Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Mon, 20 Apr 2020 10:00:21 +0200
Subject: [PATCH 28/72] doc: update terms and definitions
---
doc/terms.md | 119 ++++++++++++++++++++++++++++-----------------------
1 file changed, 66 insertions(+), 53 deletions(-)
diff --git a/doc/terms.md b/doc/terms.md
index b742f2055b..8a85a6c82e 100644
--- a/doc/terms.md
+++ b/doc/terms.md
@@ -36,8 +36,10 @@ obvious right along.
### Adaptation ##################################################################
-An Adaptation is an element of a [Period](#period) (and by extension of the
-[Manifest](#manifest)) which represents a single type of media.
+Simply put, what we call an "Adaptation" is just an audio, video or text track.
+
+More technically, it is an element of a [Period](#period) (and by extension of
+the [Manifest](#manifest)) which represents a single type of media.
An adaptation can be for example any of those things:
- A video track
@@ -63,37 +65,43 @@ You can find more infos on it [here](./dash_rxplayer_adaptation_difference.md).
In the RxPlayer, a bitrate of a [Representation](#representation) indicates the
number of bits per second of content described by that Representation.
-For example, a video [Adaptation](#adaptation) could have two Representation:
- 1. one with a bitrate: ``1,000,000``
- 2. the other with the bitrate: ``500,000``
+For example, let's imagine a video [Adaptation](#adaptation) with two
+Representation:
+ 1. one with a bitrate at `1,000,000` (which is 1 Megabit)
+ 2. the other with a bitrate at `500,000` (which is 500 kilobits)
+
+Each seconds of content described by the first Representation will be
+represented by 1 megabit of data
-The first representation here will be considered to encode each second of
-content by a million bits (or 1Mb).
+Each seconds for the second Representation will be represented by 500 kilobits.
-The second one will represent the same content for the same time and duration
-for hald the bits (500kb).
+Both will represent the same data, but the first one will need that the RxPlayer
+fetch more data to show the same amount of content.
-The second one is thus more attracting for situations where the current network
-conditions are too poor to play the first one smoothly. The catch is that most
-often a Representation with a lower bitrate will describe a content of a lower
-quality.
+In most cases, a higher bitrate means a higher quality. That's why the RxPlayer
+has to compromise between having the best quality and choosing a Representation
+having a low-enough bitrate to be able to play on the user's computer without
+needing to pause due to poor network conditions.
### Buffer ######################################################################
-RxPlayer's Buffer can describe two things, depending on the context:
+When we talk about the "buffer" in the RxPlayer, it most likely mean one of two
+things, depending on the context:
+
+ - The media data currently loaded by the browser, ready to be decoded.
+
+ In the API, that's what we mean when we talk about the buffer.
- modules in the RxPlayer downloading [media segments](#segment) and doing
what is needed to play them.
- - The part in the browser storing the segments downloaded, waiting for it to
- be decoded.
-
+ Here you will mostly encounter that term in the architecture documentation.
-### Buffer Type #################################################################
+### Buffer type #################################################################
RxPlayer's buffer types describe a single "type" of media.
@@ -101,10 +109,9 @@ Example of such types are:
- "video": which represents only the video content
- "audio": the audio content without the video
- "text": the subtitles, for example
- - "image": the thumbnail tracks.
-Those are called buffer types here (or simply "types") as each type will have a
-single, uncorellated [Buffer](#buffer) in the RxPlayer.
+Those are called buffer types here (or simply "types") as each type will have
+its own [Buffer](#buffer) (in both sense of the term) in the RxPlayer.
@@ -115,26 +122,26 @@ Segment](#segment) or the Media segment itself.
-### Initialization Segment ######################################################
+### Initialization segment ######################################################
-An initialization Segment is a [Media Segment](#segment), which includes
-metadata necessary to play the other segment of the same
-[Representation](#representation).
+An initialization segment is a specific type of [media segment](#segment), which
+includes metadata necessary to initialize the browser's internal decoder.
-This is used by multiple Streaming Technologies to avoid inserting the same
-data in multiple Media Segments.
+Those are sometimes needed before we can actually begin to push any "real" media
+segment from the corresponding [Representation](#representation).
-As such initialization Segments are the first segment downloaded, to indicate
-the metadata of the following content.
+As such, when one is needed, the initialization segment is the first segment
+downloaded for a given Representation.
### Manifest ###################################################################
-Document which describes the content you want to play.
+The Manifest is the generic name for the document which describes the content
+you want to play.
This is equivalent to the DASH's _Media Presentation Description_ (or _MPD_),
-the Microsoft Smooth Streaming's Manifest and the HLS' playlist.
+the Microsoft Smooth Streaming's _Manifest_ and the HLS' _Master Playlist_.
Such document can describe for example:
- multiple qualities for the same video or audio tracks
@@ -147,7 +154,7 @@ file.
-### Media Segment ###############################################################
+### Media segment ###############################################################
A media segment (or simply segment), is a small chunk of media data.
@@ -161,43 +168,48 @@ features:
- adaptive streaming
When you play a content with the RxPlayer, it will most of the time download
-media segments of different types (audio, video, text...) rather than the whole
-content a single time.
+media segments of different types (audio, video, text...) progressively rather
+than the whole content at a single time.
### Period #####################################################################
-A Period is an element of the [Manifest](#manifest) which describes the media
-to play at a given point in time.
+Simply put, a Period defines what the content will be from a starting time to
+an ending time. It is an element contained in the [Manifest](#manifest)) and it
+will contain the [Adaptations](#adaptation) available for the corresponding
+time period.
Depending on the transport used, they correspond to different concepts:
- - for DASH contents, it is linked to an MPD's Period element
+ - for DASH contents, it is more or less the same thing than an MPD's
+ `` element
- for "local" contents, it corresponds to a single object from the `periods`
array.
- for "MetaPlaylist" contents, it corresponds to all the Period elements we
- retrieved bwhen parsing the corresponding [Manifest](#manifest) from the
+ retrieved after parsing the corresponding [Manifest](#manifest) from the
elements of the `contents` array.
- any other transport will have a single Period, describing the whole content.
-Despite having a different source depending on the transport used, a Period is
-a single concept in the RxPlayer.
-
-Simply put, it allows to set various types of content successively in the same
-manifest.
+--
-For example, let's take a manifest describing a live content with
+As an example, let's take a manifest describing a live content with
chronologically:
1. an english TV Show
2. an old italian film with subtitles
3. an American blockbuster with closed captions.
-Those contents are drastically different (they have different languages, the
-american blockbuster might have more available bitrates than the old italian
-one).
+Let's say that those sub-contents are drastically different:
+ - they are all in different languages
+ - the american blockbuster has more available video bitrates than the old
+ italian one
+
+Because the available tracks and available qualities are different from
+sub-content to sub-content, we cannot just give a single list of Adaptations
+valid for all of them. They have to be in some way separated in the Manifest
+object.
-As such, they have to be considered separately in the Manifest.
-This can be done by putting each in a different Period:
+That's a case where Periods will be used.
+Here is a visual representation of how the Periods would be divided here:
```
Period 1 Period 2 Period 3
08h05 09h00 10h30 now
@@ -205,8 +217,8 @@ This can be done by putting each in a different Period:
TV Show Italian Film American Blockbuster
```
-Each of these Periods will be linked to different audio, video and text tracks,
-themselves linked to different qualities.
+Each of these Periods will be linked to different audio, video and text
+Adaptations, themselves linked to different Representations.
@@ -216,7 +228,7 @@ A Representation is an element of an [Adaptation](#adaptation), and by extension
of the [Manifest](#manifest)) that describes an interchangeable way to represent
the parent Adaptation.
-For example, a video Adaptation can have several representation, each having
+For example, a video Adaptation can have several Representations, each having
its own bitrate, its own width or its own height.
The idea behind a Representation is that it can be changed by any other one in
the same Adaptation as the content plays.
@@ -229,4 +241,5 @@ A Representation has its equivalent in multiple Streaming technologies. It is
roughly the same as:
- DASH's _Representation_
- Microsoft Smooth Streaming's _QualityIndex_
- - HLS' _variant_
+ - HLS' _variant_ (the notion of variant is actually a little more complex,
+ so here it's not an exact comparison)
From a2d5cdbee0b30a3de8d6d392e48db893cead56ac Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Tue, 14 Apr 2020 18:41:05 +0200
Subject: [PATCH 29/72] api: also handle preferredVideoTracks in directfile
mode
---
doc/api/player_options.md | 18 ++++---
...media_element_track_choice_manager.test.ts | 6 +++
.../api/media_element_track_choice_manager.ts | 48 ++++++++++++++++---
src/core/api/public_api.ts | 3 +-
4 files changed, 62 insertions(+), 13 deletions(-)
diff --git a/doc/api/player_options.md b/doc/api/player_options.md
index 2790d21e3e..3927440d45 100644
--- a/doc/api/player_options.md
+++ b/doc/api/player_options.md
@@ -334,8 +334,10 @@ const player = new RxPlayer({
---
-:warning: This option will have no effect for contents loaded in _DirectFile_
-mode (see [loadVideo options](./loadVideo_options.md#prop-transport)).
+:warning: This option will have no effect in _DirectFile_ mode
+(see [loadVideo options](./loadVideo_options.md#prop-transport)) when either :
+- No audio track API is supported on the current browser
+- The media file tracks are not supported on the browser
---
@@ -389,8 +391,10 @@ const player = new RxPlayer({
---
-:warning: This option will have no effect for contents loaded in _DirectFile_
-mode (see [loadVideo options](./loadVideo_options.md#prop-transport)).
+:warning: This option will have no effect in _DirectFile_ mode
+(see [loadVideo options](./loadVideo_options.md#prop-transport)) when either :
+- No text track API is supported on the current browser
+- The media file tracks are not supported on the browser
---
@@ -496,8 +500,10 @@ const player = new RxPlayer({
---
-:warning: This option will have no effect for contents loaded in _DirectFile_
-mode (see [loadVideo options](./loadVideo_options.md#prop-transport)).
+:warning: This option will have no effect in _DirectFile_ mode
+(see [loadVideo options](./loadVideo_options.md#prop-transport)) when either :
+- No video track API is supported on the current browser
+- The media file tracks are not supported on the browser
---
diff --git a/src/core/api/__tests__/media_element_track_choice_manager.test.ts b/src/core/api/__tests__/media_element_track_choice_manager.test.ts
index 02f65fd3be..44c3b9afcf 100644
--- a/src/core/api/__tests__/media_element_track_choice_manager.test.ts
+++ b/src/core/api/__tests__/media_element_track_choice_manager.test.ts
@@ -41,6 +41,7 @@ describe("API - MediaElementTrackChoiceManager", () => {
{
preferredAudioTracks: new BehaviorSubject([] as any[]),
preferredTextTracks: new BehaviorSubject([] as any[]),
+ preferredVideoTracks: new BehaviorSubject([] as any[]),
},
fakeMediaElement as any
);
@@ -79,6 +80,7 @@ describe("API - MediaElementTrackChoiceManager", () => {
{
preferredAudioTracks: new BehaviorSubject([] as any[]),
preferredTextTracks: new BehaviorSubject([] as any[]),
+ preferredVideoTracks: new BehaviorSubject([] as any[]),
},
fakeMediaElement as any
);
@@ -108,6 +110,7 @@ describe("API - MediaElementTrackChoiceManager", () => {
{
preferredAudioTracks: new BehaviorSubject([] as any[]),
preferredTextTracks: new BehaviorSubject([] as any[]),
+ preferredVideoTracks: new BehaviorSubject([] as any[]),
},
fakeMediaElement as any
);
@@ -134,6 +137,7 @@ describe("API - MediaElementTrackChoiceManager", () => {
{
preferredAudioTracks: new BehaviorSubject([] as any[]),
preferredTextTracks: new BehaviorSubject([] as any[]),
+ preferredVideoTracks: new BehaviorSubject([] as any[]),
},
fakeMediaElement as any
);
@@ -157,6 +161,7 @@ describe("API - MediaElementTrackChoiceManager", () => {
{
preferredAudioTracks: new BehaviorSubject([] as any[]),
preferredTextTracks: new BehaviorSubject([] as any[]),
+ preferredVideoTracks: new BehaviorSubject([] as any[]),
},
fakeMediaElement as any
);
@@ -183,6 +188,7 @@ describe("API - MediaElementTrackChoiceManager", () => {
{
preferredAudioTracks: new BehaviorSubject([] as any[]),
preferredTextTracks: new BehaviorSubject([] as any[]),
+ preferredVideoTracks: new BehaviorSubject([] as any[]),
},
fakeMediaElement as any
);
diff --git a/src/core/api/media_element_track_choice_manager.ts b/src/core/api/media_element_track_choice_manager.ts
index c4244b962a..dac738e5b1 100644
--- a/src/core/api/media_element_track_choice_manager.ts
+++ b/src/core/api/media_element_track_choice_manager.ts
@@ -32,6 +32,7 @@ import {
ITMTextTrackListItem,
ITMVideoTrack,
ITMVideoTrackListItem,
+ IVideoTrackPreference,
} from "./track_choice_manager";
/** Events emitted by the MediaElementTrackChoiceManager. */
@@ -169,7 +170,7 @@ function createVideoTracks(
export default class MediaElementTrackChoiceManager
extends EventEmitter {
/**
- * Array of preferred languages for audio tracks.
+ * Array of preferred settings for audio tracks.
* Sorted by order of preference descending.
*/
private _preferredAudioTracks : BehaviorSubject;
@@ -180,6 +181,12 @@ export default class MediaElementTrackChoiceManager
*/
private _preferredTextTracks : BehaviorSubject;
+ /**
+ * Array of preferred settings for video tracks.
+ * Sorted by order of preference descending.
+ */
+ private _preferredVideoTracks : BehaviorSubject;
+
/** List every available audio tracks available on the media element. */
private _audioTracks : Array<{ track: ITMAudioTrack; nativeTrack: AudioTrack }>;
/** List every available text tracks available on the media element. */
@@ -203,14 +210,15 @@ export default class MediaElementTrackChoiceManager
constructor(
defaults : { preferredAudioTracks : BehaviorSubject;
- preferredTextTracks : BehaviorSubject; },
+ preferredTextTracks : BehaviorSubject;
+ preferredVideoTracks : BehaviorSubject; },
mediaElement: HTMLMediaElement
) {
super();
- const { preferredAudioTracks, preferredTextTracks } = defaults;
- this._preferredAudioTracks = preferredAudioTracks;
- this._preferredTextTracks = preferredTextTracks;
+ this._preferredAudioTracks = defaults.preferredAudioTracks;
+ this._preferredTextTracks = defaults.preferredTextTracks;
+ this._preferredVideoTracks = defaults.preferredVideoTracks;
// TODO In practice, the audio/video/text tracks API are not always implemented on
// the media element, although Typescript HTMLMediaElement types tend to mean
@@ -301,7 +309,7 @@ export default class MediaElementTrackChoiceManager
* Throws if the wanted video track is not found.
* @param {string|number|undefined} id
*/
- public setVideoTrackById(id?: string): void {
+ public setVideoTrackById(id?: string | number): void {
for (let i = 0; i < this._videoTracks.length; i++) {
const { track, nativeTrack } = this._videoTracks[i];
if (track.id === id) {
@@ -532,6 +540,32 @@ export default class MediaElementTrackChoiceManager
}
}
+ /**
+ * Iterate over every available video tracks on the media element and over
+ * every set video track preferences to activate the preferred video track
+ * on the media element.
+ */
+ private _setPreferredVideoTrack() : void {
+ // NOTE: As we cannot access either codec information or sign interpretation
+ // information easily about the different codecs. It is the same case than
+ // if we had only tracks where those were set to undefined.
+ // Based on that, we should disable the video track as long as one of the
+ // set preferrence is "no video track" (i.e. `null`) as this is the only
+ // constraint that we know we can respect.
+ // Else, just chose the first track.
+ const preferredVideoTracks = this._preferredVideoTracks.getValue();
+ const hasNullPreference = preferredVideoTracks.some(p => p === null);
+ if (hasNullPreference) {
+ this.disableVideoTrack();
+ return;
+ }
+
+ if (this._videoTracks.length === 0) {
+ return;
+ }
+ this.setVideoTrackById(this._videoTracks[0].track.id);
+ }
+
/**
* Monitor native tracks add, remove and change callback and trigger the
* change events.
@@ -647,6 +681,7 @@ export default class MediaElementTrackChoiceManager
const newVideoTracks = createVideoTracks(this._nativeVideoTracks);
if (areTrackArraysDifferent(this._videoTracks, newVideoTracks)) {
this._videoTracks = newVideoTracks;
+ this._setPreferredVideoTrack();
this.trigger("availableVideoTracksChange", this.getAvailableVideoTracks());
const chosenVideoTrack = this._getPrivateChosenVideoTrack();
if (chosenVideoTrack?.nativeTrack !== this._lastEmittedNativeVideoTrack) {
@@ -661,6 +696,7 @@ export default class MediaElementTrackChoiceManager
const newVideoTracks = createVideoTracks(this._nativeVideoTracks);
if (areTrackArraysDifferent(this._videoTracks, newVideoTracks)) {
this._videoTracks = newVideoTracks;
+ this._setPreferredVideoTrack();
this.trigger("availableVideoTracksChange", this.getAvailableVideoTracks());
const chosenVideoTrack = this._getPrivateChosenVideoTrack();
if (chosenVideoTrack?.nativeTrack !== this._lastEmittedNativeVideoTrack) {
diff --git a/src/core/api/public_api.ts b/src/core/api/public_api.ts
index 721dacd4cd..1f648072d1 100644
--- a/src/core/api/public_api.ts
+++ b/src/core/api/public_api.ts
@@ -734,7 +734,8 @@ class Player extends EventEmitter {
new BehaviorSubject([defaultAudioTrack]),
preferredTextTracks: defaultTextTrack === undefined ?
this._priv_preferredTextTracks :
- new BehaviorSubject([defaultTextTrack]) },
+ new BehaviorSubject([defaultTextTrack]),
+ preferredVideoTracks: this._priv_preferredVideoTracks },
this.videoElement
);
From bb35a6a3299d7dc1fb320c699b45115f25de9154 Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Wed, 15 Apr 2020 16:37:03 +0200
Subject: [PATCH 30/72] doc: update warning relative to disabling the current
video track in directfile mode
---
doc/api/index.md | 26 ++++++++++++++++----------
doc/api/player_events.md | 11 ++++++-----
2 files changed, 22 insertions(+), 15 deletions(-)
diff --git a/doc/api/index.md b/doc/api/index.md
index 2d66caf9c6..0db59d7f82 100644
--- a/doc/api/index.md
+++ b/doc/api/index.md
@@ -1329,19 +1329,25 @@ _return value_: ``void``
Deactivate the current video track, if one.
-Might enter in `RELOADING` state for a short period, if not in _DirectFile_
+Might enter in `RELOADING` state for a short period after calling this API.
---
-:warning: This option will have no effect in _DirectFile_ mode
-(see [loadVideo options](./loadVideo_options.md#prop-transport)) when either :
-- No video track API was supported on the current browser
-- The media file tracks are not supported on the browser
-
-:warning: On Safari, and only for contents played in the _DirectFile_ mode (see
-[loadVideo options](./loadVideo_options.md#prop-transport)), a video track may
-have been disabled but still displaying on screen.
-Based on our understanding of the standard, we consider this to be a Safari bug.
+:warning: This option may have no effect in _DirectFile_ mode
+(see [loadVideo options](./loadVideo_options.md#prop-transport)).
+The directfile mode is a special case here because when in it, the RxPlayer
+depends for track selection on the [corresponding HTML
+standard](https://html.spec.whatwg.org/multipage/media.html) as implemented by
+the different browsers.
+Though this standard says nothing about not being able to disable the video
+track (or to stay more in line with their terms: to not select any video track),
+no browser implementation actually seem to be able to do it, even when the
+corresponding browser APIs show that no video track is currently selected.
+This might be a bug on their parts.
+
+Due to this fact, we do not recommend using this API in directfile mode for
+now. You might even receive a reassuring `videoTrackChange` event (with a `null`
+payload) while the video track is still actually active.
---
diff --git a/doc/api/player_events.md b/doc/api/player_events.md
index 1ca0cbb321..dd08322e4d 100644
--- a/doc/api/player_events.md
+++ b/doc/api/player_events.md
@@ -326,12 +326,13 @@ properties:
- ``frameRate`` (``string|undefined``): The video framerate.
-A null payload means that video track has been disabled.
+A `null` payload means that video track has been disabled.
-:warning: On Safari, and only for contents played in the _DirectFile_ mode (see
-[loadVideo options](./loadVideo_options.md#prop-transport)), a video track may
-have been disabled but still displaying on screen.
-Based on our understanding of the standard, we consider this to be a Safari bug.
+:warning: In _DirectFile_ mode, a `null` payload may be received even if the
+video track is still visually active.
+This seems due to difficult-to-detect browser bugs. We recommend not disabling
+the video track when in directfile mode to avoid that case (this is documented
+in the corresponding APIs).
### availableAudioBitratesChange ###############################################
From 86b8406edcb83d31760a7de60a21b68cc2ea70a4 Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Thu, 2 Apr 2020 18:54:02 +0200
Subject: [PATCH 31/72] init: postpone next Manifest refresh when less than 3
seconds if the parsing takes too much time
---
src/core/init/manifest_update_scheduler.ts | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
diff --git a/src/core/init/manifest_update_scheduler.ts b/src/core/init/manifest_update_scheduler.ts
index 9ca36aed7e..d5926cf836 100644
--- a/src/core/init/manifest_update_scheduler.ts
+++ b/src/core/init/manifest_update_scheduler.ts
@@ -117,11 +117,17 @@ export default function manifestUpdateScheduler({
if (manifest.lifetime === undefined || manifest.lifetime < 0) {
autoRefresh$ = EMPTY;
} else {
- const { parsingTime, updatingTime } = manifestInfos;
let autoRefreshInterval = manifest.lifetime * 1000 - timeSinceRequest;
- if (parsingTime + (updatingTime ?? 0) >= (manifest.lifetime * 1000) / 4) {
- const newInterval = Math.max(autoRefreshInterval, 0)
- + parsingTime + (updatingTime ?? 0);
+ if (manifest.lifetime < 3 && totalUpdateTime >= 100) {
+ const defaultDelay = (3 - manifest.lifetime) * 1000 + autoRefreshInterval;
+ const newInterval = Math.max(defaultDelay,
+ Math.max(autoRefreshInterval, 0) + totalUpdateTime);
+ log.info("MUS: Manifest update rythm is too frequent. Postponing next request.",
+ autoRefreshInterval,
+ newInterval);
+ autoRefreshInterval = newInterval;
+ } else if (totalUpdateTime >= (manifest.lifetime * 1000) / 10) {
+ const newInterval = Math.max(autoRefreshInterval, 0) + totalUpdateTime;
log.info("MUS: Manifest took too long to parse. Postponing next request",
autoRefreshInterval,
newInterval);
From c1f0437fccf2b008d1904a41b34a97f0caedfde2 Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Mon, 20 Apr 2020 10:01:07 +0200
Subject: [PATCH 32/72] doc: simplify player_options.md
---
doc/api/player_options.md | 144 ++++++++++++++++++--------------------
1 file changed, 70 insertions(+), 74 deletions(-)
diff --git a/doc/api/player_options.md b/doc/api/player_options.md
index 3927440d45..ccc6dfa573 100644
--- a/doc/api/player_options.md
+++ b/doc/api/player_options.md
@@ -25,11 +25,11 @@
## Overview ####################################################################
-Player options are options given to the player on instantiation. It's an object
-with multiple properties.
+Player options are options given to the player on instantiation.
-None of them are mandatory. For most usecase though, you might want to set at
-least the associated video element via the ``videoElement`` property.
+It's an object with multiple properties. None of them are mandatory.
+For most usecase though, you might want to set at least the associated video
+element via the ``videoElement`` property.
@@ -41,7 +41,9 @@ least the associated video element via the ``videoElement`` property.
_type_: ``HTMLMediaElement|undefined``
-The video element the player will use.
+The media element the player will use.
+
+Note that this can be a `` or an `` element.
```js
// Instantiate the player with the first video element in the DOM
@@ -50,14 +52,14 @@ const player = new Player({
});
```
-If not defined, a new video element will be created without being inserted in
-the document, you will have to do it yourself through the ``getVideoElement``
-method:
+If not defined, a `` element will be created without being inserted in
+the document. You will have to do it yourself through the ``getVideoElement``
+method to add it yourself:
```js
const player = new Player();
const videoElement = player.getVideoElement();
-document.appendChild(videoElement);
+document.body.appendChild(videoElement);
```
@@ -71,12 +73,10 @@ _defaults_: ``0``
This is a ceil value for the initial video bitrate chosen.
That is, the first video [Representation](../terms.md#representation) chosen
-will be:
-
- - inferior to this value.
-
- - the closest available to this value (after filtering out the other,
- superior, ones)
+will be both:
+ - inferior or equal to this value.
+ - the closest possible to this value (after filtering out the ones with a
+ superior bitrate).
If no Representation is found to respect those rules, the Representation with
@@ -91,13 +91,11 @@ const player = new Player({
});
```
----
+--
:warning: This option will have no effect for contents loaded in _DirectFile_
mode (see [loadVideo options](./loadVideo_options.md#prop-transport)).
----
-
### initialAudioBitrate ########################################################
@@ -110,12 +108,9 @@ This is a ceil value for the initial audio bitrate chosen.
That is, the first audio [Representation](../terms.md#representation) chosen
will be:
-
- - inferior to this value.
-
- - the closest available to this value (after filtering out the other,
- superior, ones)
-
+ - inferior or equal to this value.
+ - the closest possible to this value (after filtering out the ones with a
+ superior bitrate).
If no Representation is found to respect those rules, the Representation with
the lowest bitrate will be chosen instead. Thus, the default value - ``0`` -
@@ -129,13 +124,11 @@ const player = new Player({
});
```
----
+--
:warning: This option will have no effect for contents loaded in _DirectFile_
mode (see [loadVideo options](./loadVideo_options.md#prop-transport)).
----
-
### maxVideoBitrate ############################################################
@@ -161,13 +154,11 @@ call.
This limit can be removed by setting it to ``Infinity`` (which is the default
value).
----
+--
:warning: This option will have no effect for contents loaded in _DirectFile_
mode (see [loadVideo options](./loadVideo_options.md#prop-transport)).
----
-
### maxAudioBitrate ############################################################
@@ -193,13 +184,11 @@ call.
This limit can be removed by setting it to ``Infinity`` (which is the default
value).
----
+--
:warning: This option will have no effect for contents loaded in _DirectFile_
mode (see [loadVideo options](./loadVideo_options.md#prop-transport)).
----
-
### wantedBufferAhead ##########################################################
@@ -211,16 +200,14 @@ _defaults_: ``30``
Set the default buffering goal, as a duration ahead of the current position, in
seconds.
-Once this size of buffer is reached, the player won't try to download new video
+Once this size of buffer is reached, the player won't try to download new
segments anymore.
----
+--
:warning: This option will have no effect for contents loaded in _DirectFile_
mode (see [loadVideo options](./loadVideo_options.md#prop-transport)).
----
-
### preferredAudioTracks #######################################################
@@ -332,15 +319,13 @@ const player = new RxPlayer({
]
```
----
+--
:warning: This option will have no effect in _DirectFile_ mode
(see [loadVideo options](./loadVideo_options.md#prop-transport)) when either :
- No audio track API is supported on the current browser
- The media file tracks are not supported on the browser
----
-
### preferredTextTracks ########################################################
@@ -389,7 +374,7 @@ const player = new RxPlayer({
});
```
----
+--
:warning: This option will have no effect in _DirectFile_ mode
(see [loadVideo options](./loadVideo_options.md#prop-transport)) when either :
@@ -505,8 +490,6 @@ const player = new RxPlayer({
- No video track API is supported on the current browser
- The media file tracks are not supported on the browser
----
-
### maxBufferAhead #############################################################
@@ -515,23 +498,34 @@ _type_: ``Number|undefined``
_defaults_: ``Infinity``
-Set the default maximum kept buffer ahead of the current position, in seconds.
-Everything superior to that limit (``currentPosition + maxBufferAhead``) will be
-automatically garbage collected. This feature is not necessary as
-the browser is already supposed to deallocate memory from old segments if/when
-the memory is scarce.
+Set the maximum kept buffer ahead of the current position, in seconds.
-However on some custom targets, or just to better control the memory imprint of
-the player, you might want to set this limit. You can set it to ``Infinity`` to
-remove any limit and just let the browser do this job.
+Everything superior to that limit (``currentPosition + maxBufferAhead``) will
+be automatically garbage collected.
----
+This feature is not necessary as the browser should by default correctly
+remove old segments from memory if/when the memory is scarce.
+
+However on some custom targets, or just to better control the memory footprint
+of the player, you might want to set this limit.
+
+Its default value, ``Infinity``, will remove this limit and just let the browser
+do this job instead.
+
+The minimum value between this one and the one returned by
+``getWantedBufferAhead`` will be considered when downloading new segments.
+
+:warning: Bear in mind that a too-low configuration there (e.g. inferior to
+``10``) might prevent the browser to play the content at all.
+
+You can update that limit at any time through the [setMaxBufferAhead
+method](./index.md#meth-setMaxBufferAhead).
+
+--
:warning: This option will have no effect for contents loaded in _DirectFile_
mode (see [loadVideo options](./loadVideo_options.md#prop-transport)).
----
-
### maxBufferBehind ############################################################
@@ -540,24 +534,28 @@ _type_: ``Number|undefined``
_defaults_: ``Infinity``
-Set the default maximum kept past buffer, in seconds.
+Set the maximum kept buffer before the current position, in seconds.
+
Everything before that limit (``currentPosition - maxBufferBehind``) will be
automatically garbage collected.
-This feature is not necessary as the browser is already supposed to deallocate
-memory from old segments if/when the memory is scarce.
+This feature is not necessary as the browser should by default correctly
+remove old segments from memory if/when the memory is scarce.
-However on some custom targets, or just to better control the memory imprint of
-the player, you might want to set this limit. You can set it to ``Infinity`` to
-remove any limit and just let the browser do this job.
+However on some custom targets, or just to better control the memory footprint
+of the player, you might want to set this limit.
----
+Its default value, ``Infinity``, will remove this limit and just let the browser
+do this job instead.
+
+You can update that limit at any time through the [setMaxBufferBehind
+method](./index.md#meth-setMaxBufferBehind).
+
+--
:warning: This option will have no effect for contents loaded in _DirectFile_
mode (see [loadVideo options](./loadVideo_options.md#prop-transport)).
----
-
### limitVideoWidth ############################################################
@@ -586,13 +584,11 @@ const player = Player({
For some reasons (displaying directly a good quality when switching to
fullscreen, specific environments), you might not want to activate this limit.
----
+--
:warning: This option will have no effect for contents loaded in _DirectFile_
mode (see [loadVideo options](./loadVideo_options.md#prop-transport)).
----
-
### throttleVideoBitrateWhenHidden #############################################
@@ -613,13 +609,11 @@ const player = Player({
});
```
----
+--
:warning: This option will have no effect for contents loaded in _DirectFile_
mode (see [loadVideo options](./loadVideo_options.md#prop-transport)).
----
-
### stopAtEnd ##################################################################
@@ -648,12 +642,16 @@ const player = Player({
### throttleWhenHidden #########################################################
----
+--
:warning: This option is deprecated, it will disappear in the next major release
``v4.0.0`` (see [Deprecated APIs](./deprecated.md)).
----
+Please use the
+[throttleVideoBitrateWhenHidden](#prop-throttleVideoBitrateWhenHidden) property
+instead, which is better defined for advanced cases, such as Picture-In-Picture.
+
+--
_type_: ``Boolean``
@@ -669,9 +667,7 @@ const player = Player({
});
```
----
+--
:warning: This option will have no effect for contents loaded in _DirectFile_
mode (see [loadVideo options](./loadVideo_options.md#prop-transport)).
-
----
From 47b9fd46f232eae4525d84ac9795de4753a63002 Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Tue, 14 Apr 2020 18:42:51 +0200
Subject: [PATCH 33/72] api: fix dumb issue where preferredVideoTracks would
not be applied if a `defaultTextTrack` argument was set
---
src/core/api/public_api.ts | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/src/core/api/public_api.ts b/src/core/api/public_api.ts
index 1f648072d1..8cb8866d30 100644
--- a/src/core/api/public_api.ts
+++ b/src/core/api/public_api.ts
@@ -2091,9 +2091,7 @@ class Player extends EventEmitter {
preferredTextTracks: initialTextTrack === undefined ?
this._priv_preferredTextTracks :
new BehaviorSubject([initialTextTrack]),
- preferredVideoTracks: initialTextTrack === undefined ?
- this._priv_preferredVideoTracks :
- new BehaviorSubject([] as IVideoTrackPreference[]),
+ preferredVideoTracks: this._priv_preferredVideoTracks,
});
fromEvent(manifest, "manifestUpdate")
From dfd7847de810efccc943cc37aedbcd9ac9554333 Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Mon, 20 Apr 2020 10:14:13 +0200
Subject: [PATCH 34/72] doc: update comment style in core buffers and
source-buffers
---
.../buffers/adaptation/adaptation_buffer.ts | 94 +++++++++++++------
src/core/buffers/types.ts | 2 +-
.../source_buffers/source_buffers_store.ts | 67 ++++++++-----
3 files changed, 109 insertions(+), 54 deletions(-)
diff --git a/src/core/buffers/adaptation/adaptation_buffer.ts b/src/core/buffers/adaptation/adaptation_buffer.ts
index c87127fee2..de0a5d2da5 100644
--- a/src/core/buffers/adaptation/adaptation_buffer.ts
+++ b/src/core/buffers/adaptation/adaptation_buffer.ts
@@ -15,13 +15,13 @@
*/
/**
- * This file allows to create AdaptationBuffers.
+ * This file allows to create `AdaptationBuffer`s.
*
- * An AdaptationBuffer downloads and push segment for a single Adaptation (e.g.
- * a single audio or text track).
+ * An `AdaptationBuffer` downloads and push segment for a single Adaptation
+ * (e.g. a single audio, video or text track).
* It chooses which Representation to download mainly thanks to the
- * ABRManager, and orchestrates the various RepresentationBuffer, which will
- * download and push segments for a single Representation.
+ * ABRManager, and orchestrates a RepresentationBuffer, which will download and
+ * push segments corresponding to a chosen Representation.
*/
import {
@@ -82,33 +82,68 @@ import {
IRepresentationChangeEvent,
} from "../types";
+/** `Clock tick` information needed by the AdaptationBuffer. */
export interface IAdaptationBufferClockTick extends IRepresentationBufferClockTick {
- bufferGap : number; // /!\ bufferGap of the SourceBuffer
- duration : number; // duration of the HTMLMediaElement
- isPaused: boolean; // If true, the player is on pause
- speed : number; // Current regular speed asked by the user
+ /**
+ * For the current SourceBuffer, difference in seconds between the next position
+ * where no segment data is available and the current position.
+ */
+ bufferGap : number;
+ /** `duration` property of the HTMLMediaElement on which the content plays. */
+ duration : number;
+ /** If true, the player has been put on pause. */
+ isPaused: boolean;
+ /** Last "playback rate" asked by the user. */
+ speed : number;
}
+/** Arguments given when creating a new `AdaptationBuffer`. */
export interface IAdaptationBufferArguments {
- abrManager : ABRManager; // Estimate best Representation
- clock$ : Observable; // Emit current playback conditions.
+ /**
+ * Module allowing to find the best Representation depending on the current
+ * conditions like the current network bandwidth.
+ */
+ abrManager : ABRManager;
+ /**
+ * Regularly emit playback conditions.
+ * The main AdaptationBuffer logic will be triggered on each `tick`.
+ */
+ clock$ : Observable;
+ /** Content you want to create this buffer for. */
content : { manifest : Manifest;
period : Period;
- adaptation : Adaptation; }; // content to download
- options: { manualBitrateSwitchingMode : "seamless" | "direct" }; // Switch strategy
- queuedSourceBuffer : QueuedSourceBuffer; // Interact with the SourceBuffer
- segmentFetcherCreator : SegmentFetcherCreator; // Load and parse segments
- wantedBufferAhead$ : BehaviorSubject; // Buffer goal wanted by the user
+ adaptation : Adaptation; };
+ /**
+ * Strategy taken when the user switch manually the current Representation:
+ * - "seamless": the switch will happen smoothly, with the Representation
+ * with the new bitrate progressively being pushed alongside the old
+ * Representation.
+ * - "direct": hard switch. The Representation switch will be directly
+ * visible but may necessitate the current MediaSource to be reloaded.
+ */
+ options: { manualBitrateSwitchingMode : "seamless" | "direct" };
+ /** SourceBuffer wrapper - needed to push media segments. */
+ queuedSourceBuffer : QueuedSourceBuffer;
+ /** Module used to fetch the wanted media segments. */
+ segmentFetcherCreator : SegmentFetcherCreator;
+ /**
+ * "Buffer goal" wanted, or the ideal amount of time ahead of the current
+ * position in the current SourceBuffer. When this amount has been reached
+ * this AdaptationBuffer won't try to download new segments.
+ */
+ wantedBufferAhead$ : BehaviorSubject;
}
/**
- * Create new Buffer Observable linked to the given Adaptation.
+ * Create new AdaptationBuffer Observable, which task will be to download the
+ * media data for a given Adaptation (i.e. "track").
*
* It will rely on the ABRManager to choose at any time the best Representation
* for this Adaptation and then run the logic to download and push the
* corresponding segments in the SourceBuffer.
*
- * It will emit various events to report its status to the caller.
+ * After being subscribed to, it will start running and will emit various events
+ * to report its current status.
*
* @param {Object} args
* @returns {Observable}
@@ -125,18 +160,23 @@ export default function AdaptationBuffer({
const directManualBitrateSwitching = options.manualBitrateSwitchingMode === "direct";
const { manifest, period, adaptation } = content;
- // The buffer goal ratio limits the wanted buffer ahead to determine the
- // buffer goal.
- //
- // It can help in cases such as : the current browser has issues with
- // buffering and tells us that we should try to bufferize less data :
- // https://developers.google.com/web/updates/2017/10/quotaexceedederror
+ /**
+ * The buffer goal ratio base itself on the value given by `wantedBufferAhead`
+ * to determine a more dynamic buffer goal for a given Representation.
+ *
+ * It can help in cases such as : the current browser has issues with
+ * buffering and tells us that we should try to bufferize less data :
+ * https://developers.google.com/web/updates/2017/10/quotaexceedederror
+ */
const bufferGoalRatioMap: Partial> = {};
- // emit when the current RepresentationBuffer should be stopped right now
+ /** Emit when the current RepresentationBuffer should be stopped right now. */
const killCurrentBuffer$ = new Subject();
- // emit when the current RepresentationBuffer should stop making new downloads
+ /**
+ * Emit when the current RepresentationBuffer should stop making new
+ * downloads, and terminate itself when done.
+ */
const terminateCurrentBuffer$ = new Subject();
// use ABRManager for choosing the Representation
@@ -292,7 +332,7 @@ export default function AdaptationBuffer({
}
}
-// Re-export RepresentationBuffer events used by the AdaptationBufferManager
+// Re-export RepresentationBuffer events used by the AdaptationBuffer
export {
IBufferEventAddedSegment,
IBufferNeedsDiscontinuitySeek,
diff --git a/src/core/buffers/types.ts b/src/core/buffers/types.ts
index 66af9009e2..2bb7546354 100644
--- a/src/core/buffers/types.ts
+++ b/src/core/buffers/types.ts
@@ -78,12 +78,12 @@ export interface IBufferStateFull {
value : { bufferType : IBufferType };
}
+/** Emitted when a segment with protection information has been encountered. */
export interface IProtectedSegmentEvent {
type : "protected-segment";
value : { type : string;
data : Uint8Array; }; }
-// State emitted when the buffer waits
export type IRepresentationBufferStateEvent = IBufferNeededActions |
IBufferStateFull |
IBufferStateActive |
diff --git a/src/core/source_buffers/source_buffers_store.ts b/src/core/source_buffers/source_buffers_store.ts
index ea0397fa4a..ec89e8118f 100644
--- a/src/core/source_buffers/source_buffers_store.ts
+++ b/src/core/source_buffers/source_buffers_store.ts
@@ -84,11 +84,11 @@ type INativeSourceBufferType = "audio" | "video";
* same type is created.
*
* The returned SourceBuffer is actually a QueuedSourceBuffer instance which
- * wrap a SourceBuffer implementation to queue all its actions.
+ * wrap a SourceBuffer implementation and queue all its actions.
*
- * To be able to use a native SourceBuffer, you will first need to wait until
- * it is created - of course - but also until the other one is either created or
- * disabled.
+ * To be able to use a native SourceBuffer, you will first need to create it,
+ * but also wait until the other one is either created or explicitely
+ * disabled through the `disableSourceBuffer` method.
* The Observable returned by `waitForUsableSourceBuffers` will emit when
* that is the case.
*
@@ -96,9 +96,10 @@ type INativeSourceBufferType = "audio" | "video";
*/
export default class SourceBuffersStore {
/**
- * Returns true if the SourceBuffer is "native" (has to be attached to the
- * mediaSource before playback).
- * @static
+ * Returns true if the SourceBuffer is "native".
+ * Native SourceBuffers needed for the current content must all be created
+ * before the content begins to be played and cannot be disposed during
+ * playback.
* @param {string} bufferType
* @returns {Boolean}
*/
@@ -106,9 +107,23 @@ export default class SourceBuffersStore {
return shouldHaveNativeSourceBuffer(bufferType);
}
+ /**
+ * HTMLMediaElement on which the MediaSource (on which wanted SourceBuffers
+ * will be created) is attached.
+ */
private readonly _mediaElement : HTMLMediaElement;
+
+ /** MediaSource on which the SourceBuffers will be created. */
private readonly _mediaSource : MediaSource;
+ /**
+ * List of initialized and explicitely disabled SourceBuffers.
+ * SourceBuffers are actually wrapped in QueuedSourceBuffer objects for easier
+ * exploitation.
+ * A `null` value indicates that this SourceBuffers has been explicitely
+ * disabled. This means that the corresponding type (e.g. audio, video etc.)
+ * won't be needed when playing the current content.
+ */
private _initializedSourceBuffers : {
audio? : QueuedSourceBuffer; } |
- { type : "unset" } |
+ { type : "uninitialized" } |
{ type : "disabled" }
{
const initializedBuffer = this._initializedSourceBuffers[bufferType];
- return initializedBuffer === undefined ? { type: "unset" } :
+ return initializedBuffer === undefined ? { type: "uninitialized" } :
initializedBuffer === null ? { type: "disabled" } :
- { type: "set",
+ { type: "initialized",
value: initializedBuffer };
}
/**
- * Native SourceBuffers (audio and video) need to all be created before they
- * can be used.
+ * Native SourceBuffers (audio and video) needed for playing the current
+ * content need to all be created before any one can be used.
+ *
+ * This function will return an Observable emitting when any and all native
+ * Source Buffers through this store can be used.
*
* From https://w3c.github.io/media-source/#methods
* For example, a user agent may throw a QuotaExceededError
* exception if the media element has reached the HAVE_METADATA
* readyState. This can occur if the user agent's media engine
* does not support adding more tracks during playback.
- *
- * This function will return an Observable emitting when any and all native
- * Source Buffers through this store can be used.
* @return {Observable}
*/
public waitForUsableSourceBuffers() : Observable {
@@ -209,7 +224,7 @@ export default class SourceBuffersStore {
}
/**
- * Explicitely set no SourceBuffer for a given buffer type.
+ * Explicitely disable the SourceBuffer for a given buffer type.
* A call to this function is needed at least for unused native buffer types
* ("audio" and "video"), to be able to emit through
* `waitForUsableSourceBuffers` when conditions are met.
@@ -236,7 +251,7 @@ export default class SourceBuffersStore {
* already exists.
*
* Please note that you will need to wait until `this.waitForUsableSourceBuffers()`
- * has emitted before updating a native SourceBuffer.
+ * has emitted before pushing segment data to a native QueuedSourceBuffer.
* @param {string} bufferType
* @param {string} codec
* @param {Object|undefined} options
@@ -344,7 +359,7 @@ export default class SourceBuffersStore {
*/
public disposeAll() {
POSSIBLE_BUFFER_TYPES.forEach((bufferType : IBufferType) => {
- if (this.getStatus(bufferType).type === "set") {
+ if (this.getStatus(bufferType).type === "initialized") {
this.disposeSourceBuffer(bufferType);
}
});
From c490c49d8ebe371ba164b39276d26a481a051a5a Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Thu, 2 Apr 2020 18:57:38 +0200
Subject: [PATCH 35/72] init/fetchers/transports: add the concept of unsafeMode
to Manifest fetching
---
src/config.ts | 12 +++++
src/core/fetchers/index.ts | 2 +
.../manifest/create_manifest_fetcher.ts | 24 +++++++++-
src/core/fetchers/manifest/index.ts | 2 +
src/core/init/initialize_media_source.ts | 10 ++--
src/core/init/manifest_update_scheduler.ts | 47 +++++++++++++++----
src/transports/metaplaylist/pipelines.ts | 4 ++
src/transports/types.ts | 30 +++++++++---
src/utils/rx-throttle.ts | 3 ++
9 files changed, 114 insertions(+), 20 deletions(-)
diff --git a/src/config.ts b/src/config.ts
index cb6788657d..fa5aaf997b 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -749,6 +749,18 @@ export default {
} as Partial>,
/* tslint:enable no-object-literal-type-assertion */
+ /**
+ * The Manifest parsing logic has a notion of "unsafeMode" which allows to
+ * speed-up this process a lot with a small risk of de-synchronization with
+ * what actually is on the server.
+ * Because using that mode is risky, and can lead to all sort of problems, we
+ * regularly should fall back to a regular "safe" parsing every once in a
+ * while.
+ * This value defines how many consecutive time maximum the "unsafeMode"
+ * parsing can be done.
+ */
+ MAX_CONSECUTIVE_MANIFEST_PARSING_IN_UNSAFE_MODE: 10,
+
/**
* When we detect that the local Manifest might be out-of-sync with the
* server's one, we schedule a Manifest refresh.
diff --git a/src/core/fetchers/index.ts b/src/core/fetchers/index.ts
index 1863cf23fb..f74740c95b 100644
--- a/src/core/fetchers/index.ts
+++ b/src/core/fetchers/index.ts
@@ -16,6 +16,7 @@
import createManifestFetcher, {
IManifestFetcherParsedResult,
+ IManifestFetcherParserOptions,
IManifestFetcherWarningEvent,
} from "./manifest";
import SegmentFetcherCreator, {
@@ -31,6 +32,7 @@ export {
createManifestFetcher,
SegmentFetcherCreator,
+ IManifestFetcherParserOptions,
IManifestFetcherParsedResult,
IManifestFetcherWarningEvent,
diff --git a/src/core/fetchers/manifest/create_manifest_fetcher.ts b/src/core/fetchers/manifest/create_manifest_fetcher.ts
index 6b758a33a9..b20b954dba 100644
--- a/src/core/fetchers/manifest/create_manifest_fetcher.ts
+++ b/src/core/fetchers/manifest/create_manifest_fetcher.ts
@@ -68,13 +68,31 @@ export interface IManifestFetcherWarningEvent {
value : ICustomError;
}
+export interface IManifestFetcherParserOptions {
+ /**
+ * If set, offset to add to `performance.now()` to obtain the current
+ * server's time.
+ */
+ externalClockOffset? : number;
+ /** The previous value of the Manifest (when updating). */
+ previousManifest : Manifest | null;
+ /**
+ * If set to `true`, the Manifest parser can perform advanced optimizations
+ * to speed-up the parsing process. Those optimizations might lead to a
+ * de-synchronization with what is actually on the server, hence the "unsafe"
+ * part.
+ * To use with moderation and only when needed.
+ */
+ unsafeMode : boolean;
+}
+
/** Response emitted by a Manifest fetcher. */
export interface IManifestFetcherResponse {
/** To differentiate it from a "warning" event. */
type : "response";
/** Allows to parse a fetched Manifest into a `Manifest` structure. */
- parse(parserOptions : { externalClockOffset? : number }) :
+ parse(parserOptions : IManifestFetcherParserOptions) :
Observable;
}
@@ -153,14 +171,16 @@ export default function createManifestFetcher(
return {
type: "response",
- parse(parserOptions : { externalClockOffset? : number }) {
+ parse(parserOptions : IManifestFetcherParserOptions) {
return observableMerge(
schedulerWarnings$
.pipe(map(err => ({ type: "warning" as const, value: err }))),
parser({ response: evt.value,
url,
externalClockOffset: parserOptions.externalClockOffset,
+ previousManifest: parserOptions.previousManifest,
scheduleRequest,
+ unsafeMode: parserOptions.unsafeMode,
}).pipe(
catchError((error: unknown) => {
throw formatError(error, {
diff --git a/src/core/fetchers/manifest/index.ts b/src/core/fetchers/manifest/index.ts
index d37ea89b24..baa4877b15 100644
--- a/src/core/fetchers/manifest/index.ts
+++ b/src/core/fetchers/manifest/index.ts
@@ -16,11 +16,13 @@
import createManifestFetcher, {
IManifestFetcherParsedResult,
+ IManifestFetcherParserOptions,
IManifestFetcherWarningEvent,
} from "./create_manifest_fetcher";
export default createManifestFetcher;
export {
IManifestFetcherParsedResult,
+ IManifestFetcherParserOptions,
IManifestFetcherWarningEvent,
};
diff --git a/src/core/init/initialize_media_source.ts b/src/core/init/initialize_media_source.ts
index c7aa0e4b35..9062c5f4d4 100644
--- a/src/core/init/initialize_media_source.ts
+++ b/src/core/init/initialize_media_source.ts
@@ -55,6 +55,7 @@ import {
import {
createManifestFetcher,
IManifestFetcherParsedResult,
+ IManifestFetcherParserOptions,
SegmentFetcherCreator,
} from "../fetchers";
import { ITextTrackSourceBufferOptions } from "../source_buffers";
@@ -189,13 +190,13 @@ export default function InitializeOnMediaSource(
* Fetch and parse the manifest from the URL given.
* Throttled to avoid doing multiple simultaneous requests.
*/
- const fetchManifest = throttle((manifestURL? : string,
- externalClockOffset? : number)
+ const fetchManifest = throttle((manifestURL : string | undefined,
+ options : IManifestFetcherParserOptions)
: Observable =>
manifestFetcher.fetch(manifestURL).pipe(
mergeMap((response) => response.type === "warning" ?
observableOf(response) : // bubble-up warnings
- response.parse({ externalClockOffset })),
+ response.parse(options)),
share()));
/** Interface used to download segments. */
@@ -238,7 +239,8 @@ export default function InitializeOnMediaSource(
take(1));
/** Do the first Manifest request. */
- const initialManifestRequest$ = fetchManifest(url, undefined).pipe(
+ const initialManifestRequest$ = fetchManifest(url, { previousManifest: null,
+ unsafeMode: false }).pipe(
deferSubscriptions(),
share());
diff --git a/src/core/init/manifest_update_scheduler.ts b/src/core/init/manifest_update_scheduler.ts
index d5926cf836..cb08565a53 100644
--- a/src/core/init/manifest_update_scheduler.ts
+++ b/src/core/init/manifest_update_scheduler.ts
@@ -33,10 +33,14 @@ import config from "../../config";
import log from "../../log";
import Manifest from "../../manifest";
import isNonEmptyString from "../../utils/is_non_empty_string";
-import { IManifestFetcherParsedResult } from "../fetchers";
+import {
+ IManifestFetcherParsedResult,
+ IManifestFetcherParserOptions,
+} from "../fetchers";
import { IWarningEvent } from "./types";
-const { FAILED_PARTIAL_UPDATE_MANIFEST_REFRESH_DELAY } = config;
+const { FAILED_PARTIAL_UPDATE_MANIFEST_REFRESH_DELAY,
+ MAX_CONSECUTIVE_MANIFEST_PARSING_IN_UNSAFE_MODE } = config;
/** Arguments to give to the `manifestUpdateScheduler` */
export interface IManifestUpdateSchedulerArguments {
@@ -57,7 +61,7 @@ export interface IManifestUpdateSchedulerArguments {
/** Function defined to refresh the Manifest */
export type IManifestFetcher =
- (manifestURL? : string, externalClockOffset?: number) =>
+ (manifestURL : string | undefined, options : IManifestFetcherParserOptions) =>
Observable;
/** Events sent by the `IManifestRefreshScheduler` Observable */
@@ -93,13 +97,24 @@ export default function manifestUpdateScheduler({
// The Manifest always keeps the same Manifest
const { manifest } = initialManifest;
+ /** Number of consecutive times the parsing has been done in `unsafeMode`. */
+ let consecutiveUnsafeMode = 0;
function handleManifestRefresh$(
manifestInfos: { manifest: Manifest;
sendingTime?: number;
receivedTime? : number;
parsingTime : number;
updatingTime? : number; }): Observable {
- const { sendingTime } = manifestInfos;
+ const { sendingTime,
+ parsingTime,
+ updatingTime } = manifestInfos;
+ const totalUpdateTime = parsingTime + (updatingTime ?? 0);
+
+ // Only perform parsing in `unsafeMode` when the last full parsing took too
+ // much time and do not go higher than the maximum consecutive time.
+ const unsafeMode = consecutiveUnsafeMode > 0 ?
+ consecutiveUnsafeMode < MAX_CONSECUTIVE_MANIFEST_PARSING_IN_UNSAFE_MODE :
+ totalUpdateTime >= 100;
const internalRefresh$ = scheduleRefresh$
.pipe(mergeMap(({ completeRefresh, delay }) => {
@@ -148,7 +163,8 @@ export default function manifestUpdateScheduler({
// - its lifetime expired.
return observableMerge(autoRefresh$, internalRefresh$, expired$).pipe(
take(1),
- mergeMap(({ completeRefresh }) => refreshManifest(completeRefresh)),
+ mergeMap(({ completeRefresh }) => refreshManifest({ completeRefresh,
+ unsafeMode })),
mergeMap(evt => {
if (evt.type === "warning") {
return observableOf(evt);
@@ -166,7 +182,9 @@ export default function manifestUpdateScheduler({
* @returns {Observable}
*/
function refreshManifest(
- completeRefresh : boolean
+ { completeRefresh,
+ unsafeMode } : { completeRefresh : boolean;
+ unsafeMode : boolean; }
) : Observable {
const fullRefresh = completeRefresh || manifestUpdateUrl === undefined;
const refreshURL = fullRefresh ? manifest.getUrl() :
@@ -176,7 +194,19 @@ export default function manifestUpdateScheduler({
return EMPTY;
}
const externalClockOffset = manifest.clockOffset;
- return fetchManifest(refreshURL, externalClockOffset)
+
+ if (unsafeMode) {
+ consecutiveUnsafeMode += 1;
+ log.info("Init: Refreshing the Manifest in \"unsafeMode\" for the " +
+ String(consecutiveUnsafeMode) + " consecutive time.");
+ } else if (consecutiveUnsafeMode > 0) {
+ log.info("Init: Not parsing the Manifest in \"unsafeMode\" anymore after " +
+ String(consecutiveUnsafeMode) + " consecutive times.");
+ consecutiveUnsafeMode = 0;
+ }
+ return fetchManifest(refreshURL, { externalClockOffset,
+ previousManifest: manifest,
+ unsafeMode })
.pipe(mergeMap((value) => {
if (value.type === "warning") {
return observableOf(value);
@@ -200,7 +230,8 @@ export default function manifestUpdateScheduler({
return startManualRefreshTimer(FAILED_PARTIAL_UPDATE_MANIFEST_REFRESH_DELAY,
minimumManifestUpdateInterval,
newSendingTime)
- .pipe(mergeMap(() => refreshManifest(true)));
+ .pipe(mergeMap(() =>
+ refreshManifest({ completeRefresh: true, unsafeMode: false })));
}
}
return observableOf({ type: "parsed" as const,
diff --git a/src/transports/metaplaylist/pipelines.ts b/src/transports/metaplaylist/pipelines.ts
index accb2ba805..b837244fef 100644
--- a/src/transports/metaplaylist/pipelines.ts
+++ b/src/transports/metaplaylist/pipelines.ts
@@ -175,7 +175,9 @@ export default function(options : ITransportOptions): ITransportPipelines {
parser(
{ response,
url: loaderURL,
+ previousManifest,
scheduleRequest,
+ unsafeMode,
externalClockOffset } : IManifestParserArguments
) : IManifestParserObservable {
const url = response.url === undefined ? loaderURL :
@@ -214,6 +216,8 @@ export default function(options : ITransportOptions): ITransportPipelines {
return transport.manifest.parser({ response: responseValue,
url: ressource.url,
scheduleRequest,
+ previousManifest,
+ unsafeMode,
externalClockOffset })
.pipe(map((parserData) : Manifest => parserData.manifest));
}));
diff --git a/src/transports/types.ts b/src/transports/types.ts
index 4bb3cef31a..abc8ea3a36 100644
--- a/src/transports/types.ts
+++ b/src/transports/types.ts
@@ -147,16 +147,34 @@ export type ISegmentLoaderObservable = Observable; // Response from the loader
- url? : string; // URL originally requested
- externalClockOffset? : number; // If set, offset to add to `performance.now()`
- // to obtain the current server's time
-
- // allow the parser to load supplementary ressources (of type U)
+ /** Response obtained from the loader. */
+ response : ILoaderDataLoadedValue;
+ /** URL originally requested. */
+ url? : string;
+ /**
+ * If set, offset to add to `performance.now()` to obtain the current
+ * server's time.
+ */
+ externalClockOffset? : number;
+ /** The previous value of the Manifest (when updating). */
+ previousManifest : Manifest | null;
+ /**
+ * Allow the parser to ask for loading supplementary ressources while still
+ * profiting from the same retries and error management than the loader.
+ */
scheduleRequest : (request : () =>
Observable< ILoaderDataLoadedValue< Document | string > >) =>
Observable< ILoaderDataLoadedValue< Document | string > >;
+ /**
+ * If set to `true`, the Manifest parser can perform advanced optimizations
+ * to speed-up the parsing process. Those optimizations might lead to a
+ * de-synchronization with what is actually on the server, hence the "unsafe"
+ * part.
+ * To use with moderation and only when needed.
+ */
+ unsafeMode : boolean;
}
export interface ISegmentParserArguments {
diff --git a/src/utils/rx-throttle.ts b/src/utils/rx-throttle.ts
index f2244ad000..5ab162183d 100644
--- a/src/utils/rx-throttle.ts
+++ b/src/utils/rx-throttle.ts
@@ -46,6 +46,9 @@ export default function throttle(
export default function throttle(
func : (arg1: T, arg2: U) => Observable
) : (arg1: T, arg2: U) => Observable;
+export default function throttle(
+ func : (arg1: T, arg2: U, arg3: V) => Observable
+) : (arg1: T, arg2: U, arg3: V) => Observable;
export default function throttle(
func : (...args : Array) => Observable
) : (...args : Array) => Observable {
From 721933111f9f1e4a0904c11dc768c60f98461c78 Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Mon, 20 Apr 2020 10:01:42 +0200
Subject: [PATCH 36/72] doc: put static properties after method documentation
and update style
---
doc/api/index.md | 188 +++++++++++++++++++++++++----------------------
1 file changed, 101 insertions(+), 87 deletions(-)
diff --git a/doc/api/index.md b/doc/api/index.md
index 6c38792eff..56e11a5920 100644
--- a/doc/api/index.md
+++ b/doc/api/index.md
@@ -103,21 +103,19 @@
## Overview ####################################################################
The RxPlayer has a complete API allowing you to:
- - load and stop video or audio contents
- - perform trickmodes (play, pause, seek, etc.) as a content is loaded.
+ - load and stop contents containing video and/or audio media data
+ - control playback (play, pause, seek, etc.) when a content is loaded.
- get multiple information on the current content and on the player's state.
- - choose a specific audio language or subtitles track
- - set your own bitrate and buffer length
+ - choose a specific audio language, subtitles track video track
+ - force a given bitrate
+ - update the wanted buffer length to reach
- and more
The following pages define the entire API.
:warning: Only variables and methods defined here are considered as part of the
-API. Any other property or method you might find by using our library can change
-without notice (not considered as part of the API).
-
-Only use the documented variables and open an issue if you think it's not
-enough.
+API. Any other property or method you might find in any other way are not
+considered as part of the API and can thus change without notice.
_Note: As some terms used here might be too foreign or slightly different than
the one you're used to, we also wrote a list of terms and definitions used by
@@ -128,7 +126,8 @@ the RxPlayer [here](../terms.md)._
## Instantiation ##############################################################
-Instantiating a new player is straightforward:
+Instantiating a new RxPlayer is necessary before being able to load a content.
+Doing so is straightforward:
```js
import RxPlayer from "rx-player";
const player = new RxPlayer(options);
@@ -139,77 +138,6 @@ page](./player_options.md).
-
-## Static properties ###########################################################
-
-This chapter documents the static properties that can be found on the RxPlayer
-class.
-
-
-### version ####################################################################
-
-_type_: ``Number``
-
-The current version of the RxPlayer.
-
-
-
-### ErrorTypes #################################################################
-
-_type_: ``Object``
-
-The different "types" of Error you can get on playback error,
-
-See [the Player Error documentation](./errors.md) for more information.
-
-
-
-### ErrorCodes #################################################################
-
-_type_: ``Object``
-
-The different Error "codes" you can get on playback error,
-
-See [the Player Error documentation](./errors.md) for more information.
-
-
-
-### LogLevel ###################################################################
-
-_type_: ``string``
-
-_default_: ``"NONE"``
-
-The current level of verbosity for the RxPlayer logs. Those logs all use the
-console.
-
-From the less verbose to the most:
-
- - ``"NONE"``: no log
-
- - ``"ERROR"``: unexpected errors (via ``console.error``)
-
- - ``"WARNING"``: The previous level + minor problems encountered (via
- ``console.warn``)
-
- - ``"INFO"``: The previous levels + noteworthy events (via ``console.info``)
-
- - ``"DEBUG"``: The previous levels + normal events of the player (via
- ``console.log``)
-
-
-If the value set to this property is different than those, it will be
-automatically set to ``"NONE"``.
-
-#### Example
-
-```js
-import RxPlayer from "rx-player";
-RxPlayer.LogLevel = "WARNING";
-```
-
-
-
## Basic methods ###############################################################
@@ -556,12 +484,19 @@ The minimum seekable player position. ``null`` if no content is loaded.
This is useful for live contents, where server-side buffer size are often not
infinite. This method allows thus to seek at the earliest possible time.
+As the given position is the absolute minimum position, you might add a security
+margin (like a few seconds) when seeking to this position in a live content.
+Not doing so could led to the player being behind the minimum position after
+some time, and thus unable to continue playing.
+
+For VoD contents, as the minimum position normally don't change, seeking at the
+minimum position should not cause any issue.
+
#### Example
```js
-// seeking to the earliest position possible (beginning of the buffer for live
-// contents, position '0' for non-live contents).
-player.seekTo({ position: player.getMinimumPosition() });
+// Seeking close to the minimum position (with a 5 seconds security margin)
+player.seekTo({ position: player.getMinimumPosition() + 5 });
```
@@ -575,12 +510,20 @@ The maximum seekable player position. ``null`` if no content is loaded.
This is useful for live contents, where the buffer end updates continously.
This method allows thus to seek directly at the live edge of the content.
+Please bear in mind that seeking exactly at the maximum position is rarely a
+good idea:
+ - for VoD contents, the playback will end
+ - for live contents, the player will then need to wait until it can build
+ enough buffer.
+
+As such, we advise to remove a few seconds from that position when seeking.
+
#### Example
```js
-// seeking to the end
+// seeking 5 seconds before the end (or the live edge for live contents)
player.seekTo({
- position: player.getMaximumPosition()
+ position: player.getMaximumPosition() - 5
});
```
@@ -1986,7 +1929,7 @@ This setting can be updated either by:
-### Buffer information #########################################################
+## Buffer information ##########################################################
The methods in this chapter allows to retrieve information about what is
currently buffered.
@@ -2379,6 +2322,77 @@ const textTrack = el.textTracks.length ? el.textTracks[0] : null;
+
+## Static properties ###########################################################
+
+This chapter documents the static properties that can be found on the RxPlayer
+class.
+
+
+### version ####################################################################
+
+_type_: ``Number``
+
+The current version of the RxPlayer.
+
+
+
+### ErrorTypes #################################################################
+
+_type_: ``Object``
+
+The different "types" of Error you can get on playback error,
+
+See [the Player Error documentation](./errors.md) for more information.
+
+
+
+### ErrorCodes #################################################################
+
+_type_: ``Object``
+
+The different Error "codes" you can get on playback error,
+
+See [the Player Error documentation](./errors.md) for more information.
+
+
+
+### LogLevel ###################################################################
+
+_type_: ``string``
+
+_default_: ``"NONE"``
+
+The current level of verbosity for the RxPlayer logs. Those logs all use the
+console.
+
+From the less verbose to the most:
+
+ - ``"NONE"``: no log
+
+ - ``"ERROR"``: unexpected errors (via ``console.error``)
+
+ - ``"WARNING"``: The previous level + minor problems encountered (via
+ ``console.warn``)
+
+ - ``"INFO"``: The previous levels + noteworthy events (via ``console.info``)
+
+ - ``"DEBUG"``: The previous levels + normal events of the player (via
+ ``console.log``)
+
+
+If the value set to this property is different than those, it will be
+automatically set to ``"NONE"``.
+
+#### Example
+
+```js
+import RxPlayer from "rx-player";
+RxPlayer.LogLevel = "WARNING";
+```
+
+
+
## Tools #######################################################################
From f15444753386d616ab9f6d5317854966521f4685 Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Thu, 2 Apr 2020 18:58:20 +0200
Subject: [PATCH 37/72] parsers/manifest/dash: when on unsafeMode, use the
previous version of the Manifest to speed-up the parsing process
---
.../__tests__/parse_from_document.test.ts | 4 +
src/parsers/manifest/dash/indexes/index.ts | 27 ++
.../__tests__/parse_s_element.test.ts} | 4 +-
.../construct_timeline_from_elements.ts | 60 +++
...nstruct_timeline_from_previous_timeline.ts | 106 ++++++
.../convert_element_to_index_segment.ts | 68 ++++
.../timeline/find_first_common_start_time.ts | 154 ++++++++
.../manifest/dash/indexes/timeline/index.ts | 18 +
.../timeline/parse_s_element.ts} | 4 +-
.../timeline_representation_index.ts} | 181 +++++----
.../dash/node_parsers/SegmentTimeline.ts | 22 +-
.../__tests__/SegmentTimeline.test.ts | 85 ++---
.../manifest/dash/parse_adaptation_sets.ts | 359 +++++++++---------
src/parsers/manifest/dash/parse_mpd.ts | 13 +-
src/parsers/manifest/dash/parse_periods.ts | 19 +
.../manifest/dash/parse_representations.ts | 95 +++--
src/transports/dash/manifest_parser.ts | 3 +
17 files changed, 862 insertions(+), 360 deletions(-)
create mode 100644 src/parsers/manifest/dash/indexes/index.ts
rename src/parsers/manifest/dash/{node_parsers/__tests__/S.test.ts => indexes/timeline/__tests__/parse_s_element.test.ts} (98%)
create mode 100644 src/parsers/manifest/dash/indexes/timeline/construct_timeline_from_elements.ts
create mode 100644 src/parsers/manifest/dash/indexes/timeline/construct_timeline_from_previous_timeline.ts
create mode 100644 src/parsers/manifest/dash/indexes/timeline/convert_element_to_index_segment.ts
create mode 100644 src/parsers/manifest/dash/indexes/timeline/find_first_common_start_time.ts
create mode 100644 src/parsers/manifest/dash/indexes/timeline/index.ts
rename src/parsers/manifest/dash/{node_parsers/S.ts => indexes/timeline/parse_s_element.ts} (95%)
rename src/parsers/manifest/dash/indexes/{timeline.ts => timeline/timeline_representation_index.ts} (79%)
diff --git a/src/parsers/manifest/dash/__tests__/parse_from_document.test.ts b/src/parsers/manifest/dash/__tests__/parse_from_document.test.ts
index 47179da678..4b9a056206 100644
--- a/src/parsers/manifest/dash/__tests__/parse_from_document.test.ts
+++ b/src/parsers/manifest/dash/__tests__/parse_from_document.test.ts
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+import Manifest from "../../../../manifest";
import parseFromDocument from "../index";
describe("parseFromDocument", () => {
@@ -27,13 +28,16 @@ describe("parseFromDocument", () => {
expect(function() {
parseFromDocument(doc, {
url: "",
+ baseOnPreviousManifest: null,
externalClockOffset: 10,
aggressiveMode: false,
});
}).toThrow("document root should be MPD");
expect(function() {
+ const prevManifest = {} as any as Manifest;
parseFromDocument(doc, {
url: "",
+ baseOnPreviousManifest: prevManifest,
aggressiveMode: false,
});
}).toThrow("document root should be MPD");
diff --git a/src/parsers/manifest/dash/indexes/index.ts b/src/parsers/manifest/dash/indexes/index.ts
new file mode 100644
index 0000000000..97c92ee934
--- /dev/null
+++ b/src/parsers/manifest/dash/indexes/index.ts
@@ -0,0 +1,27 @@
+/**
+ * Copyright 2015 CANAL+ Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import BaseRepresentationIndex from "./base";
+import ListRepresentationIndex from "./list";
+import TemplateRepresentationIndex from "./template";
+import TimelineRepresentationIndex from "./timeline";
+
+export {
+ BaseRepresentationIndex,
+ ListRepresentationIndex,
+ TemplateRepresentationIndex,
+ TimelineRepresentationIndex,
+};
diff --git a/src/parsers/manifest/dash/node_parsers/__tests__/S.test.ts b/src/parsers/manifest/dash/indexes/timeline/__tests__/parse_s_element.test.ts
similarity index 98%
rename from src/parsers/manifest/dash/node_parsers/__tests__/S.test.ts
rename to src/parsers/manifest/dash/indexes/timeline/__tests__/parse_s_element.test.ts
index 24831b1bb1..c12e677c35 100644
--- a/src/parsers/manifest/dash/node_parsers/__tests__/S.test.ts
+++ b/src/parsers/manifest/dash/indexes/timeline/__tests__/parse_s_element.test.ts
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-import log from "../../../../../log";
-import parseS from "../S";
+import log from "../../../../../../log";
+import parseS from "../parse_s_element";
function testNumberAttribute(attributeName : string, variableName? : string) : void {
const _variableName = variableName == null ? attributeName : variableName;
diff --git a/src/parsers/manifest/dash/indexes/timeline/construct_timeline_from_elements.ts b/src/parsers/manifest/dash/indexes/timeline/construct_timeline_from_elements.ts
new file mode 100644
index 0000000000..1e1fe5d0ff
--- /dev/null
+++ b/src/parsers/manifest/dash/indexes/timeline/construct_timeline_from_elements.ts
@@ -0,0 +1,60 @@
+/**
+ * Copyright 2015 CANAL+ Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { IIndexSegment } from "../../../utils/index_helpers";
+import convertElementsToIndexSegment from "./convert_element_to_index_segment";
+import parseSElement, {
+ IParsedS,
+} from "./parse_s_element";
+
+/**
+ * Allows to generate the "timeline" for the "Timeline" RepresentationIndex.
+ * Call this function when the timeline is unknown.
+ * This function was added to only perform that task lazily, i.e. only when
+ * first needed.
+ * @param {HTMLCollection} elements - All S nodes constituting the corresponding
+ * SegmentTimeline node.
+ * @param {number} scaledPeriodStart - Absolute start of the concerned Period,
+ * in the same scale than the segments found in `elements`.
+ * @returns {Array.}
+ */
+export default function constructTimelineFromElements(
+ elements : HTMLCollection,
+ scaledPeriodStart : number
+) : IIndexSegment[] {
+ const initialTimeline : IParsedS[] = [];
+ for (let i = 0; i < elements.length; i++) {
+ initialTimeline.push(parseSElement(elements[i]));
+ }
+ const timeline : IIndexSegment[] = [];
+ for (let i = 0; i < initialTimeline.length; i++) {
+ const item = initialTimeline[i];
+ const previousItem = timeline[timeline.length - 1] === undefined ?
+ null :
+ timeline[timeline.length - 1];
+ const nextItem = initialTimeline[i + 1] === undefined ?
+ null :
+ initialTimeline[i + 1];
+ const timelineElement = convertElementsToIndexSegment(item,
+ previousItem,
+ nextItem,
+ scaledPeriodStart);
+ if (timelineElement != null) {
+ timeline.push(timelineElement);
+ }
+ }
+ return timeline;
+}
diff --git a/src/parsers/manifest/dash/indexes/timeline/construct_timeline_from_previous_timeline.ts b/src/parsers/manifest/dash/indexes/timeline/construct_timeline_from_previous_timeline.ts
new file mode 100644
index 0000000000..ca31d9d570
--- /dev/null
+++ b/src/parsers/manifest/dash/indexes/timeline/construct_timeline_from_previous_timeline.ts
@@ -0,0 +1,106 @@
+/**
+ * Copyright 2015 CANAL+ Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import log from "../../../../../log";
+import { IIndexSegment } from "../../../utils/index_helpers";
+import constructTimelineFromElements from "./construct_timeline_from_elements";
+import convertElementToIndexSegment from "./convert_element_to_index_segment";
+import findFirstCommonStartTime from "./find_first_common_start_time";
+import parseSElement, {
+ IParsedS,
+} from "./parse_s_element";
+
+export default function constructTimelineFromPreviousTimeline(
+ newElements : HTMLCollection,
+ prevTimeline : IIndexSegment[],
+ scaledPeriodStart : number
+) : IIndexSegment[] {
+
+ // Find first index in both timeline where a common segment is found.
+ const commonStartInfo = findFirstCommonStartTime(prevTimeline, newElements);
+ if (commonStartInfo === null) {
+ log.warn("DASH: Cannot perform \"based\" update. Common segment not found.");
+ return constructTimelineFromElements(newElements, scaledPeriodStart);
+ }
+ const { prevSegmentsIdx,
+ newElementsIdx,
+ repeatNumberInPrevSegments,
+ repeatNumberInNewElements } = commonStartInfo;
+
+ /** Guess of the number of elements in common. */
+ const numberCommonEltGuess = prevTimeline.length - prevSegmentsIdx;
+ const lastCommonEltNewEltsIdx = numberCommonEltGuess + newElementsIdx - 1;
+ if (lastCommonEltNewEltsIdx >= newElements.length) {
+ log.info("DASH: Cannot perform \"based\" update. New timeline too short");
+ return constructTimelineFromElements(newElements, scaledPeriodStart);
+ }
+
+ // Remove elements which are not available anymore
+ const newTimeline = prevTimeline.slice(prevSegmentsIdx);
+ if (repeatNumberInPrevSegments > 0) {
+ const commonEltInOldTimeline = newTimeline[0];
+ commonEltInOldTimeline.start += commonEltInOldTimeline.duration *
+ repeatNumberInPrevSegments;
+ newTimeline[0].repeatCount -= repeatNumberInPrevSegments;
+ }
+
+ if (repeatNumberInNewElements > 0 && newElementsIdx !== 0) {
+ log.info("DASH: Cannot perform \"based\" update. " +
+ "The new timeline has a different form.");
+ return constructTimelineFromElements(newElements, scaledPeriodStart);
+ }
+
+ const prevLastElement = newTimeline[newTimeline.length - 1];
+ const newCommonElt = parseSElement(newElements[lastCommonEltNewEltsIdx]);
+ const newRepeatCountOffseted = (newCommonElt.repeatCount ?? 0) -
+ repeatNumberInNewElements;
+ if (newCommonElt.duration !== prevLastElement.duration ||
+ prevLastElement.repeatCount > newRepeatCountOffseted)
+ {
+ log.info("DASH: Cannot perform \"based\" update. " +
+ "The new timeline has a different form at the beginning.");
+ return constructTimelineFromElements(newElements, scaledPeriodStart);
+ }
+
+ if (newCommonElt.repeatCount !== undefined &&
+ newCommonElt.repeatCount > prevLastElement.repeatCount)
+ {
+ prevLastElement.repeatCount = newCommonElt.repeatCount;
+ }
+
+ const newEltsToPush : IIndexSegment[] = [];
+ const items : IParsedS[] = [];
+ for (let i = lastCommonEltNewEltsIdx + 1; i < newElements.length; i++) {
+ items.push(parseSElement(newElements[i]));
+ }
+ for (let i = 0; i < items.length; i++) {
+ const item = items[i];
+ const previousItem = newEltsToPush[newEltsToPush.length - 1] === undefined ?
+ prevLastElement :
+ newEltsToPush[newEltsToPush.length - 1];
+ const nextItem = items[i + 1] === undefined ?
+ null :
+ items[i + 1];
+ const timelineElement = convertElementToIndexSegment(item,
+ previousItem,
+ nextItem,
+ scaledPeriodStart);
+ if (timelineElement !== null) {
+ newEltsToPush.push(timelineElement);
+ }
+ }
+ return newTimeline.concat(newEltsToPush);
+}
diff --git a/src/parsers/manifest/dash/indexes/timeline/convert_element_to_index_segment.ts b/src/parsers/manifest/dash/indexes/timeline/convert_element_to_index_segment.ts
new file mode 100644
index 0000000000..9602757319
--- /dev/null
+++ b/src/parsers/manifest/dash/indexes/timeline/convert_element_to_index_segment.ts
@@ -0,0 +1,68 @@
+/**
+ * Copyright 2015 CANAL+ Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import log from "../../../../../log";
+import { IIndexSegment } from "../../../utils/index_helpers";
+
+/**
+ * Translate parsed `S` node into Segment compatible with this index:
+ * Find out the start, repeatCount and duration of each of these.
+ *
+ * @param {Object} item - parsed `S` node
+ * @param {Object|null} previousItem - the previously parsed Segment (related
+ * to the `S` node coming just before). If `null`, we're talking about the first
+ * segment.
+ * @param {Object|null} nextItem - the `S` node coming next. If `null`, we're
+ * talking about the last segment.
+ * @param {number} timelineStart - Absolute start for the timeline. In the same
+ * timescale than the given `S` nodes.
+ * @returns {Object|null}
+ */
+export default function convertElementsToIndexSegment(
+ item : { start? : number; repeatCount? : number; duration? : number },
+ previousItem : IIndexSegment|null,
+ nextItem : { start? : number; repeatCount? : number; duration? : number }|null,
+ timelineStart : number
+) : IIndexSegment|null {
+ let start = item.start;
+ let duration = item.duration;
+ const repeatCount = item.repeatCount;
+ if (start == null) {
+ if (previousItem == null) {
+ start = timelineStart;
+ } else if (previousItem.duration != null) {
+ start = previousItem.start +
+ (previousItem.duration * (previousItem.repeatCount + 1));
+ }
+ }
+ if ((duration == null || isNaN(duration)) &&
+ nextItem != null && nextItem.start != null && !isNaN(nextItem.start) &&
+ start != null && !isNaN(start)
+ ) {
+ duration = nextItem.start - start;
+ }
+ if ((start != null && !isNaN(start)) &&
+ (duration != null && !isNaN(duration)) &&
+ (repeatCount == null || !isNaN(repeatCount))
+ ) {
+ return { start,
+ duration,
+ repeatCount: repeatCount === undefined ? 0 :
+ repeatCount };
+ }
+ log.warn("DASH: A \"S\" Element could not have been parsed.");
+ return null;
+}
diff --git a/src/parsers/manifest/dash/indexes/timeline/find_first_common_start_time.ts b/src/parsers/manifest/dash/indexes/timeline/find_first_common_start_time.ts
new file mode 100644
index 0000000000..e41218668c
--- /dev/null
+++ b/src/parsers/manifest/dash/indexes/timeline/find_first_common_start_time.ts
@@ -0,0 +1,154 @@
+/**
+ * Copyright 2015 CANAL+ Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { IIndexSegment } from "../../../utils/index_helpers";
+
+/**
+ * By comparing two timelines for the same content at different points in time,
+ * retrieve the index in both timelines of the first segment having the same
+ * starting time.
+ * Returns `null` if not found.
+ * @param {Array.} prevTimeline
+ * @param {HTMLCollection} newElements
+ * @returns {Object|null}
+ */
+export default function findFirstCommonStartTime(
+ prevTimeline : IIndexSegment[],
+ newElements : HTMLCollection
+) : {
+ /** Index in `prevSegments` where the first common segment start is found. */
+ prevSegmentsIdx : number;
+ /** Index in `newElements` where the first common segment start is found. */
+ newElementsIdx : number;
+ /**
+ * Value to set `repeatCount` to at `prevSegments[prevSegmentsIdx]` to
+ * retrieve the segment with the first common start time
+ */
+ repeatNumberInPrevSegments : number;
+ /**
+ * Value to set `repeatCount` to at `newElements[newElementsIdx]` to
+ * retrieve the segment with the first common start time
+ */
+ repeatNumberInNewElements : number;
+} | null {
+ if (prevTimeline.length === 0 || newElements.length === 0) {
+ return null;
+ }
+ const prevInitialStart = prevTimeline[0].start;
+ const newFirstTAttr = newElements[0].getAttribute("t");
+ const newInitialStart = newFirstTAttr === null ? null :
+ parseInt(newFirstTAttr, 10);
+ if (newInitialStart === null || Number.isNaN(newInitialStart)) {
+ return null;
+ }
+
+ if (prevInitialStart === newInitialStart) {
+ return { prevSegmentsIdx: 0,
+ newElementsIdx: 0,
+ repeatNumberInPrevSegments: 0,
+ repeatNumberInNewElements: 0 };
+
+ } else if (prevInitialStart < newInitialStart) {
+ let prevElt = prevTimeline[0];
+ let prevElementIndex = 0;
+ while (true) {
+ if (prevElt.repeatCount > 0) {
+ const diff = newInitialStart - prevElt.start;
+ if (diff % prevElt.duration === 0 &&
+ diff / prevElt.duration <= prevElt.repeatCount)
+ {
+ const repeatNumberInPrevSegments = diff / prevElt.duration;
+ return { repeatNumberInPrevSegments,
+ prevSegmentsIdx: prevElementIndex,
+ newElementsIdx: 0,
+ repeatNumberInNewElements: 0 };
+ }
+ }
+ prevElementIndex++;
+ if (prevElementIndex >= prevTimeline.length) {
+ return null;
+ }
+ prevElt = prevTimeline[prevElementIndex];
+ if (prevElt.start === newInitialStart) {
+ return { prevSegmentsIdx: prevElementIndex,
+ newElementsIdx: 0,
+ repeatNumberInPrevSegments: 0,
+ repeatNumberInNewElements: 0 };
+ } else if (prevElt.start > newInitialStart) {
+ return null;
+ }
+ }
+
+ } else {
+ let newElementsIdx = 0;
+ let newElt = newElements[0];
+ let currentTimeOffset = newInitialStart;
+ while (true) {
+ const dAttr = newElt.getAttribute("d");
+ const duration = dAttr === null ? null :
+ parseInt(dAttr, 10);
+ if (duration === null || Number.isNaN(duration)) {
+ return null;
+ }
+ const rAttr = newElt.getAttribute("r");
+ const repeatCount = rAttr === null ? null :
+ parseInt(rAttr, 10);
+
+ if (repeatCount !== null) {
+ if (Number.isNaN(repeatCount) || repeatCount < 0) {
+ return null;
+ }
+ if (repeatCount > 0) {
+ const diff = prevInitialStart - currentTimeOffset;
+ if (diff % duration === 0 &&
+ diff / duration <= repeatCount)
+ {
+ const repeatNumberInNewElements = diff / duration;
+ return { repeatNumberInPrevSegments: 0,
+ repeatNumberInNewElements,
+ prevSegmentsIdx: 0,
+ newElementsIdx };
+ }
+ }
+ currentTimeOffset += duration * (repeatCount + 1);
+ } else {
+ currentTimeOffset += duration;
+ }
+ newElementsIdx++;
+ if (newElementsIdx >= newElements.length) {
+ return null;
+ }
+ newElt = newElements[newElementsIdx];
+ const tAttr = newElt.getAttribute("t");
+ const time = tAttr === null ? null :
+ parseInt(tAttr, 10);
+ if (time !== null) {
+ if (Number.isNaN(time)) {
+ return null;
+ }
+ currentTimeOffset = time;
+ }
+ if (currentTimeOffset === prevInitialStart) {
+ return { newElementsIdx,
+ prevSegmentsIdx: 0,
+ repeatNumberInPrevSegments: 0,
+ repeatNumberInNewElements: 0 };
+ } else if (currentTimeOffset > newInitialStart) {
+ return null;
+ }
+ }
+ }
+}
diff --git a/src/parsers/manifest/dash/indexes/timeline/index.ts b/src/parsers/manifest/dash/indexes/timeline/index.ts
new file mode 100644
index 0000000000..d059c0bfef
--- /dev/null
+++ b/src/parsers/manifest/dash/indexes/timeline/index.ts
@@ -0,0 +1,18 @@
+/**
+ * Copyright 2015 CANAL+ Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import TimelineRepresentationIndex from "./timeline_representation_index";
+export default TimelineRepresentationIndex;
diff --git a/src/parsers/manifest/dash/node_parsers/S.ts b/src/parsers/manifest/dash/indexes/timeline/parse_s_element.ts
similarity index 95%
rename from src/parsers/manifest/dash/node_parsers/S.ts
rename to src/parsers/manifest/dash/indexes/timeline/parse_s_element.ts
index 6aa3c885fc..7e688d2c17 100644
--- a/src/parsers/manifest/dash/node_parsers/S.ts
+++ b/src/parsers/manifest/dash/indexes/timeline/parse_s_element.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import log from "../../../../log";
+import log from "../../../../../log";
/** SegmentTimeline `S` element once parsed. */
export interface IParsedS {
@@ -35,7 +35,7 @@ export interface IParsedS {
* @param {Element} root
* @returns {Object}
*/
-export default function parseS(root : Element) : IParsedS {
+export default function parseSElement(root : Element) : IParsedS {
const parsedS : IParsedS = {};
for (let j = 0; j < root.attributes.length; j++) {
diff --git a/src/parsers/manifest/dash/indexes/timeline.ts b/src/parsers/manifest/dash/indexes/timeline/timeline_representation_index.ts
similarity index 79%
rename from src/parsers/manifest/dash/indexes/timeline.ts
rename to src/parsers/manifest/dash/indexes/timeline/timeline_representation_index.ts
index 5f65760a44..dbb387b31c 100644
--- a/src/parsers/manifest/dash/indexes/timeline.ts
+++ b/src/parsers/manifest/dash/indexes/timeline/timeline_representation_index.ts
@@ -17,26 +17,29 @@
import {
ICustomError,
NetworkError,
-} from "../../../../errors";
-import log from "../../../../log";
+} from "../../../../../errors";
+import log from "../../../../../log";
import {
IRepresentationIndex,
ISegment,
-} from "../../../../manifest";
-import clearTimelineFromPosition from "../../utils/clear_timeline_from_position";
+ Representation,
+} from "../../../../../manifest";
+import clearTimelineFromPosition from "../../../utils/clear_timeline_from_position";
import {
fromIndexTime,
getIndexSegmentEnd,
IIndexSegment,
toIndexTime,
-} from "../../utils/index_helpers";
-import isSegmentStillAvailable from "../../utils/is_segment_still_available";
-import updateSegmentTimeline from "../../utils/update_segment_timeline";
-import ManifestBoundsCalculator from "../manifest_bounds_calculator";
-import { IParsedS } from "../node_parsers/S";
-import getInitSegment from "./get_init_segment";
-import getSegmentsFromTimeline from "./get_segments_from_timeline";
-import { createIndexURLs } from "./tokens";
+} from "../../../utils/index_helpers";
+import isSegmentStillAvailable from "../../../utils/is_segment_still_available";
+import updateSegmentTimeline from "../../../utils/update_segment_timeline";
+import ManifestBoundsCalculator from "../../manifest_bounds_calculator";
+// import { IParsedS } from "../../node_parsers/S";
+import getInitSegment from "../get_init_segment";
+import getSegmentsFromTimeline from "../get_segments_from_timeline";
+import { createIndexURLs } from "../tokens";
+import constructTimelineFromElements from "./construct_timeline_from_elements";
+import constructTimelineFromPreviousTimeline from "./construct_timeline_from_previous_timeline";
/**
* Index property defined for a SegmentTimeline RepresentationIndex
@@ -96,7 +99,7 @@ export interface ITimelineIndexIndexArgument {
initialization? : { media? : string; range?: [number, number] };
media? : string;
startNumber? : number;
- parseTimeline : () => IParsedS[];
+ parseTimeline : () => HTMLCollection;
timescale : number;
/**
* Offset present in the index to convert from the mediaTime (time declared in
@@ -117,6 +120,15 @@ export interface ITimelineIndexIndexArgument {
/** Aditional context needed by a SegmentTimeline RepresentationIndex. */
export interface ITimelineIndexContextArgument {
+ /**
+ * The parser should take this previous version of the
+ * `TimelineRepresentationIndex` - which was from the same Representation
+ * parsed at an earlier time - as a base to speed-up the parsing process.
+ * /!\ If unexpected differences exist between both, there is a risk of
+ * de-synchronization with what is actually on the server,
+ * Use with moderation.
+ */
+ baseOnPreviousRepresentation : Representation | null;
/** Allows to obtain the minimum and maximum positions of a content. */
manifestBoundsCalculator : ManifestBoundsCalculator;
/** Start of the period concerned by this RepresentationIndex, in seconds. */
@@ -138,56 +150,6 @@ export interface ITimelineIndexContextArgument {
representationBitrate? : number;
}
-/**
- * Translate parsed `S` node into Segment compatible with this index:
- * Find out the start, repeatCount and duration of each of these.
- *
- * @param {Object} item - parsed `S` node
- * @param {Object|null} previousItem - the previously parsed Segment (related
- * to the `S` node coming just before). If `null`, we're talking about the first
- * segment.
- * @param {Object|null} nextItem - the `S` node coming next. If `null`, we're
- * talking about the last segment.
- * @param {number} timelineStart - Absolute start for the timeline. In the same
- * timescale than the given `S` nodes.
- * @returns {Object|null}
- */
-function fromParsedSToIndexSegment(
- item : { start? : number; repeatCount? : number; duration? : number },
- previousItem : IIndexSegment|null,
- nextItem : { start? : number; repeatCount? : number; duration? : number }|null,
- timelineStart : number
-) : IIndexSegment|null {
- let start = item.start;
- let duration = item.duration;
- const repeatCount = item.repeatCount;
- if (start == null) {
- if (previousItem == null) {
- start = timelineStart;
- } else if (previousItem.duration != null) {
- start = previousItem.start +
- (previousItem.duration * (previousItem.repeatCount + 1));
- }
- }
- if ((duration == null || isNaN(duration)) &&
- nextItem != null && nextItem.start != null && !isNaN(nextItem.start) &&
- start != null && !isNaN(start)
- ) {
- duration = nextItem.start - start;
- }
- if ((start != null && !isNaN(start)) &&
- (duration != null && !isNaN(duration)) &&
- (repeatCount == null || !isNaN(repeatCount))
- ) {
- return { start,
- duration,
- repeatCount: repeatCount === undefined ? 0 :
- repeatCount };
- }
- log.warn("DASH: A \"S\" Element could not have been parsed.");
- return null;
-}
-
/**
* Get index of the segment containing the given timescaled timestamp.
* @param {Object} index
@@ -223,6 +185,16 @@ export default class TimelineRepresentationIndex implements IRepresentationIndex
/** Underlying structure to retrieve segment information. */
protected _index : ITimelineIndex;
+ /**
+ * This variable represents the same `TimelineRepresentationIndex` at the
+ * previous Manifest update.
+ * Note that it is not always set.
+ * This can be used as a base to speed-up the creation of the underlying
+ * index structure as it can be really heavy for long Manifests.
+ * To avoid taking too much memory, this variable is reset to `null` once used.
+ */
+ private _baseOnPreviousIndex : TimelineRepresentationIndex | null;
+
/** Time, in terms of `performance.now`, of the last Manifest update. */
private _lastUpdate : number;
@@ -238,8 +210,11 @@ export default class TimelineRepresentationIndex implements IRepresentationIndex
/** Retrieve the maximum and minimum position of the whole content. */
private _manifestBoundsCalculator : ManifestBoundsCalculator;
- /** Lazily parse the part of the MPD declaring segments. */
- private _parseTimeline : () => IParsedS[];
+ /**
+ * Lazily get the S elements from this timeline.
+ * `null` once this call has been done once, to free memory.
+ */
+ private _parseTimeline : (() => HTMLCollection) | null;
/**
* @param {Object} index
@@ -271,6 +246,14 @@ export default class TimelineRepresentationIndex implements IRepresentationIndex
performance.now() :
context.receivedTime;
+ this._baseOnPreviousIndex = null;
+ if (context.baseOnPreviousRepresentation !== null &&
+ context.baseOnPreviousRepresentation.index instanceof TimelineRepresentationIndex)
+ {
+ // avoid too much nested references, to keep memory down
+ context.baseOnPreviousRepresentation.index._baseOnPreviousIndex = null;
+ this._baseOnPreviousIndex = context.baseOnPreviousRepresentation.index;
+ }
this._isDynamic = isDynamic;
this._parseTimeline = index.parseTimeline;
this._index = { indexRange: index.indexRange,
@@ -313,7 +296,7 @@ export default class TimelineRepresentationIndex implements IRepresentationIndex
getSegments(from : number, duration : number) : ISegment[] {
this._refreshTimeline(); // clear timeline if needed
if (this._index.timeline === null) {
- this._index.timeline = this._constructTimeline();
+ this._index.timeline = this._getTimeline();
}
// destructuring to please TypeScript
@@ -353,7 +336,7 @@ export default class TimelineRepresentationIndex implements IRepresentationIndex
getFirstPosition() : number|null {
this._refreshTimeline();
if (this._index.timeline === null) {
- this._index.timeline = this._constructTimeline();
+ this._index.timeline = this._getTimeline();
}
const timeline = this._index.timeline;
return timeline.length === 0 ? null :
@@ -370,7 +353,7 @@ export default class TimelineRepresentationIndex implements IRepresentationIndex
getLastPosition() : number|null {
this._refreshTimeline();
if (this._index.timeline === null) {
- this._index.timeline = this._constructTimeline();
+ this._index.timeline = this._getTimeline();
}
const lastTime = TimelineRepresentationIndex.getIndexEnd(this._index.timeline,
this._scaledPeriodStart);
@@ -392,7 +375,7 @@ export default class TimelineRepresentationIndex implements IRepresentationIndex
}
this._refreshTimeline();
if (this._index.timeline === null) {
- this._index.timeline = this._constructTimeline();
+ this._index.timeline = this._getTimeline();
}
const { timeline, timescale, indexTimeOffset } = this._index;
return isSegmentStillAvailable(segment, timeline, timescale, indexTimeOffset);
@@ -410,7 +393,7 @@ export default class TimelineRepresentationIndex implements IRepresentationIndex
checkDiscontinuity(_time : number) : number {
this._refreshTimeline();
if (this._index.timeline === null) {
- this._index.timeline = this._constructTimeline();
+ this._index.timeline = this._getTimeline();
}
const { timeline, timescale } = this._index;
const scaledTime = toIndexTime(_time, this._index);
@@ -487,10 +470,10 @@ export default class TimelineRepresentationIndex implements IRepresentationIndex
*/
_update(newIndex : TimelineRepresentationIndex) : void {
if (this._index.timeline === null) {
- this._index.timeline = this._constructTimeline();
+ this._index.timeline = this._getTimeline();
}
if (newIndex._index.timeline === null) {
- newIndex._index.timeline = newIndex._constructTimeline();
+ newIndex._index.timeline = newIndex._getTimeline();
}
updateSegmentTimeline(this._index.timeline, newIndex._index.timeline);
this._isDynamic = newIndex._isDynamic;
@@ -517,7 +500,7 @@ export default class TimelineRepresentationIndex implements IRepresentationIndex
return true;
}
if (this._index.timeline === null) {
- this._index.timeline = this._constructTimeline();
+ this._index.timeline = this._getTimeline();
}
const { timeline } = this._index;
if (this._scaledPeriodEnd == null || timeline.length === 0) {
@@ -539,7 +522,7 @@ export default class TimelineRepresentationIndex implements IRepresentationIndex
*/
private _refreshTimeline() : void {
if (this._index.timeline === null) {
- this._index.timeline = this._constructTimeline();
+ this._index.timeline = this._getTimeline();
}
const firstPosition = this._manifestBoundsCalculator.getMinimumBound();
if (firstPosition == null) {
@@ -564,27 +547,41 @@ export default class TimelineRepresentationIndex implements IRepresentationIndex
* Call this function when the timeline is unknown.
* This function was added to only perform that task lazily, i.e. only when
* first needed.
+ * After calling it, every now unneeded variable will be freed from memory.
+ * This means that calling _getTimeline more than once will just return an
+ * empty array.
* @returns {Array.}
*/
- private _constructTimeline() : IIndexSegment[] {
- const initialTimeline = this._parseTimeline();
- const timeline : IIndexSegment[] = [];
- for (let i = 0; i < initialTimeline.length; i++) {
- const item = initialTimeline[i];
- const nextItem = timeline[timeline.length - 1] === undefined ?
- null :
- timeline[timeline.length - 1];
- const prevItem = initialTimeline[i + 1] === undefined ?
- null :
- initialTimeline[i + 1];
- const timelineElement = fromParsedSToIndexSegment(item,
- nextItem,
- prevItem,
- this._scaledPeriodStart);
- if (timelineElement != null) {
- timeline.push(timelineElement);
+ private _getTimeline() : IIndexSegment[] {
+ if (this._parseTimeline === null) {
+ if (this._index.timeline !== null) {
+ return this._index.timeline;
}
+ log.error("DASH: Timeline already lazily parsed.");
+ return [];
+ }
+
+ const newElements = this._parseTimeline();
+ this._parseTimeline = null; // Free memory
+
+ if (this._baseOnPreviousIndex === null) {
+ // Just completely parse the current timeline
+ return constructTimelineFromElements(newElements, this._scaledPeriodStart);
}
- return timeline;
+
+ // Construct previously parsed timeline if not already done
+ let prevTimeline : IIndexSegment[];
+ if (this._baseOnPreviousIndex._index.timeline === null) {
+ prevTimeline = this._baseOnPreviousIndex._getTimeline();
+ this._baseOnPreviousIndex._index.timeline = prevTimeline;
+ } else {
+ prevTimeline = this._baseOnPreviousIndex._index.timeline;
+ }
+ this._baseOnPreviousIndex = null; // Free memory
+
+ return constructTimelineFromPreviousTimeline(newElements,
+ prevTimeline,
+ this._scaledPeriodStart);
+
}
}
diff --git a/src/parsers/manifest/dash/node_parsers/SegmentTimeline.ts b/src/parsers/manifest/dash/node_parsers/SegmentTimeline.ts
index 842b0a3388..53f189fe6e 100644
--- a/src/parsers/manifest/dash/node_parsers/SegmentTimeline.ts
+++ b/src/parsers/manifest/dash/node_parsers/SegmentTimeline.ts
@@ -14,29 +14,19 @@
* limitations under the License.
*/
-import parseS, {
- IParsedS,
-} from "./S";
-
-export type ITimelineParser = () => IParsedS[];
+export type ITimelineParser = () => HTMLCollection;
/**
* @param {Element} root
* @returns {Function}
*/
export default function createSegmentTimelineParser(root: Element) : ITimelineParser {
- let result : IParsedS[] | null = null;
- return function() {
+ let result : HTMLCollection | null = null;
+ return function() : HTMLCollection {
if (result === null) {
- const parsedS : IParsedS[] = [];
- const timelineChildren = root.getElementsByTagName("S");
- const timelineChildrenLength = timelineChildren.length;
- for (let i = 0; i < timelineChildrenLength; i++) {
- const currentElement = timelineChildren[i];
- const s = parseS(currentElement);
- parsedS.push(s);
- }
- result = parsedS;
+ const elements = root.getElementsByTagName("S");
+ result = elements;
+ return elements;
}
return result;
};
diff --git a/src/parsers/manifest/dash/node_parsers/__tests__/SegmentTimeline.test.ts b/src/parsers/manifest/dash/node_parsers/__tests__/SegmentTimeline.test.ts
index a2f3703d70..5f78093b5f 100644
--- a/src/parsers/manifest/dash/node_parsers/__tests__/SegmentTimeline.test.ts
+++ b/src/parsers/manifest/dash/node_parsers/__tests__/SegmentTimeline.test.ts
@@ -21,70 +21,54 @@ describe("DASH Node parsers - SegmentTimeline", () => {
});
it("should return a function to parse lazily the timeline", () => {
- const parseS = jest.fn();
- jest.mock("../S", () => ({
- __esModule: true,
- default: parseS,
- }));
const parseSegmentTimeline = require("../SegmentTimeline").default;
const element = new DOMParser()
- .parseFromString("", "text/xml")
+ .parseFromString(" ", "text/xml")
.childNodes[0] as Element;
+ const getElementsByTagNameSpy = jest.spyOn(element, "getElementsByTagName");
const timeline = parseSegmentTimeline(element);
expect(typeof timeline).toEqual("function");
expect(timeline.length).toEqual(0);
+ expect(getElementsByTagNameSpy).not.toHaveBeenCalled();
+ getElementsByTagNameSpy.mockReset();
});
- it("should do nothing if no childNode is present", () => {
- const parseS = jest.fn();
- jest.mock("../S", () => ({
- __esModule: true,
- default: parseS,
- }));
+ it("should return an empty HTMLCollection if no S element is present", () => {
const parseSegmentTimeline = require("../SegmentTimeline").default;
const element = new DOMParser()
- .parseFromString("", "text/xml")
+ .parseFromString(" ", "text/xml")
.childNodes[0] as Element;
+ const getElementsByTagNameSpy = jest.spyOn(element, "getElementsByTagName");
const timeline = parseSegmentTimeline(element);
- expect(timeline()).toEqual([]);
- expect(parseS).not.toHaveBeenCalled();
+ const res = timeline();
+ expect(res).toBeInstanceOf(HTMLCollection);
+ expect(res).toHaveLength(0);
+ expect(getElementsByTagNameSpy).toHaveBeenCalledTimes(1);
+ expect(getElementsByTagNameSpy).toHaveBeenCalledWith("S");
+ getElementsByTagNameSpy.mockReset();
});
- it("should do nothing with childNodes if no S is present", () => {
- const parseS = jest.fn();
- jest.mock("../S", () => ({
- __esModule: true,
- default: parseS,
- }));
+ it("should return an empty HTMLCollection for an Invalid XML", () => {
const parseSegmentTimeline = require("../SegmentTimeline").default;
- const aElement = new DOMParser()
- .parseFromString(" ", "text/xml")
- .childNodes[0] as Element;
- const oElement = new DOMParser()
- .parseFromString(" ", "text/xml")
- .childNodes[0] as Element;
const element = new DOMParser()
- .parseFromString("", "text/xml")
+ .parseFromString(" ", "text/xml")
.childNodes[0] as Element;
-
- element.appendChild(aElement);
- element.appendChild(oElement);
+ const getElementsByTagNameSpy = jest.spyOn(element, "getElementsByTagName");
const timeline = parseSegmentTimeline(element);
- expect(timeline()).toEqual([]);
- expect(parseS).not.toHaveBeenCalled();
+ const res = timeline();
+ expect(res).toBeInstanceOf(HTMLCollection);
+ expect(res).toHaveLength(0);
+ expect(getElementsByTagNameSpy).toHaveBeenCalledTimes(1);
+ expect(getElementsByTagNameSpy).toHaveBeenCalledWith("S");
+ getElementsByTagNameSpy.mockReset();
});
it("should parse S elements only when called for the first time", () => {
- const parseS = jest.fn((s) => ({ start: +s.innerHTML }));
- jest.mock("../S", () => ({
- __esModule: true,
- default: parseS,
- }));
const parseSegmentTimeline = require("../SegmentTimeline").default;
const sElement1 = new DOMParser()
@@ -107,17 +91,24 @@ describe("DASH Node parsers - SegmentTimeline", () => {
element.appendChild(aElement);
element.appendChild(oElement);
element.appendChild(sElement2);
+ const getElementsByTagNameSpy = jest.spyOn(element, "getElementsByTagName");
const timeline = parseSegmentTimeline(element);
- expect(parseS).toHaveBeenCalledTimes(0);
- expect(timeline()).toEqual([{ start: 1 }, { start: 2 }]);
- expect(parseS).toHaveBeenCalledTimes(2);
- expect(parseS).toHaveBeenCalledWith(sElement1);
- expect(parseS).toHaveBeenCalledWith(sElement2);
- timeline();
- expect(parseS).toHaveBeenCalledTimes(2);
- timeline();
- expect(parseS).toHaveBeenCalledTimes(2);
+ const res1 = timeline();
+ expect(res1).toBeInstanceOf(HTMLCollection);
+ expect(res1).toHaveLength(2);
+ expect(getElementsByTagNameSpy).toHaveBeenCalledTimes(1);
+ expect(getElementsByTagNameSpy).toHaveBeenCalledWith("S");
+ getElementsByTagNameSpy.mockClear();
+ const res2 = timeline();
+ const res3 = timeline();
+ expect(res2).toBe(res1);
+ expect(res2).toBeInstanceOf(HTMLCollection);
+ expect(res2).toHaveLength(2);
+ expect(res3).toBe(res1);
+ expect(res3).toBeInstanceOf(HTMLCollection);
+ expect(res3).toHaveLength(2);
+ expect(getElementsByTagNameSpy).not.toHaveBeenCalled();
});
});
/* tslint:enable no-unsafe-any */
diff --git a/src/parsers/manifest/dash/parse_adaptation_sets.ts b/src/parsers/manifest/dash/parse_adaptation_sets.ts
index e666cca41d..d87b4da5cd 100644
--- a/src/parsers/manifest/dash/parse_adaptation_sets.ts
+++ b/src/parsers/manifest/dash/parse_adaptation_sets.ts
@@ -15,13 +15,15 @@
*/
import log from "../../../log";
+import {
+ Period,
+} from "../../../manifest";
import arrayFind from "../../../utils/array_find";
import arrayIncludes from "../../../utils/array_includes";
import isNonEmptyString from "../../../utils/is_non_empty_string";
import {
IParsedAdaptation,
IParsedAdaptations,
- IParsedRepresentation,
} from "../types";
import extractMinimumAvailabilityTimeOffset from "./extract_minimum_availability_time_offset";
import inferAdaptationType from "./infer_adaptation_type";
@@ -29,7 +31,9 @@ import ManifestBoundsCalculator from "./manifest_bounds_calculator";
import {
IAdaptationSetIntermediateRepresentation,
} from "./node_parsers/AdaptationSet";
-import parseRepresentations from "./parse_representations";
+import parseRepresentations, {
+ IAdaptationInfos,
+} from "./parse_representations";
import resolveBaseURLs from "./resolve_base_urls";
/** Context needed when calling `parseAdaptationSets`. */
@@ -38,6 +42,15 @@ export interface IAdaptationSetsContextInfos {
aggressiveMode : boolean;
/** availabilityTimeOffset of the concerned period. */
availabilityTimeOffset: number;
+ /**
+ * The parser should take this Period - which is from a previously parsed
+ * Manifest for the same dynamic content - as a base to speed-up the parsing
+ * process.
+ * /!\ If unexpected differences exist between both, there is a risk of
+ * de-synchronization with what is actually on the server,
+ * Use with moderation.
+ */
+ baseOnPreviousPeriod : Period | null;
/** Eventual URLs from which every relative URL will be based on. */
baseURLs : string[];
/** Allows to obtain the first available position of a content. */
@@ -104,12 +117,13 @@ function isHardOfHearing(
}
/**
- * Detect if the accessibility given defines an adaptation for the hearing impaired
+ * Detect if the accessibility given defines an AdaptationSet containing a sign
+ * language interpretation.
* Based on DASH-IF 4.3.
* @param {Object} accessibility
* @returns {Boolean}
*/
-function isHearingImpaired(
+function hasSignLanguageInterpretation(
accessibility? : { schemeIdUri? : string; value? : string }
) : boolean {
if (accessibility == null) {
@@ -129,13 +143,10 @@ function isHearingImpaired(
*/
function getAdaptationID(
adaptation : IAdaptationSetIntermediateRepresentation,
- representations : IParsedRepresentation[],
- infos : {
- isClosedCaption? : boolean;
- isAudioDescription? : boolean;
- isSignInterpreted?: boolean;
- type : string;
- }
+ infos : { isClosedCaption : boolean | undefined;
+ isAudioDescription : boolean | undefined;
+ isSignInterpreted : boolean | undefined;
+ type : string; }
) : string {
if (isNonEmptyString(adaptation.attributes.id)) {
return adaptation.attributes.id;
@@ -166,10 +177,6 @@ function getAdaptationID(
if (isNonEmptyString(adaptation.attributes.frameRate)) {
idString += `-${adaptation.attributes.frameRate}`;
}
- if (idString.length === infos.type.length) {
- idString += representations.length > 0 ?
- ("-" + representations[0].id) : "-empty";
- }
return idString;
}
@@ -209,189 +216,197 @@ export default function parseAdaptationSets(
adaptationsIR : IAdaptationSetIntermediateRepresentation[],
periodInfos : IAdaptationSetsContextInfos
): IParsedAdaptations {
- return adaptationsIR
- .reduce<{ adaptations : IParsedAdaptations;
- adaptationSwitchingInfos : IAdaptationSwitchingInfos;
- videoMainAdaptation : IParsedAdaptation|null; }>
- ((acc, adaptation) => {
- const adaptationChildren = adaptation.children;
- const parsedAdaptations = acc.adaptations;
+ const parsedAdaptations : IParsedAdaptations = {};
+ const adaptationSwitchingInfos : IAdaptationSwitchingInfos = {};
+ const parsedAdaptationsIDs : string[] = [];
+ let videoMainAdaptation : IParsedAdaptation | null = null;
- const { essentialProperties,
- roles } = adaptationChildren;
+ for (let i = 0; i < adaptationsIR.length; i++) {
+ const adaptation = adaptationsIR[i];
+ const adaptationChildren = adaptation.children;
+ const { essentialProperties,
+ roles } = adaptationChildren;
- const isExclusivelyTrickModeTrack = (Array.isArray(essentialProperties) &&
- essentialProperties.some((ep) =>
- ep.schemeIdUri === "http://dashif.org/guidelines/trickmode"));
+ const isExclusivelyTrickModeTrack = (Array.isArray(essentialProperties) &&
+ essentialProperties.some((ep) =>
+ ep.schemeIdUri === "http://dashif.org/guidelines/trickmode"));
- if (isExclusivelyTrickModeTrack) {
- // We do not for the moment parse trickmode tracks
- return acc;
- }
+ if (isExclusivelyTrickModeTrack) {
+ // We do not for the moment parse trickmode tracks
+ continue;
+ }
- const isMainAdaptation = Array.isArray(roles) &&
- roles.some((role) => role.value === "main") &&
- roles.some((role) => role.schemeIdUri === "urn:mpeg:dash:role:2011");
+ const isMainAdaptation = Array.isArray(roles) &&
+ roles.some((role) => role.value === "main") &&
+ roles.some((role) => role.schemeIdUri === "urn:mpeg:dash:role:2011");
- const representationsIR = adaptation.children.representations;
- const availabilityTimeOffset =
- extractMinimumAvailabilityTimeOffset(adaptation.children.baseURLs) +
- periodInfos.availabilityTimeOffset;
+ const representationsIR = adaptation.children.representations;
+ const availabilityTimeOffset =
+ extractMinimumAvailabilityTimeOffset(adaptation.children.baseURLs) +
+ periodInfos.availabilityTimeOffset;
- const adaptationInfos = {
- aggressiveMode: periodInfos.aggressiveMode,
- availabilityTimeOffset,
- baseURLs: resolveBaseURLs(periodInfos.baseURLs, adaptationChildren.baseURLs),
- manifestBoundsCalculator: periodInfos.manifestBoundsCalculator,
- end: periodInfos.end,
- isDynamic: periodInfos.isDynamic,
- receivedTime: periodInfos.receivedTime,
- start: periodInfos.start,
- timeShiftBufferDepth: periodInfos.timeShiftBufferDepth,
- };
- const adaptationMimeType = adaptation.attributes.mimeType;
- const adaptationCodecs = adaptation.attributes.codecs;
- const type = inferAdaptationType(representationsIR,
- isNonEmptyString(adaptationMimeType) ?
- adaptationMimeType :
- null,
- isNonEmptyString(adaptationCodecs) ?
- adaptationCodecs :
- null,
- adaptationChildren.roles != null ?
- adaptationChildren.roles :
- null);
- if (type === undefined) {
- return acc; // let's not parse unrecognized tracks
- }
+ const adaptationMimeType = adaptation.attributes.mimeType;
+ const adaptationCodecs = adaptation.attributes.codecs;
+ const type = inferAdaptationType(representationsIR,
+ isNonEmptyString(adaptationMimeType) ?
+ adaptationMimeType :
+ null,
+ isNonEmptyString(adaptationCodecs) ?
+ adaptationCodecs :
+ null,
+ adaptationChildren.roles != null ?
+ adaptationChildren.roles :
+ null);
+ if (type === undefined) {
+ continue;
+ }
+
+ const originalID = adaptation.attributes.id;
+ let newID : string;
+ const adaptationSetSwitchingIDs = getAdaptationSetSwitchingIDs(adaptation);
+ const adaptationInfos : IAdaptationInfos = {
+ aggressiveMode: periodInfos.aggressiveMode,
+ availabilityTimeOffset,
+ baseOnPreviousAdaptation: null,
+ baseURLs: resolveBaseURLs(periodInfos.baseURLs, adaptationChildren.baseURLs),
+ manifestBoundsCalculator: periodInfos.manifestBoundsCalculator,
+ end: periodInfos.end,
+ isDynamic: periodInfos.isDynamic,
+ receivedTime: periodInfos.receivedTime,
+ start: periodInfos.start,
+ timeShiftBufferDepth: periodInfos.timeShiftBufferDepth,
+ };
+ if (type === "video" && videoMainAdaptation !== null && isMainAdaptation) {
+ adaptationInfos.baseOnPreviousAdaptation =
+ periodInfos.baseOnPreviousPeriod?.getAdaptation(videoMainAdaptation.id) ?? null;
const representations = parseRepresentations(representationsIR,
adaptation,
adaptationInfos);
+ videoMainAdaptation.representations.push(...representations);
+ newID = videoMainAdaptation.id;
+ } else {
+ const { accessibility } = adaptationChildren;
- const originalID = adaptation.attributes.id;
- let newID : string;
- const adaptationSetSwitchingIDs = getAdaptationSetSwitchingIDs(adaptation);
- const videoMainAdaptation = acc.videoMainAdaptation;
- if (type === "video" && videoMainAdaptation !== null && isMainAdaptation) {
- videoMainAdaptation.representations.push(...representations);
- newID = videoMainAdaptation.id;
- } else {
- const { accessibility } = adaptationChildren;
+ let isDub : boolean|undefined;
+ if (roles !== undefined &&
+ roles.some((role) => role.value === "dub"))
+ {
+ isDub = true;
+ }
- let isDub : boolean|undefined;
- if (roles !== undefined &&
- roles.some((role) => role.value === "dub"))
- {
- isDub = true;
- }
+ const isClosedCaption = type === "text" &&
+ accessibility != null &&
+ isHardOfHearing(accessibility) ? true :
+ undefined;
+ const isAudioDescription = type === "audio" &&
+ accessibility != null &&
+ isVisuallyImpaired(accessibility) ? true :
+ undefined;
- const isClosedCaption = type === "text" &&
+ const isSignInterpreted = type === "video" &&
accessibility != null &&
- isHardOfHearing(accessibility) ? true :
- undefined;
- const isAudioDescription = type === "audio" &&
- accessibility != null &&
- isVisuallyImpaired(accessibility) ? true :
- undefined;
+ hasSignLanguageInterpretation(accessibility) ? true :
+ undefined;
- const isSignInterpreted = type === "video" &&
- accessibility != null &&
- isHearingImpaired(accessibility) ? true :
- undefined;
+ let adaptationID = getAdaptationID(adaptation,
+ { isAudioDescription,
+ isClosedCaption,
+ isSignInterpreted,
+ type });
- const adaptationID = newID = getAdaptationID(adaptation,
- representations,
- { isClosedCaption,
- isAudioDescription,
- isSignInterpreted,
- type });
- const parsedAdaptationSet : IParsedAdaptation = { id: adaptationID,
- representations,
- type };
- if (adaptation.attributes.language != null) {
- parsedAdaptationSet.language = adaptation.attributes.language;
- }
- if (isClosedCaption != null) {
- parsedAdaptationSet.closedCaption = isClosedCaption;
- }
- if (isAudioDescription != null) {
- parsedAdaptationSet.audioDescription = isAudioDescription;
- }
- if (isDub === true) {
- parsedAdaptationSet.isDub = true;
- }
+ // Avoid duplicate IDs
+ while (arrayIncludes(parsedAdaptationsIDs, adaptationID)) {
+ adaptationID += "-dup";
+ }
- if (isSignInterpreted === true) {
- parsedAdaptationSet.isSignInterpreted = true;
- }
+ newID = adaptationID;
+ parsedAdaptationsIDs.push(adaptationID);
- const adaptationsOfTheSameType = parsedAdaptations[type];
- if (adaptationsOfTheSameType === undefined) {
- parsedAdaptations[type] = [parsedAdaptationSet];
- if (isMainAdaptation && type === "video") {
- acc.videoMainAdaptation = parsedAdaptationSet;
- }
- } else {
- let mergedInto : IParsedAdaptation|null = null;
+ adaptationInfos.baseOnPreviousAdaptation =
+ periodInfos.baseOnPreviousPeriod?.getAdaptation(adaptationID) ?? null;
+ const representations = parseRepresentations(representationsIR,
+ adaptation,
+ adaptationInfos);
+ const parsedAdaptationSet : IParsedAdaptation = { id: adaptationID,
+ representations,
+ type };
+ if (adaptation.attributes.language != null) {
+ parsedAdaptationSet.language = adaptation.attributes.language;
+ }
+ if (isClosedCaption != null) {
+ parsedAdaptationSet.closedCaption = isClosedCaption;
+ }
+ if (isAudioDescription != null) {
+ parsedAdaptationSet.audioDescription = isAudioDescription;
+ }
+ if (isDub === true) {
+ parsedAdaptationSet.isDub = true;
+ }
+ if (isSignInterpreted === true) {
+ parsedAdaptationSet.isSignInterpreted = true;
+ }
- // look if we have to merge this into another Adaptation
- for (let k = 0; k < adaptationSetSwitchingIDs.length; k++) {
- const id : string = adaptationSetSwitchingIDs[k];
- const switchingInfos = acc.adaptationSwitchingInfos[id];
- if (switchingInfos != null &&
- switchingInfos.newID !== newID &&
- arrayIncludes(switchingInfos.adaptationSetSwitchingIDs, originalID))
+ const adaptationsOfTheSameType = parsedAdaptations[type];
+ if (adaptationsOfTheSameType === undefined) {
+ parsedAdaptations[type] = [parsedAdaptationSet];
+ if (isMainAdaptation && type === "video") {
+ videoMainAdaptation = parsedAdaptationSet;
+ }
+ } else {
+ let mergedInto : IParsedAdaptation|null = null;
+
+ // look if we have to merge this into another Adaptation
+ for (let k = 0; k < adaptationSetSwitchingIDs.length; k++) {
+ const id : string = adaptationSetSwitchingIDs[k];
+ const switchingInfos = adaptationSwitchingInfos[id];
+ if (switchingInfos != null &&
+ switchingInfos.newID !== newID &&
+ arrayIncludes(switchingInfos.adaptationSetSwitchingIDs, originalID))
+ {
+ const adaptationToMergeInto = arrayFind(adaptationsOfTheSameType,
+ (a) => a.id === id);
+ if (adaptationToMergeInto != null &&
+ adaptationToMergeInto.audioDescription ===
+ parsedAdaptationSet.audioDescription &&
+ adaptationToMergeInto.closedCaption ===
+ parsedAdaptationSet.closedCaption &&
+ adaptationToMergeInto.language === parsedAdaptationSet.language)
{
- const adaptationToMergeInto = arrayFind(adaptationsOfTheSameType,
- (a) => a.id === id);
- if (adaptationToMergeInto != null &&
- adaptationToMergeInto.audioDescription ===
- parsedAdaptationSet.audioDescription &&
- adaptationToMergeInto.closedCaption ===
- parsedAdaptationSet.closedCaption &&
- adaptationToMergeInto.language === parsedAdaptationSet.language)
- {
- log.info("DASH Parser: merging \"switchable\" AdaptationSets",
- originalID, id);
- adaptationToMergeInto.representations
- .push(...parsedAdaptationSet.representations);
- mergedInto = adaptationToMergeInto;
- }
+ log.info("DASH Parser: merging \"switchable\" AdaptationSets",
+ originalID, id);
+ adaptationToMergeInto.representations
+ .push(...parsedAdaptationSet.representations);
+ mergedInto = adaptationToMergeInto;
}
}
+ }
- if (isMainAdaptation && type === "video") {
- if (mergedInto == null) {
- // put "main" adaptation as the first
+ if (isMainAdaptation && type === "video") {
+ if (mergedInto == null) {
+ // put "main" adaptation as the first
+ adaptationsOfTheSameType.unshift(parsedAdaptationSet);
+ videoMainAdaptation = parsedAdaptationSet;
+ } else {
+ // put the resulting adaptation first instead
+ const indexOf = adaptationsOfTheSameType.indexOf(mergedInto);
+ if (indexOf < 0) {
adaptationsOfTheSameType.unshift(parsedAdaptationSet);
- acc.videoMainAdaptation = parsedAdaptationSet;
- } else {
- // put the resulting adaptation first instead
- const indexOf = adaptationsOfTheSameType.indexOf(mergedInto);
- if (indexOf < 0) {
- adaptationsOfTheSameType.unshift(parsedAdaptationSet);
- } else if (indexOf !== 0) {
- adaptationsOfTheSameType.splice(indexOf, 1);
- adaptationsOfTheSameType.unshift(mergedInto);
- }
- acc.videoMainAdaptation = mergedInto;
+ } else if (indexOf !== 0) {
+ adaptationsOfTheSameType.splice(indexOf, 1);
+ adaptationsOfTheSameType.unshift(mergedInto);
}
- } else if (mergedInto === null) {
- adaptationsOfTheSameType.push(parsedAdaptationSet);
+ videoMainAdaptation = mergedInto;
}
+ } else if (mergedInto === null) {
+ adaptationsOfTheSameType.push(parsedAdaptationSet);
}
}
+ }
- if (originalID != null && acc.adaptationSwitchingInfos[originalID] == null) {
- acc.adaptationSwitchingInfos[originalID] = { newID,
- adaptationSetSwitchingIDs };
- }
-
- return { adaptations: parsedAdaptations,
- adaptationSwitchingInfos: acc.adaptationSwitchingInfos,
- videoMainAdaptation: acc.videoMainAdaptation };
- }, { adaptations: {},
- videoMainAdaptation: null,
- adaptationSwitchingInfos: {} }
- ).adaptations;
+ if (originalID != null && adaptationSwitchingInfos[originalID] == null) {
+ adaptationSwitchingInfos[originalID] = { newID,
+ adaptationSetSwitchingIDs };
+ }
+ }
+ return parsedAdaptations;
}
diff --git a/src/parsers/manifest/dash/parse_mpd.ts b/src/parsers/manifest/dash/parse_mpd.ts
index 879fd57c52..84632cac35 100644
--- a/src/parsers/manifest/dash/parse_mpd.ts
+++ b/src/parsers/manifest/dash/parse_mpd.ts
@@ -15,10 +15,10 @@
*/
import config from "../../../config";
+import Manifest from "../../../manifest";
import arrayFind from "../../../utils/array_find";
import { normalizeBaseURL } from "../../../utils/resolve_url";
import { IParsedManifest } from "../types";
-import checkManifestIDs from "../utils/check_manifest_ids";
import extractMinimumAvailabilityTimeOffset from "./extract_minimum_availability_time_offset";
import getClockOffset from "./get_clock_offset";
import getHTTPUTCTimingURL from "./get_http_utc-timing_url";
@@ -43,6 +43,15 @@ const { DASH_FALLBACK_LIFETIME_WHEN_MINIMUM_UPDATE_PERIOD_EQUAL_0 } = config;
export interface IMPDParserArguments {
/** Whether we should request new segments even if they are not yet finished. */
aggressiveMode : boolean;
+ /**
+ * The parser should take this Manifest - which is a previously parsed
+ * Manifest for the same dynamic content - as a base to speed-up the parsing
+ * process.
+ * /!\ If unexpected differences exist between the two, there is a risk of
+ * de-synchronization with what is actually on the server,
+ * Use with moderation.
+ */
+ baseOnPreviousManifest : Manifest | null;
/**
* If set, offset to add to `performance.now()` to obtain the current server's
* time.
@@ -220,6 +229,7 @@ function parseCompleteIntermediateRepresentation(
availabilityStartTime,
availabilityTimeOffset,
baseURLs,
+ baseOnPreviousManifest: args.baseOnPreviousManifest,
clockOffset,
duration: rootAttributes.duration,
isDynamic,
@@ -249,7 +259,6 @@ function parseCompleteIntermediateRepresentation(
rootAttributes.minimumUpdatePeriod;
}
- checkManifestIDs(parsedMPD);
const [minTime, maxTime] = getMinimumAndMaximumPosition(parsedMPD);
const now = performance.now();
if (!isDynamic) {
diff --git a/src/parsers/manifest/dash/parse_periods.ts b/src/parsers/manifest/dash/parse_periods.ts
index f778b82b43..f6902f4a23 100644
--- a/src/parsers/manifest/dash/parse_periods.ts
+++ b/src/parsers/manifest/dash/parse_periods.ts
@@ -15,6 +15,7 @@
*/
import log from "../../../log";
+import Manifest from "../../../manifest";
import flatMap from "../../../utils/flat_map";
import idGenerator from "../../../utils/id_generator";
import objectValues from "../../../utils/object_values";
@@ -49,6 +50,15 @@ export interface IPeriodsContextInfos {
aggressiveMode : boolean;
availabilityTimeOffset: number;
availabilityStartTime : number;
+ /**
+ * The parser should take this Manifest - which is a previously parsed
+ * Manifest for the same dynamic content - as a base to speed-up the parsing
+ * process.
+ * /!\ If unexpected differences exist between the two, there is a risk of
+ * de-synchronization with what is actually on the server,
+ * Use with moderation.
+ */
+ baseOnPreviousManifest : Manifest | null;
baseURLs : string[];
clockOffset? : number;
duration? : number;
@@ -108,6 +118,11 @@ export interface IPeriodsContextInfos {
periodID = periodIR.attributes.id;
}
+ // Avoid duplicate IDs
+ while (parsedPeriods.some(p => p.id === periodID)) {
+ periodID += "-dup";
+ }
+
const receivedTime = xlinkInfos !== undefined ? xlinkInfos.receivedTime :
contextInfos.receivedTime;
@@ -115,8 +130,12 @@ export interface IPeriodsContextInfos {
extractMinimumAvailabilityTimeOffset(periodIR.children.baseURLs) +
contextInfos.availabilityTimeOffset;
+ const baseOnPreviousPeriod =
+ contextInfos.baseOnPreviousManifest?.getPeriod(periodID) ?? null;
+
const periodInfos = { aggressiveMode: contextInfos.aggressiveMode,
availabilityTimeOffset,
+ baseOnPreviousPeriod,
baseURLs: periodBaseURLs,
manifestBoundsCalculator,
end: periodEnd,
diff --git a/src/parsers/manifest/dash/parse_representations.ts b/src/parsers/manifest/dash/parse_representations.ts
index dd35fa5f5f..93af817606 100644
--- a/src/parsers/manifest/dash/parse_representations.ts
+++ b/src/parsers/manifest/dash/parse_representations.ts
@@ -15,16 +15,22 @@
*/
import log from "../../../log";
+import {
+ Adaptation,
+ Representation,
+} from "../../../manifest";
import IRepresentationIndex from "../../../manifest/representation_index";
import {
IContentProtections,
IParsedRepresentation,
} from "../types";
import extractMinimumAvailabilityTimeOffset from "./extract_minimum_availability_time_offset";
-import BaseRepresentationIndex from "./indexes/base";
-import ListRepresentationIndex from "./indexes/list";
-import TemplateRepresentationIndex from "./indexes/template";
-import TimelineRepresentationIndex from "./indexes/timeline";
+import {
+ BaseRepresentationIndex,
+ ListRepresentationIndex,
+ TemplateRepresentationIndex,
+ TimelineRepresentationIndex
+} from "./indexes";
import ManifestBoundsCalculator from "./manifest_bounds_calculator";
import {
IAdaptationSetIntermediateRepresentation
@@ -40,6 +46,15 @@ export interface IAdaptationInfos {
aggressiveMode : boolean;
/** availability time offset of the concerned Adaptation. */
availabilityTimeOffset: number;
+ /**
+ * The parser should take this Adaptation - which is from a previously parsed
+ * Manifest for the same dynamic content - as a base to speed-up the parsing
+ * process.
+ * /!\ If unexpected differences exist between both, there is a risk of
+ * de-synchronization with what is actually on the server,
+ * Use with moderation.
+ */
+ baseOnPreviousAdaptation : Adaptation | null;
/** Eventual URLs from which every relative URL will be based on. */
baseURLs : string[];
/** Allows to obtain the first available position of a dynamic content. */
@@ -64,6 +79,13 @@ interface IIndexContext {
/** Whether we should request new segments even if they are not yet finished. */
aggressiveMode : boolean;
availabilityTimeOffset: number;
+ /**
+ * The parser should take this Representation - which is the same as this one
+ * parsed at an earlier time - as a base to speed-up the parsing process.
+ * /!\ If unexpected differences exist between both, there is a risk of
+ * de-synchronization with what is actually on the server.
+ */
+ baseOnPreviousRepresentation: Representation | null;
/** Allows to obtain the first available position of a dynamic content. */
manifestBoundsCalculator : ManifestBoundsCalculator;
/** Whether the Manifest can evolve with time. */
@@ -128,13 +150,46 @@ export default function parseRepresentations(
adaptation : IAdaptationSetIntermediateRepresentation,
adaptationInfos : IAdaptationInfos
): IParsedRepresentation[] {
- return representationsIR.map((representation) => {
+ const parsedRepresentations : IParsedRepresentation[] = [];
+ for (let representationIdx = 0;
+ representationIdx < representationsIR.length;
+ representationIdx++)
+ {
+ const representation = representationsIR[representationIdx];
const representationBaseURLs = resolveBaseURLs(adaptationInfos.baseURLs,
representation.children.baseURLs);
- // 4-2-1. Find Index
+ // 1. Get ID
+ let representationID = representation.attributes.id != null ?
+ representation.attributes.id :
+ (String(representation.attributes.bitrate) +
+ (representation.attributes.height != null ?
+ (`-${representation.attributes.height}`) :
+ "") +
+ (representation.attributes.width != null ?
+ (`-${representation.attributes.width}`) :
+ "") +
+ (representation.attributes.mimeType != null ?
+ (`-${representation.attributes.mimeType}`) :
+ "") +
+ (representation.attributes.codecs != null ?
+ (`-${representation.attributes.codecs}`) :
+ ""));
+
+ // Avoid duplicate IDs
+ while (parsedRepresentations.some(r => r.id === representationID)) {
+ representationID += "-dup";
+ }
+
+ // 2. Retrieve previous version of the Representation, if one.
+ const baseOnPreviousRepresentation =
+ adaptationInfos.baseOnPreviousAdaptation?.getRepresentation(representationID) ??
+ null;
+
+ // 3. Find Index
const context = { aggressiveMode: adaptationInfos.aggressiveMode,
availabilityTimeOffset: adaptationInfos.availabilityTimeOffset,
+ baseOnPreviousRepresentation,
manifestBoundsCalculator: adaptationInfos.manifestBoundsCalculator,
isDynamic: adaptationInfos.isDynamic,
periodEnd: adaptationInfos.end,
@@ -170,7 +225,7 @@ export default function parseRepresentations(
representationIndex = findAdaptationIndex(adaptation, context);
}
- // 4-2-2. Find bitrate
+ // 3. Find bitrate
let representationBitrate : number;
if (representation.attributes.bitrate == null) {
log.warn("DASH: No usable bitrate found in the Representation.");
@@ -179,28 +234,13 @@ export default function parseRepresentations(
representationBitrate = representation.attributes.bitrate;
}
- // 4-2-3. Set ID
- const representationID = representation.attributes.id != null ?
- representation.attributes.id :
- (String(representation.attributes.bitrate) +
- (representation.attributes.height != null ?
- (`-${representation.attributes.height}`) :
- "") +
- (representation.attributes.width != null ?
- (`-${representation.attributes.width}`) :
- "") +
- (representation.attributes.mimeType != null ?
- (`-${representation.attributes.mimeType}`) :
- "") +
- (representation.attributes.codecs != null ?
- (`-${representation.attributes.codecs}`) :
- ""));
- // 4-2-4. Construct Representation Base
+ // 4. Construct Representation Base
const parsedRepresentation : IParsedRepresentation =
{ bitrate: representationBitrate,
index: representationIndex,
id: representationID };
- // 4-2-5. Add optional attributes
+
+ // 5. Add optional attributes
let codecs : string|undefined;
if (representation.attributes.codecs != null) {
codecs = representation.attributes.codecs;
@@ -273,6 +313,7 @@ export default function parseRepresentations(
}
}
- return parsedRepresentation;
- });
+ parsedRepresentations.push(parsedRepresentation);
+ }
+ return parsedRepresentations;
}
diff --git a/src/transports/dash/manifest_parser.ts b/src/transports/dash/manifest_parser.ts
index 8b88e45918..990c15e3e4 100644
--- a/src/transports/dash/manifest_parser.ts
+++ b/src/transports/dash/manifest_parser.ts
@@ -79,8 +79,11 @@ export default function generateManifestParser(
response.responseData as Document;
const externalClockOffset = serverTimeOffset ?? argClockOffset;
+ const baseOnPreviousManifest = args.unsafeMode ? args.previousManifest :
+ null;
const parsedManifest = dashManifestParser(data, { aggressiveMode:
aggressiveMode === true,
+ baseOnPreviousManifest,
url,
referenceDateTime,
externalClockOffset });
From 73a4271e1a92226e99c9909ce2d06da9820f1663 Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Fri, 3 Apr 2020 17:42:47 +0200
Subject: [PATCH 38/72] init: do not parse in unsafe mode when the Manifest
might be out-of-sync
---
src/core/init/initialize_media_source.ts | 4 +++-
src/core/init/manifest_update_scheduler.ts | 22 +++++++++++++++-------
2 files changed, 18 insertions(+), 8 deletions(-)
diff --git a/src/core/init/initialize_media_source.ts b/src/core/init/initialize_media_source.ts
index 9062c5f4d4..0911f6bd2d 100644
--- a/src/core/init/initialize_media_source.ts
+++ b/src/core/init/initialize_media_source.ts
@@ -328,11 +328,13 @@ export default function InitializeOnMediaSource(
.pipe(tap(evt => {
switch (evt.type) {
case "needs-manifest-refresh":
- scheduleRefresh$.next({ completeRefresh: false });
+ scheduleRefresh$.next({ completeRefresh: false,
+ canUseUnsafeMode: true });
break;
case "manifest-might-be-out-of-sync":
scheduleRefresh$.next({
completeRefresh: true,
+ canUseUnsafeMode: false,
delay: OUT_OF_SYNC_MANIFEST_REFRESH_DELAY,
});
break;
diff --git a/src/core/init/manifest_update_scheduler.ts b/src/core/init/manifest_update_scheduler.ts
index cb08565a53..b8474e8461 100644
--- a/src/core/init/manifest_update_scheduler.ts
+++ b/src/core/init/manifest_update_scheduler.ts
@@ -77,6 +77,12 @@ export interface IManifestRefreshSchedulerEvent {
* before updating the Manifest
*/
delay? : number;
+ /**
+ * Whether the parsing can be done in the more efficient "unsafeMode".
+ * This mode is extremely fast but can lead to de-synchronisation with the
+ * server.
+ */
+ canUseUnsafeMode : boolean;
}
/** Observable to send events related to refresh requests coming from the Player. */
@@ -112,16 +118,17 @@ export default function manifestUpdateScheduler({
// Only perform parsing in `unsafeMode` when the last full parsing took too
// much time and do not go higher than the maximum consecutive time.
- const unsafeMode = consecutiveUnsafeMode > 0 ?
+ const unsafeModeEnabled = consecutiveUnsafeMode > 0 ?
consecutiveUnsafeMode < MAX_CONSECUTIVE_MANIFEST_PARSING_IN_UNSAFE_MODE :
totalUpdateTime >= 100;
const internalRefresh$ = scheduleRefresh$
- .pipe(mergeMap(({ completeRefresh, delay }) => {
+ .pipe(mergeMap(({ completeRefresh, delay, canUseUnsafeMode }) => {
+ const unsafeMode = canUseUnsafeMode && unsafeModeEnabled;
return startManualRefreshTimer(delay ?? 0,
minimumManifestUpdateInterval,
sendingTime)
- .pipe(mapTo({ completeRefresh }));
+ .pipe(mapTo({ completeRefresh, unsafeMode }));
}));
const timeSinceRequest = sendingTime == null ? 0 :
@@ -149,22 +156,23 @@ export default function manifestUpdateScheduler({
autoRefreshInterval = newInterval;
}
autoRefresh$ = observableTimer(Math.max(autoRefreshInterval, minInterval))
- .pipe(mapTo({ completeRefresh: false }));
+ .pipe(mapTo({ completeRefresh: false, unsafeMode: unsafeModeEnabled }));
}
const expired$ = manifest.expired === null ?
EMPTY :
observableTimer(minInterval)
.pipe(mergeMapTo(observableFrom(manifest.expired)),
- mapTo({ completeRefresh: true }));
+ mapTo({ completeRefresh: true, unsafeMode: unsafeModeEnabled }));
// Emit when the manifest should be refreshed. Either when:
// - A buffer asks for it to be refreshed
// - its lifetime expired.
return observableMerge(autoRefresh$, internalRefresh$, expired$).pipe(
take(1),
- mergeMap(({ completeRefresh }) => refreshManifest({ completeRefresh,
- unsafeMode })),
+ mergeMap(({ completeRefresh,
+ unsafeMode }) => refreshManifest({ completeRefresh,
+ unsafeMode })),
mergeMap(evt => {
if (evt.type === "warning") {
return observableOf(evt);
From c2e1c2af4c17804f6541f0c9e31526b44817ea7c Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Mon, 20 Apr 2020 10:36:07 +0200
Subject: [PATCH 39/72] buffers/api: use the new "initializied" source buffer
API
---
src/core/api/public_api.ts | 2 +-
src/core/buffers/orchestrator/buffer_orchestrator.ts | 2 +-
src/core/buffers/period/period_buffer.ts | 6 +++---
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/core/api/public_api.ts b/src/core/api/public_api.ts
index 8cb8866d30..85ca206061 100644
--- a/src/core/api/public_api.ts
+++ b/src/core/api/public_api.ts
@@ -1919,7 +1919,7 @@ class Player extends EventEmitter {
}
const sourceBufferStatus = this._priv_contentInfos
.sourceBuffersStore.getStatus(bufferType);
- return sourceBufferStatus.type === "set" ?
+ return sourceBufferStatus.type === "initialized" ?
sourceBufferStatus.value.getInventory() :
null;
}
diff --git a/src/core/buffers/orchestrator/buffer_orchestrator.ts b/src/core/buffers/orchestrator/buffer_orchestrator.ts
index 88dc6f9633..06596f881e 100644
--- a/src/core/buffers/orchestrator/buffer_orchestrator.ts
+++ b/src/core/buffers/orchestrator/buffer_orchestrator.ts
@@ -282,7 +282,7 @@ export default function BufferOrchestrator(
.pipe(mergeMap((updates) => {
const sourceBufferStatus = sourceBuffersStore.getStatus(bufferType);
const hasType = updates.some(update => update.adaptation.type === bufferType);
- if (!hasType || sourceBufferStatus.type !== "set") {
+ if (!hasType || sourceBufferStatus.type !== "initialized") {
return EMPTY; // no need to stop the current buffers
}
const queuedSourceBuffer = sourceBufferStatus.value;
diff --git a/src/core/buffers/period/period_buffer.ts b/src/core/buffers/period/period_buffer.ts
index 04139d194b..3bd3b301b7 100644
--- a/src/core/buffers/period/period_buffer.ts
+++ b/src/core/buffers/period/period_buffer.ts
@@ -119,7 +119,7 @@ export default function PeriodBuffer({
const sourceBufferStatus = sourceBuffersStore.getStatus(bufferType);
let cleanBuffer$ : Observable;
- if (sourceBufferStatus.type === "set") {
+ if (sourceBufferStatus.type === "initialized") {
log.info(`Buffer: Clearing previous ${bufferType} SourceBuffer`);
if (SourceBuffersStore.isNative(bufferType)) {
return clock$.pipe(map(tick => EVENTS.needsMediaSourceReload(tick)));
@@ -129,7 +129,7 @@ export default function PeriodBuffer({
period.end == null ? Infinity :
period.end);
} else {
- if (sourceBufferStatus.type === "unset") {
+ if (sourceBufferStatus.type === "uninitialized") {
sourceBuffersStore.disableSourceBuffer(bufferType);
}
cleanBuffer$ = observableOf(null);
@@ -247,7 +247,7 @@ function createOrReuseQueuedSourceBuffer(
options: { textTrackOptions? : ITextTrackSourceBufferOptions }
) : QueuedSourceBuffer {
const sourceBufferStatus = sourceBuffersStore.getStatus(bufferType);
- if (sourceBufferStatus.type === "set") {
+ if (sourceBufferStatus.type === "initialized") {
log.info("Buffer: Reusing a previous SourceBuffer for the type", bufferType);
return sourceBufferStatus.value;
}
From 70e7a1baa7b12a6b6585d484c935a4827e206364 Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Mon, 6 Apr 2020 17:38:15 +0200
Subject: [PATCH 40/72] Init: add a not about the parsing time in the
`manifestUpdateScheduler`
---
src/core/init/manifest_update_scheduler.ts | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/src/core/init/manifest_update_scheduler.ts b/src/core/init/manifest_update_scheduler.ts
index b8474e8461..547f042fa2 100644
--- a/src/core/init/manifest_update_scheduler.ts
+++ b/src/core/init/manifest_update_scheduler.ts
@@ -114,10 +114,15 @@ export default function manifestUpdateScheduler({
const { sendingTime,
parsingTime,
updatingTime } = manifestInfos;
+
+ /**
+ * Total time taken to fully update the last Manifest.
+ * Note: this time also includes possible requests done by the parsers.
+ */
const totalUpdateTime = parsingTime + (updatingTime ?? 0);
- // Only perform parsing in `unsafeMode` when the last full parsing took too
- // much time and do not go higher than the maximum consecutive time.
+ // Only perform parsing in `unsafeMode` when the last full parsing took a
+ // lot of time and do not go higher than the maximum consecutive time.
const unsafeModeEnabled = consecutiveUnsafeMode > 0 ?
consecutiveUnsafeMode < MAX_CONSECUTIVE_MANIFEST_PARSING_IN_UNSAFE_MODE :
totalUpdateTime >= 100;
From 6a84306146fbf931c3c169691223de613881ce16 Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Mon, 20 Apr 2020 10:39:25 +0200
Subject: [PATCH 41/72] api: for preferred audio tracks in directfile, ignore
preferences where the language is not set
---
src/core/api/media_element_track_choice_manager.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/core/api/media_element_track_choice_manager.ts b/src/core/api/media_element_track_choice_manager.ts
index dac738e5b1..c6175f2c48 100644
--- a/src/core/api/media_element_track_choice_manager.ts
+++ b/src/core/api/media_element_track_choice_manager.ts
@@ -502,7 +502,7 @@ export default class MediaElementTrackChoiceManager
const preferredAudioTracks = this._preferredAudioTracks.getValue();
for (let i = 0; i < preferredAudioTracks.length; i++) {
const track = preferredAudioTracks[i];
- if (track !== null) {
+ if (track !== null && track.language !== undefined) {
const normalized = normalizeLanguage(track.language);
for (let j = 0; j < this._audioTracks.length; j++) {
const audioTrack = this._audioTracks[j];
From b77a9963f4d035c570146950b8cf849a3a1faeba Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Mon, 6 Apr 2020 17:41:34 +0200
Subject: [PATCH 42/72] init/config: add
MIN_MANIFEST_PARSING_TIME_TO_ENTER_UNSAFE_MODE config variable
---
src/config.ts | 8 ++++++++
src/core/init/manifest_update_scheduler.ts | 5 +++--
2 files changed, 11 insertions(+), 2 deletions(-)
diff --git a/src/config.ts b/src/config.ts
index fa5aaf997b..e77eb948ce 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -761,6 +761,14 @@ export default {
*/
MAX_CONSECUTIVE_MANIFEST_PARSING_IN_UNSAFE_MODE: 10,
+ /**
+ * Minimum time spent parsing the Manifest before we can authorize parsing
+ * it in an "unsafeMode", to speed-up the process with a little risk.
+ * Please note that this parsing time also sometimes includes idle time such
+ * as when the parser is waiting for a request to finish.
+ */
+ MIN_MANIFEST_PARSING_TIME_TO_ENTER_UNSAFE_MODE: 150,
+
/**
* When we detect that the local Manifest might be out-of-sync with the
* server's one, we schedule a Manifest refresh.
diff --git a/src/core/init/manifest_update_scheduler.ts b/src/core/init/manifest_update_scheduler.ts
index 547f042fa2..19031ac1b5 100644
--- a/src/core/init/manifest_update_scheduler.ts
+++ b/src/core/init/manifest_update_scheduler.ts
@@ -40,7 +40,8 @@ import {
import { IWarningEvent } from "./types";
const { FAILED_PARTIAL_UPDATE_MANIFEST_REFRESH_DELAY,
- MAX_CONSECUTIVE_MANIFEST_PARSING_IN_UNSAFE_MODE } = config;
+ MAX_CONSECUTIVE_MANIFEST_PARSING_IN_UNSAFE_MODE,
+ MIN_MANIFEST_PARSING_TIME_TO_ENTER_UNSAFE_MODE } = config;
/** Arguments to give to the `manifestUpdateScheduler` */
export interface IManifestUpdateSchedulerArguments {
@@ -125,7 +126,7 @@ export default function manifestUpdateScheduler({
// lot of time and do not go higher than the maximum consecutive time.
const unsafeModeEnabled = consecutiveUnsafeMode > 0 ?
consecutiveUnsafeMode < MAX_CONSECUTIVE_MANIFEST_PARSING_IN_UNSAFE_MODE :
- totalUpdateTime >= 100;
+ totalUpdateTime >= MIN_MANIFEST_PARSING_TIME_TO_ENTER_UNSAFE_MODE;
const internalRefresh$ = scheduleRefresh$
.pipe(mergeMap(({ completeRefresh, delay, canUseUnsafeMode }) => {
From ec7c02869ae652efe15e546ac316cc4d6477d044 Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Mon, 20 Apr 2020 13:47:13 +0200
Subject: [PATCH 43/72] doc: also move `static properties` chapter in the table
of contents
---
doc/api/index.md | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/doc/api/index.md b/doc/api/index.md
index 56e11a5920..4b2fe2aea1 100644
--- a/doc/api/index.md
+++ b/doc/api/index.md
@@ -5,11 +5,6 @@
- [Overview](#overview)
- [Instantiation](#instantiation)
-- [Static properties](#static)
- - [version](#static-version)
- - [ErrorTypes](#static-ErrorTypes)
- - [ErrorCodes](#static-ErrorCodes)
- - [LogLevel](#static-LogLevel)
- [Basic methods](#meth-group-basic)
- [loadVideo](#meth-loadVideo)
- [getPlayerState](#meth-getPlayerState)
@@ -91,6 +86,11 @@
- [exitFullscreen (deprecated)](#meth-exitFullscreen)
- [isFullscreen (deprecated)](#meth-isFullscreen)
- [getNativeTextTrack (deprecated)](#meth-getNativeTextTrack)
+- [Static properties](#static)
+ - [version](#static-version)
+ - [ErrorTypes](#static-ErrorTypes)
+ - [ErrorCodes](#static-ErrorCodes)
+ - [LogLevel](#static-LogLevel)
- [Tools](#tools)
- [Experimental - MediaCapabilitiesProber](#tools-mediaCapabilitiesProber)
- [Experimental - TextTrackRenderer](#tools-textTrackRenderer)
From 6a0cdb3b7d085b8802dd2d18f40fff6720110049 Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Mon, 6 Apr 2020 17:49:36 +0200
Subject: [PATCH 44/72] config: double minimum time of parsing time needed
compared to what was needed initially
---
src/config.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/config.ts b/src/config.ts
index e77eb948ce..c88aad7bb4 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -767,7 +767,7 @@ export default {
* Please note that this parsing time also sometimes includes idle time such
* as when the parser is waiting for a request to finish.
*/
- MIN_MANIFEST_PARSING_TIME_TO_ENTER_UNSAFE_MODE: 150,
+ MIN_MANIFEST_PARSING_TIME_TO_ENTER_UNSAFE_MODE: 200,
/**
* When we detect that the local Manifest might be out-of-sync with the
From cc66d30d1b928e012a07da4cad906e07f1011c19 Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Mon, 20 Apr 2020 14:57:30 +0200
Subject: [PATCH 45/72] misc: deprecate `getManifest`, `getCurrentAdaptations`
and `getCurrentRepresentations`
---
doc/api/deprecated.md | 44 +++++++++++++++++++++++++++++
doc/api/index.md | 57 ++++++++++++++++++++++++++------------
src/core/api/public_api.ts | 17 ++++++++++++
3 files changed, 100 insertions(+), 18 deletions(-)
diff --git a/doc/api/deprecated.md b/doc/api/deprecated.md
index dce43e9fdc..aee2625b88 100644
--- a/doc/api/deprecated.md
+++ b/doc/api/deprecated.md
@@ -69,6 +69,50 @@ multiple reasons:
The following RxPlayer methods are deprecated.
+### getManifest ################################################################
+
+`getManifest` returns our internal representation we have of a given "manifest"
+document.
+
+Though it was first exposed to allow users to have access to more precize
+information on the current content, this method also limited us on the possible
+evolutions we could do, as changing what this function returns would mean
+breaking the API.
+
+We also realized that that method was not used for now by the implementation we
+know of.
+
+For now, we decided we will simply remove that API in the next major version. If
+that's a problem for you, please open an issue.
+
+
+### getCurrentAdaptations ######################################################
+
+`getCurrentAdaptations` returns an object describing each tracks available for
+the current Period in the current content.
+
+Like `getManifest`, we found that this API was not much used and limited us on
+the possible evolutions we can do on the RxPlayer.
+
+Again like `getManifest`, we plan to remove that API completely without
+replacing it.
+If that's a problem for you, please open an issue.
+
+
+### getCurrentRepresentations ##################################################
+
+`getCurrentRepresentations` returns an object describing each "qualities"
+available in the current chosen tracks.
+
+Exactly like `getCurrentAdaptations` and `getManifest`, we found that this API:
+ - was not used as far as we know
+ - limited the evolutions we could do on the RxPlayer's code without breaking
+ the API.
+
+We thus plan to remove that API completely without replacing it.
+If that's a problem for you, please open an issue.
+
+
### getNativeTextTrack #########################################################
``getNativeTextTrack`` returned the first ``TextTrack`` element attached to the
diff --git a/doc/api/index.md b/doc/api/index.md
index 4b2fe2aea1..259eaebe59 100644
--- a/doc/api/index.md
+++ b/doc/api/index.md
@@ -76,11 +76,11 @@
- [Content information](#meth-group-content-info)
- [isLive](#meth-isLive)
- [getUrl](#meth-getUrl)
- - [getManifest](#meth-getManifest)
- - [getCurrentAdaptations](#meth-getCurrentAdaptations)
- - [getCurrentRepresentations](#meth-getCurrentRepresentations)
- [getCurrentKeySystem](#meth-getCurrentKeySystem)
- [Deprecated](#meth-group-deprecated)
+ - [getManifest (deprecated)](#meth-getManifest)
+ - [getCurrentAdaptations (deprecated)](#meth-getCurrentAdaptations)
+ - [getCurrentRepresentations (deprecated)](#meth-getCurrentRepresentations)
- [getImageTrackData (deprecated)](#meth-getImageTrackData)
- [setFullscreen (deprecated)](#meth-setFullscreen)
- [exitFullscreen (deprecated)](#meth-exitFullscreen)
@@ -2145,10 +2145,32 @@ if it was called.
It will return an empty Array if none of those two APIs were used until now.
+
+### getCurrentKeySystem ########################################################
+
+_return value_: ``string|undefined``
+
+Returns the type of keySystem used for DRM-protected contents.
+
+
+
+
+## Deprecated ##################################################################
+
+The following methods are deprecated. They are still supported but we advise
+users to not use those as they might become not supported in the future.
+
### getManifest ################################################################
+--
+
+:warning: This method is deprecated, it will disappear in the next major
+release ``v4.0.0`` (see [Deprecated APIs](./deprecated.md)).
+
+--
+
_return value_: ``Manifest|null``
Returns the current loaded [Manifest](../terms.md#manifest) if one.
@@ -2166,6 +2188,13 @@ The Manifest will be available before the player reaches the ``"LOADED"`` state.
### getCurrentAdaptations ######################################################
+--
+
+:warning: This method is deprecated, it will disappear in the next major
+release ``v4.0.0`` (see [Deprecated APIs](./deprecated.md)).
+
+--
+
_return value_: ``Object|null``
Returns the [Adaptations](../terms.md#adaptation) being loaded per type if a
@@ -2185,6 +2214,13 @@ options](./loadVideo_options.md#prop-transport)).
### getCurrentRepresentations ##################################################
+--
+
+:warning: This method is deprecated, it will disappear in the next major
+release ``v4.0.0`` (see [Deprecated APIs](./deprecated.md)).
+
+--
+
_return value_: ``Object|null``
Returns the [Representations](../terms.md#representation) being loaded per type
@@ -2201,21 +2237,6 @@ An Representation object structure is relatively complex and is described in the
options](./loadVideo_options.md#prop-transport)).
-
-### getCurrentKeySystem ########################################################
-
-_return value_: ``string|undefined``
-
-Returns the type of keySystem used for DRM-protected contents.
-
-
-
-
-## Deprecated ##################################################################
-
-The following methods are deprecated. They are still supported but we advise
-users to not use those as they might become not supported in the future.
-
### getImageTrackData ##########################################################
diff --git a/src/core/api/public_api.ts b/src/core/api/public_api.ts
index 85ca206061..23cc23735a 100644
--- a/src/core/api/public_api.ts
+++ b/src/core/api/public_api.ts
@@ -912,9 +912,12 @@ class Player extends EventEmitter {
/**
* Returns manifest/playlist object.
* null if the player is STOPPED.
+ * @deprecated
* @returns {Manifest|null} - The current Manifest (`null` when not known).
*/
getManifest() : Manifest|null {
+ warnOnce("getManifest is deprecated." +
+ " Please open an issue if you used this API.");
if (this._priv_contentInfos === null) {
return null;
}
@@ -924,11 +927,14 @@ class Player extends EventEmitter {
/**
* Returns Adaptations (tracks) for every currently playing type
* (audio/video/text...).
+ * @deprecated
* @returns {Object|null} - The current Adaptation objects, per type (`null`
* when none is known for now.
*/
getCurrentAdaptations(
) : Partial> | null {
+ warnOnce("getCurrentAdaptations is deprecated." +
+ " Please open an issue if you used this API.");
if (this._priv_contentInfos === null) {
return null;
}
@@ -945,11 +951,14 @@ class Player extends EventEmitter {
/**
* Returns representations (qualities) for every currently playing type
* (audio/video/text...).
+ * @deprecated
* @returns {Object|null} - The current Representation objects, per type
* (`null` when none is known for now.
*/
getCurrentRepresentations(
) : Partial> | null {
+ warnOnce("getCurrentRepresentations is deprecated." +
+ " Please open an issue if you used this API.");
if (this._priv_contentInfos === null) {
return null;
}
@@ -1223,7 +1232,9 @@ class Player extends EventEmitter {
* @returns {Number|undefined}
*/
getVideoBitrate() : number|undefined {
+ /* tslint:disable deprecation */
const representations = this.getCurrentRepresentations();
+ /* tslint:enable deprecation */
if (representations === null || representations.video == null) {
return undefined;
}
@@ -1235,7 +1246,9 @@ class Player extends EventEmitter {
* @returns {Number|undefined}
*/
getAudioBitrate() : number|undefined {
+ /* tslint:disable deprecation */
const representations = this.getCurrentRepresentations();
+ /* tslint:enable deprecation */
if (representations === null || representations.audio == null) {
return undefined;
}
@@ -2146,10 +2159,14 @@ class Player extends EventEmitter {
this._priv_triggerAvailableBitratesChangeEvent("availableVideoBitratesChange",
this.getAvailableVideoBitrates());
+ /* tslint:disable deprecation */
const audioBitrate = this.getCurrentRepresentations()?.audio?.bitrate ?? -1;
+ /* tslint:enable deprecation */
this._priv_triggerCurrentBitrateChangeEvent("audioBitrateChange", audioBitrate);
+ /* tslint:disable deprecation */
const videoBitrate = this.getCurrentRepresentations()?.video?.bitrate ?? -1;
+ /* tslint:enable deprecation */
this._priv_triggerCurrentBitrateChangeEvent("videoBitrateChange", videoBitrate);
}
From 806155e3d6f747e8ed2972d9b568726fca0c0a39 Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Mon, 20 Apr 2020 17:52:30 +0200
Subject: [PATCH 46/72] scripts: fix commit message on gh-pages when deploying
a demo through scripts/deploy_new_demo
---
scripts/deploy_new_demo | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/scripts/deploy_new_demo b/scripts/deploy_new_demo
index 04de3c3eec..826319fc7f 100755
--- a/scripts/deploy_new_demo
+++ b/scripts/deploy_new_demo
@@ -101,7 +101,7 @@ if [ -n "$(git status --porcelain $deployed_branch)" ]; then
echo "+--------------------------------------------------------+"
elif [[ $REPLY =~ ^[Yy](es)?$ ]]; then
git add "$deployed_branch"
- git commit -m "demo: deploy $current_version to the gh-pages" -S
+ git commit -m "demo: deploy $deployed_branch demo to the gh-pages" -S
git push origin gh-pages
break
elif [[ $REPLY =~ ^[Dd](iff)?$ ]]; then
From 903127abd384b44e30c792df9d3ecd5369f0e116 Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Mon, 6 Apr 2020 17:50:41 +0200
Subject: [PATCH 47/72] parsers/manifest/dash: rename baseOnPreviousManifest
unsafelyBaseOnPreviousManifest
---
.../__tests__/parse_from_document.test.ts | 6 +-
.../timeline/timeline_representation_index.ts | 64 ++++++++++---------
.../manifest/dash/parse_adaptation_sets.ts | 28 ++++----
src/parsers/manifest/dash/parse_mpd.ts | 23 +++----
src/parsers/manifest/dash/parse_periods.ts | 26 ++++----
.../manifest/dash/parse_representations.ts | 38 +++++------
src/transports/dash/manifest_parser.ts | 6 +-
7 files changed, 98 insertions(+), 93 deletions(-)
diff --git a/src/parsers/manifest/dash/__tests__/parse_from_document.test.ts b/src/parsers/manifest/dash/__tests__/parse_from_document.test.ts
index 4b9a056206..38c74c4102 100644
--- a/src/parsers/manifest/dash/__tests__/parse_from_document.test.ts
+++ b/src/parsers/manifest/dash/__tests__/parse_from_document.test.ts
@@ -28,17 +28,17 @@ describe("parseFromDocument", () => {
expect(function() {
parseFromDocument(doc, {
url: "",
- baseOnPreviousManifest: null,
- externalClockOffset: 10,
aggressiveMode: false,
+ externalClockOffset: 10,
+ unsafelyBaseOnPreviousManifest: null,
});
}).toThrow("document root should be MPD");
expect(function() {
const prevManifest = {} as any as Manifest;
parseFromDocument(doc, {
url: "",
- baseOnPreviousManifest: prevManifest,
aggressiveMode: false,
+ unsafelyBaseOnPreviousManifest: prevManifest,
});
}).toThrow("document root should be MPD");
});
diff --git a/src/parsers/manifest/dash/indexes/timeline/timeline_representation_index.ts b/src/parsers/manifest/dash/indexes/timeline/timeline_representation_index.ts
index dbb387b31c..e74f11cf92 100644
--- a/src/parsers/manifest/dash/indexes/timeline/timeline_representation_index.ts
+++ b/src/parsers/manifest/dash/indexes/timeline/timeline_representation_index.ts
@@ -120,15 +120,6 @@ export interface ITimelineIndexIndexArgument {
/** Aditional context needed by a SegmentTimeline RepresentationIndex. */
export interface ITimelineIndexContextArgument {
- /**
- * The parser should take this previous version of the
- * `TimelineRepresentationIndex` - which was from the same Representation
- * parsed at an earlier time - as a base to speed-up the parsing process.
- * /!\ If unexpected differences exist between both, there is a risk of
- * de-synchronization with what is actually on the server,
- * Use with moderation.
- */
- baseOnPreviousRepresentation : Representation | null;
/** Allows to obtain the minimum and maximum positions of a content. */
manifestBoundsCalculator : ManifestBoundsCalculator;
/** Start of the period concerned by this RepresentationIndex, in seconds. */
@@ -148,6 +139,15 @@ export interface ITimelineIndexContextArgument {
representationId? : string;
/** Bitrate of the Representation concerned. */
representationBitrate? : number;
+ /**
+ * The parser should take this previous version of the
+ * `TimelineRepresentationIndex` - which was from the same Representation
+ * parsed at an earlier time - as a base to speed-up the parsing process.
+ * /!\ If unexpected differences exist between both, there is a risk of
+ * de-synchronization with what is actually on the server,
+ * Use with moderation.
+ */
+ unsafelyBaseOnPreviousRepresentation : Representation | null;
}
/**
@@ -185,16 +185,6 @@ export default class TimelineRepresentationIndex implements IRepresentationIndex
/** Underlying structure to retrieve segment information. */
protected _index : ITimelineIndex;
- /**
- * This variable represents the same `TimelineRepresentationIndex` at the
- * previous Manifest update.
- * Note that it is not always set.
- * This can be used as a base to speed-up the creation of the underlying
- * index structure as it can be really heavy for long Manifests.
- * To avoid taking too much memory, this variable is reset to `null` once used.
- */
- private _baseOnPreviousIndex : TimelineRepresentationIndex | null;
-
/** Time, in terms of `performance.now`, of the last Manifest update. */
private _lastUpdate : number;
@@ -216,6 +206,16 @@ export default class TimelineRepresentationIndex implements IRepresentationIndex
*/
private _parseTimeline : (() => HTMLCollection) | null;
+ /**
+ * This variable represents the same `TimelineRepresentationIndex` at the
+ * previous Manifest update.
+ * Note that it is not always set.
+ * This can be used as a base to speed-up the creation of the underlying
+ * index structure as it can be really heavy for long Manifests.
+ * To avoid taking too much memory, this variable is reset to `null` once used.
+ */
+ private _unsafelyBaseOnPreviousIndex : TimelineRepresentationIndex | null;
+
/**
* @param {Object} index
* @param {Object} context
@@ -246,14 +246,18 @@ export default class TimelineRepresentationIndex implements IRepresentationIndex
performance.now() :
context.receivedTime;
- this._baseOnPreviousIndex = null;
- if (context.baseOnPreviousRepresentation !== null &&
- context.baseOnPreviousRepresentation.index instanceof TimelineRepresentationIndex)
+ this._unsafelyBaseOnPreviousIndex = null;
+ if (context.unsafelyBaseOnPreviousRepresentation !== null &&
+ context.unsafelyBaseOnPreviousRepresentation.index
+ instanceof TimelineRepresentationIndex)
{
// avoid too much nested references, to keep memory down
- context.baseOnPreviousRepresentation.index._baseOnPreviousIndex = null;
- this._baseOnPreviousIndex = context.baseOnPreviousRepresentation.index;
+ context.unsafelyBaseOnPreviousRepresentation
+ .index._unsafelyBaseOnPreviousIndex = null;
+ this._unsafelyBaseOnPreviousIndex = context
+ .unsafelyBaseOnPreviousRepresentation.index;
}
+
this._isDynamic = isDynamic;
this._parseTimeline = index.parseTimeline;
this._index = { indexRange: index.indexRange,
@@ -564,20 +568,20 @@ export default class TimelineRepresentationIndex implements IRepresentationIndex
const newElements = this._parseTimeline();
this._parseTimeline = null; // Free memory
- if (this._baseOnPreviousIndex === null) {
+ if (this._unsafelyBaseOnPreviousIndex === null) {
// Just completely parse the current timeline
return constructTimelineFromElements(newElements, this._scaledPeriodStart);
}
// Construct previously parsed timeline if not already done
let prevTimeline : IIndexSegment[];
- if (this._baseOnPreviousIndex._index.timeline === null) {
- prevTimeline = this._baseOnPreviousIndex._getTimeline();
- this._baseOnPreviousIndex._index.timeline = prevTimeline;
+ if (this._unsafelyBaseOnPreviousIndex._index.timeline === null) {
+ prevTimeline = this._unsafelyBaseOnPreviousIndex._getTimeline();
+ this._unsafelyBaseOnPreviousIndex._index.timeline = prevTimeline;
} else {
- prevTimeline = this._baseOnPreviousIndex._index.timeline;
+ prevTimeline = this._unsafelyBaseOnPreviousIndex._index.timeline;
}
- this._baseOnPreviousIndex = null; // Free memory
+ this._unsafelyBaseOnPreviousIndex = null; // Free memory
return constructTimelineFromPreviousTimeline(newElements,
prevTimeline,
diff --git a/src/parsers/manifest/dash/parse_adaptation_sets.ts b/src/parsers/manifest/dash/parse_adaptation_sets.ts
index d87b4da5cd..7d9dca6e02 100644
--- a/src/parsers/manifest/dash/parse_adaptation_sets.ts
+++ b/src/parsers/manifest/dash/parse_adaptation_sets.ts
@@ -42,15 +42,6 @@ export interface IAdaptationSetsContextInfos {
aggressiveMode : boolean;
/** availabilityTimeOffset of the concerned period. */
availabilityTimeOffset: number;
- /**
- * The parser should take this Period - which is from a previously parsed
- * Manifest for the same dynamic content - as a base to speed-up the parsing
- * process.
- * /!\ If unexpected differences exist between both, there is a risk of
- * de-synchronization with what is actually on the server,
- * Use with moderation.
- */
- baseOnPreviousPeriod : Period | null;
/** Eventual URLs from which every relative URL will be based on. */
baseURLs : string[];
/** Allows to obtain the first available position of a content. */
@@ -68,6 +59,15 @@ export interface IAdaptationSetsContextInfos {
start : number;
/** Depth of the buffer for the whole content, in seconds. */
timeShiftBufferDepth? : number;
+ /**
+ * The parser should take this Period - which is from a previously parsed
+ * Manifest for the same dynamic content - as a base to speed-up the parsing
+ * process.
+ * /!\ If unexpected differences exist between both, there is a risk of
+ * de-synchronization with what is actually on the server,
+ * Use with moderation.
+ */
+ unsafelyBaseOnPreviousPeriod : Period | null;
}
// Supplementary information for "switchable" AdaptationSets of the same Period
@@ -267,7 +267,6 @@ export default function parseAdaptationSets(
const adaptationInfos : IAdaptationInfos = {
aggressiveMode: periodInfos.aggressiveMode,
availabilityTimeOffset,
- baseOnPreviousAdaptation: null,
baseURLs: resolveBaseURLs(periodInfos.baseURLs, adaptationChildren.baseURLs),
manifestBoundsCalculator: periodInfos.manifestBoundsCalculator,
end: periodInfos.end,
@@ -275,10 +274,11 @@ export default function parseAdaptationSets(
receivedTime: periodInfos.receivedTime,
start: periodInfos.start,
timeShiftBufferDepth: periodInfos.timeShiftBufferDepth,
+ unsafelyBaseOnPreviousAdaptation: null,
};
if (type === "video" && videoMainAdaptation !== null && isMainAdaptation) {
- adaptationInfos.baseOnPreviousAdaptation =
- periodInfos.baseOnPreviousPeriod?.getAdaptation(videoMainAdaptation.id) ?? null;
+ adaptationInfos.unsafelyBaseOnPreviousAdaptation = periodInfos
+ .unsafelyBaseOnPreviousPeriod?.getAdaptation(videoMainAdaptation.id) ?? null;
const representations = parseRepresentations(representationsIR,
adaptation,
adaptationInfos);
@@ -322,8 +322,8 @@ export default function parseAdaptationSets(
newID = adaptationID;
parsedAdaptationsIDs.push(adaptationID);
- adaptationInfos.baseOnPreviousAdaptation =
- periodInfos.baseOnPreviousPeriod?.getAdaptation(adaptationID) ?? null;
+ adaptationInfos.unsafelyBaseOnPreviousAdaptation = periodInfos
+ .unsafelyBaseOnPreviousPeriod?.getAdaptation(adaptationID) ?? null;
const representations = parseRepresentations(representationsIR,
adaptation,
adaptationInfos);
diff --git a/src/parsers/manifest/dash/parse_mpd.ts b/src/parsers/manifest/dash/parse_mpd.ts
index 84632cac35..7ae0e49efe 100644
--- a/src/parsers/manifest/dash/parse_mpd.ts
+++ b/src/parsers/manifest/dash/parse_mpd.ts
@@ -43,15 +43,6 @@ const { DASH_FALLBACK_LIFETIME_WHEN_MINIMUM_UPDATE_PERIOD_EQUAL_0 } = config;
export interface IMPDParserArguments {
/** Whether we should request new segments even if they are not yet finished. */
aggressiveMode : boolean;
- /**
- * The parser should take this Manifest - which is a previously parsed
- * Manifest for the same dynamic content - as a base to speed-up the parsing
- * process.
- * /!\ If unexpected differences exist between the two, there is a risk of
- * de-synchronization with what is actually on the server,
- * Use with moderation.
- */
- baseOnPreviousManifest : Manifest | null;
/**
* If set, offset to add to `performance.now()` to obtain the current server's
* time.
@@ -61,6 +52,15 @@ export interface IMPDParserArguments {
manifestReceivedTime? : number;
/** Default base time, in seconds. */
referenceDateTime? : number;
+ /**
+ * The parser should take this Manifest - which is a previously parsed
+ * Manifest for the same dynamic content - as a base to speed-up the parsing
+ * process.
+ * /!\ If unexpected differences exist between the two, there is a risk of
+ * de-synchronization with what is actually on the server,
+ * Use with moderation.
+ */
+ unsafelyBaseOnPreviousManifest : Manifest | null;
/** URL of the manifest (post-redirection if one). */
url? : string;
}
@@ -221,7 +221,8 @@ function parseCompleteIntermediateRepresentation(
const availabilityStartTime = parseAvailabilityStartTime(rootAttributes,
args.referenceDateTime);
const timeShiftBufferDepth = rootAttributes.timeShiftBufferDepth;
- const clockOffset = args.externalClockOffset;
+ const { externalClockOffset: clockOffset,
+ unsafelyBaseOnPreviousManifest } = args;
const availabilityTimeOffset =
extractMinimumAvailabilityTimeOffset(rootChildren.baseURLs);
@@ -229,12 +230,12 @@ function parseCompleteIntermediateRepresentation(
availabilityStartTime,
availabilityTimeOffset,
baseURLs,
- baseOnPreviousManifest: args.baseOnPreviousManifest,
clockOffset,
duration: rootAttributes.duration,
isDynamic,
receivedTime: args.manifestReceivedTime,
timeShiftBufferDepth,
+ unsafelyBaseOnPreviousManifest,
xlinkInfos };
const parsedPeriods = parsePeriods(rootChildren.periods, manifestInfos);
const mediaPresentationDuration = rootAttributes.duration;
diff --git a/src/parsers/manifest/dash/parse_periods.ts b/src/parsers/manifest/dash/parse_periods.ts
index f6902f4a23..b46cba4acb 100644
--- a/src/parsers/manifest/dash/parse_periods.ts
+++ b/src/parsers/manifest/dash/parse_periods.ts
@@ -50,15 +50,6 @@ export interface IPeriodsContextInfos {
aggressiveMode : boolean;
availabilityTimeOffset: number;
availabilityStartTime : number;
- /**
- * The parser should take this Manifest - which is a previously parsed
- * Manifest for the same dynamic content - as a base to speed-up the parsing
- * process.
- * /!\ If unexpected differences exist between the two, there is a risk of
- * de-synchronization with what is actually on the server,
- * Use with moderation.
- */
- baseOnPreviousManifest : Manifest | null;
baseURLs : string[];
clockOffset? : number;
duration? : number;
@@ -70,6 +61,15 @@ export interface IPeriodsContextInfos {
receivedTime? : number;
/** Depth of the buffer for the whole content, in seconds. */
timeShiftBufferDepth? : number;
+ /**
+ * The parser should take this Manifest - which is a previously parsed
+ * Manifest for the same dynamic content - as a base to speed-up the parsing
+ * process.
+ * /!\ If unexpected differences exist between the two, there is a risk of
+ * de-synchronization with what is actually on the server,
+ * Use with moderation.
+ */
+ unsafelyBaseOnPreviousManifest : Manifest | null;
xlinkInfos : IXLinkInfos;
}
@@ -130,19 +130,19 @@ export interface IPeriodsContextInfos {
extractMinimumAvailabilityTimeOffset(periodIR.children.baseURLs) +
contextInfos.availabilityTimeOffset;
- const baseOnPreviousPeriod =
- contextInfos.baseOnPreviousManifest?.getPeriod(periodID) ?? null;
+ const unsafelyBaseOnPreviousPeriod = contextInfos
+ .unsafelyBaseOnPreviousManifest?.getPeriod(periodID) ?? null;
const periodInfos = { aggressiveMode: contextInfos.aggressiveMode,
availabilityTimeOffset,
- baseOnPreviousPeriod,
baseURLs: periodBaseURLs,
manifestBoundsCalculator,
end: periodEnd,
isDynamic,
receivedTime,
start: periodStart,
- timeShiftBufferDepth };
+ timeShiftBufferDepth,
+ unsafelyBaseOnPreviousPeriod };
const adaptations = parseAdaptationSets(periodIR.children.adaptations,
periodInfos);
const parsedPeriod : IParsedPeriod = { id: periodID,
diff --git a/src/parsers/manifest/dash/parse_representations.ts b/src/parsers/manifest/dash/parse_representations.ts
index 93af817606..7f4e3b523c 100644
--- a/src/parsers/manifest/dash/parse_representations.ts
+++ b/src/parsers/manifest/dash/parse_representations.ts
@@ -46,15 +46,6 @@ export interface IAdaptationInfos {
aggressiveMode : boolean;
/** availability time offset of the concerned Adaptation. */
availabilityTimeOffset: number;
- /**
- * The parser should take this Adaptation - which is from a previously parsed
- * Manifest for the same dynamic content - as a base to speed-up the parsing
- * process.
- * /!\ If unexpected differences exist between both, there is a risk of
- * de-synchronization with what is actually on the server,
- * Use with moderation.
- */
- baseOnPreviousAdaptation : Adaptation | null;
/** Eventual URLs from which every relative URL will be based on. */
baseURLs : string[];
/** Allows to obtain the first available position of a dynamic content. */
@@ -72,6 +63,15 @@ export interface IAdaptationInfos {
start : number;
/** Depth of the buffer for the whole content, in seconds. */
timeShiftBufferDepth? : number;
+ /**
+ * The parser should take this Adaptation - which is from a previously parsed
+ * Manifest for the same dynamic content - as a base to speed-up the parsing
+ * process.
+ * /!\ If unexpected differences exist between both, there is a risk of
+ * de-synchronization with what is actually on the server,
+ * Use with moderation.
+ */
+ unsafelyBaseOnPreviousAdaptation : Adaptation | null;
}
/** Base context given to the various indexes. */
@@ -79,13 +79,6 @@ interface IIndexContext {
/** Whether we should request new segments even if they are not yet finished. */
aggressiveMode : boolean;
availabilityTimeOffset: number;
- /**
- * The parser should take this Representation - which is the same as this one
- * parsed at an earlier time - as a base to speed-up the parsing process.
- * /!\ If unexpected differences exist between both, there is a risk of
- * de-synchronization with what is actually on the server.
- */
- baseOnPreviousRepresentation: Representation | null;
/** Allows to obtain the first available position of a dynamic content. */
manifestBoundsCalculator : ManifestBoundsCalculator;
/** Whether the Manifest can evolve with time. */
@@ -102,6 +95,13 @@ interface IIndexContext {
representationBitrate? : number;
/** Depth of the buffer for the whole content, in seconds. */
timeShiftBufferDepth? : number;
+ /**
+ * The parser should take this Representation - which is the same as this one
+ * parsed at an earlier time - as a base to speed-up the parsing process.
+ * /!\ If unexpected differences exist between both, there is a risk of
+ * de-synchronization with what is actually on the server.
+ */
+ unsafelyBaseOnPreviousRepresentation: Representation | null;
}
/**
@@ -182,14 +182,14 @@ export default function parseRepresentations(
}
// 2. Retrieve previous version of the Representation, if one.
- const baseOnPreviousRepresentation =
- adaptationInfos.baseOnPreviousAdaptation?.getRepresentation(representationID) ??
+ const unsafelyBaseOnPreviousRepresentation = adaptationInfos
+ .unsafelyBaseOnPreviousAdaptation?.getRepresentation(representationID) ??
null;
// 3. Find Index
const context = { aggressiveMode: adaptationInfos.aggressiveMode,
availabilityTimeOffset: adaptationInfos.availabilityTimeOffset,
- baseOnPreviousRepresentation,
+ unsafelyBaseOnPreviousRepresentation,
manifestBoundsCalculator: adaptationInfos.manifestBoundsCalculator,
isDynamic: adaptationInfos.isDynamic,
periodEnd: adaptationInfos.end,
diff --git a/src/transports/dash/manifest_parser.ts b/src/transports/dash/manifest_parser.ts
index 990c15e3e4..3ef4203cc8 100644
--- a/src/transports/dash/manifest_parser.ts
+++ b/src/transports/dash/manifest_parser.ts
@@ -79,11 +79,11 @@ export default function generateManifestParser(
response.responseData as Document;
const externalClockOffset = serverTimeOffset ?? argClockOffset;
- const baseOnPreviousManifest = args.unsafeMode ? args.previousManifest :
- null;
+ const unsafelyBaseOnPreviousManifest = args.unsafeMode ? args.previousManifest :
+ null;
const parsedManifest = dashManifestParser(data, { aggressiveMode:
aggressiveMode === true,
- baseOnPreviousManifest,
+ unsafelyBaseOnPreviousManifest,
url,
referenceDateTime,
externalClockOffset });
From eade848e95bd1ae30b6b397258718777aa2bd00d Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Mon, 6 Apr 2020 19:09:53 +0200
Subject: [PATCH 48/72] parsers/manifest/dash: only perform the unsafe
optimization after a minimum amount of S elements of 300
---
src/config.ts | 8 ++++++++
.../indexes/timeline/timeline_representation_index.ts | 7 ++++++-
2 files changed, 14 insertions(+), 1 deletion(-)
diff --git a/src/config.ts b/src/config.ts
index c88aad7bb4..e4f175f525 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -769,6 +769,14 @@ export default {
*/
MIN_MANIFEST_PARSING_TIME_TO_ENTER_UNSAFE_MODE: 200,
+ /**
+ * Minimum amount of elements in a DASH MPD's element
+ * necessary to begin parsing the current SegmentTimeline element in an
+ * unsafe manner (meaning: with risks of de-synchronization).
+ * This is only done when the "unsafeMode" parsing mode is enabled.
+ */
+ MIN_DASH_S_ELEMENTS_TO_PARSE_UNSAFELY: 300,
+
/**
* When we detect that the local Manifest might be out-of-sync with the
* server's one, we schedule a Manifest refresh.
diff --git a/src/parsers/manifest/dash/indexes/timeline/timeline_representation_index.ts b/src/parsers/manifest/dash/indexes/timeline/timeline_representation_index.ts
index e74f11cf92..6c129b9775 100644
--- a/src/parsers/manifest/dash/indexes/timeline/timeline_representation_index.ts
+++ b/src/parsers/manifest/dash/indexes/timeline/timeline_representation_index.ts
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+import config from "../../../../../config";
import {
ICustomError,
NetworkError,
@@ -41,6 +42,8 @@ import { createIndexURLs } from "../tokens";
import constructTimelineFromElements from "./construct_timeline_from_elements";
import constructTimelineFromPreviousTimeline from "./construct_timeline_from_previous_timeline";
+const { MIN_DASH_S_ELEMENTS_TO_PARSE_UNSAFELY } = config;
+
/**
* Index property defined for a SegmentTimeline RepresentationIndex
* This object contains every property needed to generate an ISegment for a
@@ -568,7 +571,9 @@ export default class TimelineRepresentationIndex implements IRepresentationIndex
const newElements = this._parseTimeline();
this._parseTimeline = null; // Free memory
- if (this._unsafelyBaseOnPreviousIndex === null) {
+ if (this._unsafelyBaseOnPreviousIndex === null ||
+ newElements.length < MIN_DASH_S_ELEMENTS_TO_PARSE_UNSAFELY)
+ {
// Just completely parse the current timeline
return constructTimelineFromElements(newElements, this._scaledPeriodStart);
}
From 834478f22b4b82fe4e804b1f9f4c8f10fc4fdd64 Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Mon, 20 Apr 2020 19:16:19 +0200
Subject: [PATCH 49/72] doc: fix typo in getVideoTrack documentation
---
doc/api/index.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/doc/api/index.md b/doc/api/index.md
index 259eaebe59..8b1eb77171 100644
--- a/doc/api/index.md
+++ b/doc/api/index.md
@@ -886,7 +886,7 @@ no text tracks API in the browser, this method will return ``undefined``.
_returns_: ``Object|null|undefined``
Get information about the video track currently set.
-``null`` if no audio track is enabled right now.
+``null`` if no video track is enabled right now.
If a video track is set and information about it is known, this method will
return an object with the following properties:
From c8f0ce530cf76504ce15bd953704d34e9288b3ec Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Mon, 20 Apr 2020 14:34:09 +0200
Subject: [PATCH 50/72] dash/parsers: add big warning to anounce that the
`timeline` property of a TimelineRepresentationIndex should mirror as close
as possible the correspdonding MPD SegmentTimeline node.
---
.../timeline/timeline_representation_index.ts | 24 +++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/src/parsers/manifest/dash/indexes/timeline/timeline_representation_index.ts b/src/parsers/manifest/dash/indexes/timeline/timeline_representation_index.ts
index 6c129b9775..160a989207 100644
--- a/src/parsers/manifest/dash/indexes/timeline/timeline_representation_index.ts
+++ b/src/parsers/manifest/dash/indexes/timeline/timeline_representation_index.ts
@@ -83,6 +83,18 @@ export interface ITimelineIndex {
* Every segments defined in this index.
* `null` at the beginning as this property is parsed lazily (only when first
* needed) for performances reasons.
+ *
+ * /!\ Please note that this structure should follow the exact same structure
+ * than a SegmentTimeline element in the corresponding MPD.
+ * This means:
+ * - It should have the same amount of elements in its array than there was
+ * `` elements in the SegmentTimeline.
+ * - Each of those same elements should have the same start time, the same
+ * duration and the same repeat counter than what could be deduced from
+ * the SegmentTimeline.
+ * This is needed to be able to run parsing optimization when refreshing the
+ * MPD. Not doing so could lead to the RxPlayer not being able to play the
+ * stream anymore.
*/
timeline : IIndexSegment[] | null;
/**
@@ -557,6 +569,18 @@ export default class TimelineRepresentationIndex implements IRepresentationIndex
* After calling it, every now unneeded variable will be freed from memory.
* This means that calling _getTimeline more than once will just return an
* empty array.
+ *
+ * /!\ Please note that this structure should follow the exact same structure
+ * than a SegmentTimeline element in the corresponding MPD.
+ * This means:
+ * - It should have the same amount of elements in its array than there was
+ * `` elements in the SegmentTimeline.
+ * - Each of those same elements should have the same start time, the same
+ * duration and the same repeat counter than what could be deduced from
+ * the SegmentTimeline.
+ * This is needed to be able to run parsing optimization when refreshing the
+ * MPD. Not doing so could lead to the RxPlayer not being able to play the
+ * stream anymore.
* @returns {Array.}
*/
private _getTimeline() : IIndexSegment[] {
From 7a34029f4613c11915fbd296fa8f69ba68cfd5b3 Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Tue, 21 Apr 2020 11:33:24 +0200
Subject: [PATCH 51/72] demo: add the possibility to disable the current video
track on the demo page
---
.../scripts/controllers/PlayerKnobsSettings.jsx | 7 +------
.../scripts/controllers/knobs/Subtitles.jsx | 4 ++--
.../scripts/controllers/knobs/VideoTrack.jsx | 17 +++++++++++------
demo/full/scripts/modules/player/index.js | 6 +++++-
4 files changed, 19 insertions(+), 15 deletions(-)
diff --git a/demo/full/scripts/controllers/PlayerKnobsSettings.jsx b/demo/full/scripts/controllers/PlayerKnobsSettings.jsx
index b452442b32..e5c0171ba3 100644
--- a/demo/full/scripts/controllers/PlayerKnobsSettings.jsx
+++ b/demo/full/scripts/controllers/PlayerKnobsSettings.jsx
@@ -11,7 +11,6 @@ function PlayerKnobsSettings({
shouldDisplay,
close,
player,
- availableVideoTracks,
lowLatencyMode,
isContentLoaded,
}) {
@@ -43,10 +42,7 @@ function PlayerKnobsSettings({
- {
- availableVideoTracks.length > 1 ?
- : null
- }
+
);
@@ -57,6 +53,5 @@ export default React.memo(withModulesState({
lowLatencyMode: "lowLatencyMode",
isStopped: "isStopped",
isContentLoaded: "isContentLoaded",
- availableVideoTracks: "availableVideoTracks",
},
})(PlayerKnobsSettings));
diff --git a/demo/full/scripts/controllers/knobs/Subtitles.jsx b/demo/full/scripts/controllers/knobs/Subtitles.jsx
index 01a62ffd4a..54df52148a 100644
--- a/demo/full/scripts/controllers/knobs/Subtitles.jsx
+++ b/demo/full/scripts/controllers/knobs/Subtitles.jsx
@@ -41,10 +41,10 @@ const SubtitlesKnobBase = ({
return (
`track ${i}: ${track.id}`);
+ options = ["no video track"].concat(
+ availableVideoTracks.map((track, i) => `track ${i}: ${track.id}`),
+ );
selectedIndex = currentVideoTrack ?
- Math.max(findVideoTrackIndex(currentVideoTrack, availableVideoTracks), 0)
+ Math.max(findVideoTrackIndex(currentVideoTrack, availableVideoTracks), 1)
: 0;
}
const onTrackChange = (evt) => {
const index = +evt.target.value;
- const track = availableVideoTracks[index];
- player.dispatch("SET_VIDEO_TRACK", track);
+ if (index === 0) {
+ player.dispatch("DISABLE_VIDEO_TRACK");
+ } else {
+ const track = availableVideoTracks[index - 1];
+ player.dispatch("SET_VIDEO_TRACK", track);
+ }
};
return (
@@ -38,7 +43,7 @@ const VideoTrackKnobBase = ({
name="Video Track"
ariaLabel="Update the video track"
className={className}
- disabled={availableVideoTracks.length < 2}
+ disabled={options.length <= 1}
onChange={onTrackChange}
options={options}
selected={selectedIndex}
diff --git a/demo/full/scripts/modules/player/index.js b/demo/full/scripts/modules/player/index.js
index d962358e12..bf367d57f7 100644
--- a/demo/full/scripts/modules/player/index.js
+++ b/demo/full/scripts/modules/player/index.js
@@ -152,6 +152,10 @@ const PLAYER = ({ $destroy, state }, { videoElement, textTrackElement }) => {
player.setVideoTrack(track.id);
},
+ DISABLE_VIDEO_TRACK: () => {
+ player.disableVideoTrack();
+ },
+
SET_SUBTITLES_TRACK: (track) => {
player.setTextTrack(track.id);
},
@@ -171,7 +175,7 @@ const PLAYER = ({ $destroy, state }, { videoElement, textTrackElement }) => {
DISABLE_LIVE_CATCH_UP() {
$switchCatchUpMode.next(false);
- }
+ },
};
};
From 275e63e977791a1359a68ae813cef8c01c321d5c Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Mon, 20 Apr 2020 19:08:38 +0200
Subject: [PATCH 52/72] manifest: remove commented import
---
src/manifest/adaptation.ts | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/manifest/adaptation.ts b/src/manifest/adaptation.ts
index ee6eab2d4f..ba8852b7da 100644
--- a/src/manifest/adaptation.ts
+++ b/src/manifest/adaptation.ts
@@ -25,7 +25,6 @@ import arrayIncludes from "../utils/array_includes";
import isNullOrUndefined from "../utils/is_null_or_undefined";
import normalizeLanguage from "../utils/languages";
import uniq from "../utils/uniq";
-// import filterSupportedRepresentations from "./filter_supported_representations";
import Representation from "./representation";
import { IAdaptationType } from "./types";
From e8a176a73eb60cd5ff14e94f8f7c1b3c4cb56928 Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Tue, 21 Apr 2020 11:38:24 +0200
Subject: [PATCH 53/72] parsers/dash: remove unnecessary `adaptation-` from
Adaptation ids
---
src/parsers/manifest/dash/parse_adaptation_sets.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/parsers/manifest/dash/parse_adaptation_sets.ts b/src/parsers/manifest/dash/parse_adaptation_sets.ts
index 084ee4a837..e666cca41d 100644
--- a/src/parsers/manifest/dash/parse_adaptation_sets.ts
+++ b/src/parsers/manifest/dash/parse_adaptation_sets.ts
@@ -170,7 +170,7 @@ function getAdaptationID(
idString += representations.length > 0 ?
("-" + representations[0].id) : "-empty";
}
- return "adaptation-" + idString;
+ return idString;
}
/**
From b92ee2b82b1760f39de2251bc1ac61a4c9da0eac Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Mon, 20 Apr 2020 19:42:51 +0200
Subject: [PATCH 54/72] parsers: remove commented import
---
.../dash/indexes/timeline/timeline_representation_index.ts | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/parsers/manifest/dash/indexes/timeline/timeline_representation_index.ts b/src/parsers/manifest/dash/indexes/timeline/timeline_representation_index.ts
index 160a989207..507fda2fc9 100644
--- a/src/parsers/manifest/dash/indexes/timeline/timeline_representation_index.ts
+++ b/src/parsers/manifest/dash/indexes/timeline/timeline_representation_index.ts
@@ -35,7 +35,6 @@ import {
import isSegmentStillAvailable from "../../../utils/is_segment_still_available";
import updateSegmentTimeline from "../../../utils/update_segment_timeline";
import ManifestBoundsCalculator from "../../manifest_bounds_calculator";
-// import { IParsedS } from "../../node_parsers/S";
import getInitSegment from "../get_init_segment";
import getSegmentsFromTimeline from "../get_segments_from_timeline";
import { createIndexURLs } from "../tokens";
From f338dc7418ddf5b26adb3c11a0db06c0c541435c Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Tue, 21 Apr 2020 11:49:31 +0200
Subject: [PATCH 55/72] parsers/dash: remove unnecessary blank line
---
.../dash/indexes/timeline/find_first_common_start_time.ts | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/parsers/manifest/dash/indexes/timeline/find_first_common_start_time.ts b/src/parsers/manifest/dash/indexes/timeline/find_first_common_start_time.ts
index e41218668c..262b21b2ee 100644
--- a/src/parsers/manifest/dash/indexes/timeline/find_first_common_start_time.ts
+++ b/src/parsers/manifest/dash/indexes/timeline/find_first_common_start_time.ts
@@ -91,7 +91,6 @@ export default function findFirstCommonStartTime(
return null;
}
}
-
} else {
let newElementsIdx = 0;
let newElt = newElements[0];
From 94b0a85edbd299a848d76666cc52c90b21730b97 Mon Sep 17 00:00:00 2001
From: peaBerberian
Date: Tue, 21 Apr 2020 11:57:48 +0200
Subject: [PATCH 56/72] update generated documentation
---
doc/generated/list.html | 2 +-
doc/generated/pages/api/deprecated.html | 36 +
doc/generated/pages/api/index.html | 2194 ++++++++++-------
doc/generated/pages/api/local_manifest.html | 12 +-
doc/generated/pages/api/manifest.html | 20 +-
doc/generated/pages/api/minimal_player.html | 4 +-
doc/generated/pages/api/player_events.html | 6 +
doc/generated/pages/api/player_options.html | 316 ++-
.../{pipelines => fetchers}/index.html | 48 +-
doc/generated/pages/architecture/files.html | 12 +-
doc/generated/pages/architecture/index.html | 8 +-
doc/generated/pages/terms.html | 134 +-
12 files changed, 1773 insertions(+), 1019 deletions(-)
rename doc/generated/pages/architecture/{pipelines => fetchers}/index.html (60%)
diff --git a/doc/generated/list.html b/doc/generated/list.html
index fc19441fec..a831c2c33e 100644
--- a/doc/generated/list.html
+++ b/doc/generated/list.html
@@ -50,8 +50,8 @@ Page list
Custom SourceBuffers
The EMEManager
+The fetchers
The Init
-The Pipelines
The SourceBuffers
SegmentInventory
diff --git a/doc/generated/pages/api/deprecated.html b/doc/generated/pages/api/deprecated.html
index 2547cc0f6c..708863c64d 100644
--- a/doc/generated/pages/api/deprecated.html
+++ b/doc/generated/pages/api/deprecated.html
@@ -4,6 +4,9 @@
Image (BIF) APIs
RxPlayer Methods
+getManifest
+getCurrentAdaptations
+getCurrentRepresentations
getNativeTextTrack
isFullscreen
setFullscreen
@@ -103,6 +106,39 @@ Image (BIF) APIs
RxPlayer Methods
The following RxPlayer methods are deprecated.
+
+getManifest
+getManifest
returns our internal representation we have of a given “manifest”
+document.
+Though it was first exposed to allow users to have access to more precize
+information on the current content, this method also limited us on the possible
+evolutions we could do, as changing what this function returns would mean
+breaking the API.
+We also realized that that method was not used for now by the implementation we
+know of.
+For now, we decided we will simply remove that API in the next major version. If
+that’s a problem for you, please open an issue.
+
+getCurrentAdaptations
+getCurrentAdaptations
returns an object describing each tracks available for
+the current Period in the current content.
+Like getManifest
, we found that this API was not much used and limited us on
+the possible evolutions we can do on the RxPlayer.
+Again like getManifest
, we plan to remove that API completely without
+replacing it.
+If that’s a problem for you, please open an issue.
+
+getCurrentRepresentations
+getCurrentRepresentations
returns an object describing each “qualities”
+available in the current chosen tracks.
+Exactly like getCurrentAdaptations
and getManifest
, we found that this API:
+
+was not used as far as we know
+limited the evolutions we could do on the RxPlayer’s code without breaking
+the API.
+
+We thus plan to remove that API completely without replacing it.
+If that’s a problem for you, please open an issue.
getNativeTextTrack
getNativeTextTrack
returned the first TextTrack
element attached to the
diff --git a/doc/generated/pages/api/index.html b/doc/generated/pages/api/index.html
index bff6f156e8..6ddace1a64 100644
--- a/doc/generated/pages/api/index.html
+++ b/doc/generated/pages/api/index.html
@@ -2,18 +2,9 @@
Overview
Instantiation
-Static properties
-
-
-Methods
+Basic methods
+
+Speed control
+
+
+Volume control
+
+
+Track selection
+
+
+Bitrate selection
+
+
+Buffer control
+
+
+Buffer information
+
+
+Content information
+
+
+Deprecated
+
+Static properties
+
+
Tools
MediaCapabilitiesProber
@@ -99,87 +134,36 @@ RxPlayer API
Overview
The RxPlayer has a complete API allowing you to:
-load and stop video or audio contents
-perform trickmodes (play, pause, seek, etc.) as a content is loaded.
+load and stop contents containing video and/or audio media data
+control playback (play, pause, seek, etc.) when a content is loaded.
get multiple information on the current content and on the player’s state.
-choose a specific audio language or subtitles track
-set your own bitrate and buffer length
+choose a specific audio language, subtitles track video track
+force a given bitrate
+update the wanted buffer length to reach
and more
The following pages define the entire API.
⚠️ Only variables and methods defined here are considered as part of the
-API. Any other property or method you might find by using our library can change
-without notice (not considered as part of the API).
-Only use the documented variables and open an issue if you think it’s not
-enough.
+API. Any other property or method you might find in any other way are not
+considered as part of the API and can thus change without notice.
Note: As some terms used here might be too foreign or slightly different than
the one you’re used to, we also wrote a list of terms and definitions used by
the RxPlayer here .
Instantiation
-Instantiating a new player is straightforward:
+Instantiating a new RxPlayer is necessary before being able to load a content.
+Doing so is straightforward:
import RxPlayer from "rx-player" ;
const player = new RxPlayer(options);
The options are all… optional. They are all defined in the Player Options
page .
-
-
-Static properties
-
-
-version
-type : Number
-The current version of the RxPlayer.
-
-
-ErrorTypes
-type : Object
-The different “types” of Error you can get on playback error,
-See the Player Error documentation for more information.
-
-
-ErrorCodes
-type : Object
-The different Error “codes” you can get on playback error,
-See the Player Error documentation for more information.
-
-
-LogLevel
-type : string
-default : "NONE"
-The current level of verbosity for the RxPlayer logs. Those logs all use the
-console.
-From the less verbose to the most:
-
-
-"NONE"
: no log
-
-
-"ERROR"
: unexpected errors (via console.error
)
-
-
-"WARNING"
: The previous level + minor problems encountered (via
-console.warn
)
-
-
-"INFO"
: The previous levels + noteworthy events (via console.info
)
-
-
-"DEBUG"
: The previous levels + normal events of the player (via
-console.log
)
-
-
-If the value set to this property is different than those, it will be
-automatically set to "NONE"
.
-Example
-import RxPlayer from "rx-player" ;
-RxPlayer.LogLevel = "WARNING" ;
-
-
-Methods
+
+Basic methods
+In this chapter, we will go through the basic methods you will need to use when
+playing a content through the RxPlayer.
loadVideo
@@ -187,9 +171,11 @@ loadVideo
-Loads a new video described in the argument.
-The options possible as arguments are all defined in this
+Loads the content described in the argument.
+This is the central method to use when you want to play a new content.
+The options possible as arguments are all defined in this
page .
+Despite its name, this method can also load audio-only content.
Example
player.loadVideo({
url : "http://vm2.dashif.org/livesim-dev/segtimeline_1/testpic_6s/Manifest.mpd" ,
@@ -197,21 +183,11 @@ Example
autoPlay : true ,
});
-
-
-getVideoElement
-return value : HTMLMediaElement
-Returns the video element used by the player.
-You’re not encouraged to use its API, you should always prefer the Player’s API.
-Example
-const videoElement = player.getVideoElement();
-videoElement.className = "my-video-element" ;
-
getPlayerState
return value : string
-The current player’s state.
+
The “state” the player is currently in.
Can be either one of those strings:
@@ -304,6 +280,9 @@ addEventListener
Add an event listener to trigger a callback as it happens. The callback will
have the event payload as a single argument.
+The RxPlayer API is heavily event-based. As an example: to know when a content
+is loaded, the most straightforward way is to add an event listener for the
+"playerStateChange"
event. This can be done only through this method.
To have the complete list of player events, consult the Player events
page .
Example
@@ -320,11 +299,12 @@ removeEventListener
callback (optional) (Function
): The callback given when calling the
corresponding addEventListener
API.
-Remove an event listener. That is, stop your registered callback (with
-addEventListener
) to be called as events happen and free up ressources.
+Remove an event listener.
+That is, remove a callback previously registered with addEventListener
from
+being triggered on the corresponding event. This also free-up the corresponding
+ressources.
The callback given is optional: if not given, every registered callback to
-that event will be removed. That’s why using both arguments is recommended for
-most usecase.
+that event will be removed.
Example
player.removeEventListener("playerStateChange" , listenerCallback);
@@ -333,6 +313,9 @@ Example
play
return value : Promise.<void>
Play/resume the current video. Equivalent to a video element’s play method.
+You might want to call that method either to start playing (when the content is
+in the "LOADED"
state and auto-play has not been enabled in the last
+loadVideo
call) or to resume when the content has been paused.
The returned Promise informs you on the result:
@@ -372,6 +355,8 @@ Example
stop
Stop playback of the current content if one.
+This will totaly un-load the current content. To re-start playing the same
+content, you will need to call loadVideo
again.
Example
const stopVideo = () => {
player.stop();
@@ -381,9 +366,23 @@ Example
getPosition
return value : Number
-Returns the video element’s current position, in seconds.
-The difference with the getWallClockTime
method is that for live contents
-the position is not re-calculated to match a live timestamp.
+Returns the current media element’s playing position, in seconds.
+For live contents, the returned position will not be re-scaled to correspond to
+a live timestamp. If you want that behavior, you can call getWallClockTime
+instead.
+This is the only difference between the two. Generally, you can follow the
+following rule:
+
+
+if you want to use that current position to use it with the other APIs
+(like seekTo
, getMinimumPosition
, getMaximumPosition
+etc.) use getPosition
.
+
+
+if you want to display the current position to the viewer/listener, use
+getWallClockTime
instead.
+
+
Example
const pos = player.getPosition();
console .log(`The video element's current position is: ${pos} second(s)` );
@@ -392,11 +391,18 @@ Example
getWallClockTime
return value : Number
-Returns the wall-clock-time of the current position in seconds.
+Returns the current “wall-clock” playing position in seconds.
That is:
-for live content, get a timestamp in seconds of the current position.
-for static content, returns the position from beginning, also in seconds.
+
+for live contents, this is the current position scaled to correspond to a
+live timestamp, in seconds.
+
+
+for non-live contents, returns the position from the absolute beginning time
+of the content, also in seconds. In the absolute majority of cases this will
+be equal to the value returned by getPosition
.
+
Use this method to display the current position to the user.
Example
@@ -410,58 +416,11 @@ Example
console .log(`You're playing ${delta} seconds behind the live content` );
}
-
-
-getVideoDuration
-return value : Number
-Returns the duration of the current video, directly from the video element.
-Example
-const pos = player.getPosition();
-const dur = player.getVideoDuration();
-
-console .log(`current position: ${pos} / ${dur} ` );
-
-
-
-getVolume
-return value : Number
-Current volume of the player, from 0 (no sound) to 1 (maximum sound). 0 if muted
-(different than videoElement.muted).
-Example
-const volume = player.getVolume();
-
-if (volume === 1 ) {
- console .log("You're playing at maximum volume" );
-} else if (volume === 0 ) {
- console .log("You're playing at no volume" );
-} else if (volume > 0.5 ) {
- console .log("You're playing at a high volume" );
-} else {
- console .log("You're playing at a low volume" );
-}
-
-
-
-getError
-return value : Error|null
-Returns the fatal error if it happened. null otherwise.
-See the Player Error documentation for more information.
-Example
-const error = player.getError();
-
-if (!error) {
- console .log("The player did not crash" );
-} else if (error.code === "PIPELINE_LOAD_ERROR" ) {
- console .error("The player crashed due to a failing request" );
-} else {
- console .error(`The player crashed: ${error.code} ` );
-}
-
seekTo
arguments : Object|Number
-Seek in the current content.
+Seek in the current content (i.e. change the current position).
The argument can be an object with a single Number
property, either:
@@ -478,7 +437,7 @@ seekTo
The argument can also just be a Number
property, which will have the same
effect than the position
property (absolute position).
-Example
+Examples
player.seekTo({ position : 54 });
@@ -494,309 +453,359 @@ Example
player.seekTo({ wallClockTime : Date .now() / 1000 });
-
-
-isLive
-return value : Boolean
-Returns true
if the content is a “live” content (e.g. a live TV Channel).
-false
otherwise.
-Also false
if no content is loaded yet.
+
+
+getMinimumPosition
+return value : Number|null
+The minimum seekable player position. null
if no content is loaded.
+This is useful for live contents, where server-side buffer size are often not
+infinite. This method allows thus to seek at the earliest possible time.
+As the given position is the absolute minimum position, you might add a security
+margin (like a few seconds) when seeking to this position in a live content.
+Not doing so could led to the player being behind the minimum position after
+some time, and thus unable to continue playing.
+For VoD contents, as the minimum position normally don’t change, seeking at the
+minimum position should not cause any issue.
Example
-if (player.isLive()) {
- console .log("We're playing a live content" );
-}
+
+player.seekTo({ position : player.getMinimumPosition() + 5 });
-
-
-getUrl
-return value : string|undefined
-Returns the URL of the downloaded Manifest .
-In DirectFile mode (see loadVideo
-options ), returns the URL of the content
-being played.
-Returns undefined
if no content is loaded yet.
+
+
+getMaximumPosition
+return value : Number|null
+The maximum seekable player position. null
if no content is loaded.
+This is useful for live contents, where the buffer end updates continously.
+This method allows thus to seek directly at the live edge of the content.
+Please bear in mind that seeking exactly at the maximum position is rarely a
+good idea:
+
+for VoD contents, the playback will end
+for live contents, the player will then need to wait until it can build
+enough buffer.
+
+As such, we advise to remove a few seconds from that position when seeking.
Example
-const url = player.getUrl();
-if (url) {
- console .log("We are playing the following content:" , url);
+
+player.seekTo({
+ position : player.getMaximumPosition() - 5
+});
+
+
+
+getVideoDuration
+return value : Number
+Returns the duration of the current video as taken from the video element.
+⚠️ This duration is in fact the maximum position possible for the
+content. As such, for contents not starting at 0
, this value will not be equal
+to the difference between the maximum and minimum possible position, as would
+normally be expected from a property named “duration”.
+Example
+const pos = player.getPosition();
+const dur = player.getVideoDuration();
+
+console .log(`current position: ${pos} / ${dur} ` );
+
+
+
+getError
+return value : Error|null
+Returns the current “fatal error” if one happenned for the last loaded content.
+Returns null
otherwise.
+A “fatal error” is an error which led the current loading/loaded content to
+completely stop.
+Such errors are usually also sent through the "error"
event when they happen.
+See the Player Error documentation for more information.
+Example
+const error = player.getError();
+
+if (!error) {
+ console .log("The player did not crash" );
+} else if (error.code === "PIPELINE_LOAD_ERROR" ) {
+ console .error("The player crashed due to a failing request" );
+} else {
+ console .error(`The player crashed: ${error.code} ` );
}
-
-
-getAvailableVideoBitrates
-return value : Array.<Number>
-The different bitrates available for the current video
-Adaptation , in bits per seconds.
-In DirectFile mode (see loadVideo
-options ), returns an empty Array.
+
+
+getVideoElement
+return value : HTMLMediaElement
+Returns the media element used by the RxPlayer.
+You’re not encouraged to use its APIs as they can enter in conflict with the
+RxPlayer’s API.
+Despite its name, this method can also return an audio element if the RxPlayer
+was instantiated with one.
Example
-const videoBitrates = player.getAvailableVideoBitrates();
-if (videoBitrates.length) {
- console .log(
- "The current video is available in the following bitrates" ,
- videoBitrates.join(", " )
- );
+const videoElement = player.getVideoElement();
+videoElement.className = "my-video-element" ;
+
+
+
+dispose
+Free the ressources used by the player.
+You can call this method if you know you won’t need the RxPlayer anymore.
+⚠️ The player won’t work correctly after calling this method.
+
+
+Speed control
+The following methods allows to update the current speed of playback (also
+called the “playback rate”).
+
+
+setPlaybackRate
+arguments : Number
+Updates the current playback rate.
+Setting that value to 1
reset the playback rate to its “normal” rythm.
+Setting it to 2
allows to play at a speed multiplied by 2 relatively to
+regular playback.
+Setting it to 0.5
allows to play at half the speed relatively to regular
+playback.
+etc.
+Example
+
+player.setPlaybackRate(3 );
+
+
+
+getPlaybackRate
+return value : Number
+Returns the current video playback rate. 1
for normal playback, 2
when
+playing at double the speed, etc.
+Example
+const currentPlaybackRate = player.getPlaybackRate();
+console .log(`Playing at a x${currentPlaybackRate} } speed` );
+
+
+
+Volume control
+Those methods allows to have control over the current audio volume of playing
+contents.
+
+
+setVolume
+arguments : Number
+Set the current volume, from 0 (no sound) to 1 (the maximum sound level).
+Note that the volume set here is persisted even when loading another content.
+As such, this method can also be called when no content is currently playing.
+Example
+
+player.setVolume(1 );
+
+
+
+getVolume
+return value : Number
+Current volume of the player, from 0 (no sound) to 1 (maximum sound).
+0 if muted through the mute
API.
+As the volume is not dependent on a single content (it is persistent), this
+method can also be called when no content is playing.
+Example
+const volume = player.getVolume();
+
+if (volume === 1 ) {
+ console .log("You're playing at maximum volume" );
+} else if (volume === 0 ) {
+ console .log("You're playing at no volume" );
+} else if (volume > 0.5 ) {
+ console .log("You're playing at a high volume" );
+} else {
+ console .log("You're playing at a low volume" );
}
-
-
-getAvailableAudioBitrates
-return value : Array.<Number>
-The different bitrates available for the current audio
-Adaptation , in bits per seconds.
-In DirectFile mode (see loadVideo
-options ), returns an empty Array.
+
+
+mute
+Mute the volume.
+Basically set the volume to 0 while keeping in memory the previous volume to
+reset it at the next unMute
call.
+As the volume is not dependent on a single content (it is persistent), this
+method can also be called when no content is playing.
Example
-const audioBitrates = player.getAvailableAudioBitrates();
-if (audioBitrates.length) {
- console .log(
- "The current audio is available in the following bitrates" ,
- audioBitrates.join(", " )
- );
+
+player.mute();
+
+
+
+unMute
+When muted, restore the volume to the one previous to the last mute
call.
+When the volume is already superior to 0
, this call won’t do anything.
+As the volume is not dependent on a single content (it is persistent), this
+method can also be called when no content is playing.
+Example
+
+player.mute();
+
+
+player.unMute();
+
+
+
+isMute
+returns : Boolean
+Returns true if the volume is set to 0
.
+Example
+if (player.isMute()) {
+ console .log("The content plays with no sound." );
}
-
-
-getVideoBitrate
-return value : Number|undefined
-Returns the video bitrate of the last downloaded video segment, in bits per
-seconds.
-Returns undefined
if no content is loaded.
-In DirectFile mode (see loadVideo
-options ), returns undefined
.
-
-
-getAudioBitrate
-return value : Number|undefined
-Returns the audio bitrate of the last downloaded audio segment, in bits per
-seconds.
-Returns undefined
if no content is loaded.
-In DirectFile mode (see loadVideo
-options ), returns undefined
.
-
-
-getMaxVideoBitrate
-return value : Number|undefined
-Returns the maximum set video bitrate to which switching is possible, in bits
-per seconds.
-This only affects adaptive strategies (you can bypass this limit by calling
-setVideoBitrate
), and is set to Infinity
when no limit has been set.
-
-
-getMaxAudioBitrate
-return value : Number"undefined
-Returns the maximum set audio bitrate to which switching is possible, in bits
-per seconds.
-This only affects adaptive strategies (you can bypass this limit by calling
-setAudioBitrate
), and is set to Infinity
when no limit has been set.
-
-
-setVideoBitrate
-arguments : Number
-Force the current video track to be of a certain bitrate.
-If an video Representation (in the current video
-Adaptation ) is found with the exact same bitrate, this
-Representation will be set.
-If no video Representation is found with the exact same bitrate, either:
+
+
+Track selection
+The following methods allows to choose the right video audio or text track and
+to obtain information about the currently playing tracks.
+
+
+getAudioTrack
+returns : Object|null|undefined
+Get information about the audio track currently set.
+null
if no audio track is enabled right now.
+If an audio track is set and information about it is known, this method will
+return an object with the following properties:
-the video Representation immediately inferior to it will be chosen instead
-(the closest inferior)
+id
(Number|string
): The id used to identify this track. No other
+audio track for the same Period will have the
+same id
.
+This can be useful when setting the track through the setAudioTrack
+method.
+
+
+language
(string
): The language the audio track is in, as set in the
+Manifest .
+
+
+normalized
(string
): An attempt to translate the language
+property into an ISO 639-3 language code (for now only support translations
+from ISO 639-1 and ISO 639-3 language codes). If the translation attempt
+fails (no corresponding ISO 639-3 language code is found), it will equal the
+value of language
+
+
+audioDescription
(Boolean
): Whether the track is an audio
+description (for the visually impaired or not).
-if no video Representation has a bitrate lower than that value, the video
-Representation with the lowest bitrate will be chosen instead.
+dub
(Boolean|undefined
): If set to true
, this audio track is a
+“dub”, meaning it was recorded in another language than the original.
+If set to false
, we know that this audio track is in an original language.
+This property is undefined
if we do not known whether it is in an original
+language.
-Set to -1
to deactivate (and thus re-activate adaptive streaming for video
-tracks).
-When active (called with a positive value), adaptive streaming for video tracks
-will be disabled to stay in the chosen Representation.
-You can use getAvailableVideoBitrates
to get the list of available bitrates
-you can set on the current content.
-Note that the value set is persistent between loadVideo
calls.
-As such, this method can also be called when no content is playing (the same
-rules apply for future contents).
-
-⚠️ This option will have no effect for contents loaded in DirectFile
-mode (see loadVideo options ).
-
-
-
-setAudioBitrate
-arguments : Number
-Force the current audio track to be of a certain bitrate.
-If an audio Representation (in the current audio
-Adaptation ) is found with the exact same bitrate, this
-Representation will be set.
-If no audio Representation is found with the exact same bitrate, either:
+undefined
if no audio content has been loaded yet or if its information is
+unknown.
+–
+Note for multi-Period contents:
+This method will only return the chosen audio track for the
+Period that is currently playing.
+__
+In DirectFile mode
+(see loadVideo options ), if there is
+no audio tracks API in the browser, this method will return undefined
.
+
+
+getTextTrack
+returns : Object|null|undefined
+Get information about the text track currently set.
+null
if no audio track is enabled right now.
+If a text track is set and information about it is known, this method will
+return an object with the following properties:
-the audio Representation immediately inferior to it will be chosen instead
-(the closest inferior)
+id
(Number|string
): The id used to identify this track. No other
+text track for the same Period will have the same
+id
.
+This can be useful when setting the track through the setTextTrack
method.
+
+
+language
(string
): The language the text track is in, as set in the
+Manifest .
+
+
+normalized
(string
): An attempt to translate the language
+property into an ISO 639-3 language code (for now only support translations
+from ISO 639-1 and ISO 639-3 language codes). If the translation attempt
+fails (no corresponding ISO 639-3 language code is found), it will equal the
+value of language
-if no audio Representation has a bitrate lower than that value, the audio
-Representation with the lowest bitrate will be chosen instead.
+closedCaption
(Boolean
): Whether the track is specially adapted for
+the hard of hearing or not.
-Set to -1
to deactivate (and thus re-activate adaptive streaming for audio
-tracks).
-When active (called with a positive value), adaptive streaming for audio tracks
-will be disabled to stay in the chosen Representation.
-You can use getAvailableAudioBitrates
to get the list of available bitrates
-you can set on the current content.
-Note that the value set is persistent between loadVideo
calls.
-As such, this method can also be called when no content is playing (the same
-rules apply for future contents).
-
-⚠️ This option will have no effect for contents loaded in DirectFile
-mode (see loadVideo options ).
-
-
-
-getManualVideoBitrate
-arguments : Number
-Get the last video bitrate manually set. Either via setVideoBitrate
or via
-the initialVideoBitrate
constructor option.
-This value can be different than the one returned by getVideoBitrate
:
+undefined
if no text content has been loaded yet or if its information is
+unknown.
+–
+Note for multi-Period contents:
+This method will only return the chosen text track for the
+Period that is currently playing.
+__
+In DirectFile mode
+(see loadVideo options ), if there is
+no text tracks API in the browser, this method will return undefined
.
+
+
+getVideoTrack
+returns : Object|null|undefined
+Get information about the video track currently set.
+null
if no video track is enabled right now.
+If a video track is set and information about it is known, this method will
+return an object with the following properties:
-getManualVideoBitrate
returns the last bitrate set manually by the user
-getVideoBitrate
returns the actual bitrate of the current video track
-
--1
when no video bitrate is forced.
-
-
-getManualAudioBitrate
-arguments : Number
-Get the last audio bitrate manually set. Either via setAudioBitrate
or via
-the initialAudioBitrate
constructor option.
-This value can be different than the one returned by getAudioBitrate
:
+
+id
(Number|string
): The id used to identify this track. No other
+video track for the same Period will have the same
+id
.
+This can be useful when setting the track through the setVideoTrack
+method.
+
+
+representations
(Array.<Object>
):
+Representations of this video track, with
+attributes:
-getManualAudioBitrate
returns the last bitrate set manually by the user
-getAudioBitrate
returns the actual bitrate of the current audio track
-
--1
when no audio bitrate is forced.
-
-
-setMaxVideoBitrate
-arguments : Number
-Set the maximum video bitrate reachable through adaptive streaming. The player
-will never automatically switch to a video
-Representation with a higher bitrate.
-This limit can be removed by setting it to Infinity
:
-
-player.setMaxVideoBitrate(Infinity );
-
-This only affects adaptive strategies (you can bypass this limit by calling
-setVideoBitrate
).
-
-⚠️ This option will have no effect for contents loaded in DirectFile
-mode (see loadVideo options ).
-
-
-
-setMaxAudioBitrate
-arguments : Number
-Set the maximum audio bitrate reachable through adaptive streaming. The player
-will never automatically switch to a audio
-Representation with a higher bitrate.
-This limit can be removed by setting it to Infinity
:
-
-player.setMaxAudioBitrate(Infinity );
-
-This only affects adaptive strategies (you can bypass this limit by calling
-setAudioBitrate
).
-
-⚠️ This option will have no effect for contents loaded in DirectFile
-mode (see loadVideo options ).
-
-
-
-setWantedBufferAhead
-arguments : Number
-Set the buffering goal, as a duration ahead of the current position, in seconds.
-Once this size of buffer reached, the player won’t try to download new video
-segments anymore.
-
-⚠️ This option will have no effect for contents loaded in DirectFile
-mode (see loadVideo options ).
-
-
-
-getWantedBufferAhead
-return value : Number
-defaults : 30
-returns the buffering goal, as a duration ahead of the current position, in
+
+id
(string
): The id used to identify this Representation.
+No other Representation from this track will have the same id
.
+
+
+bitrate
(Number
): The bitrate of this Representation, in bits per
seconds.
-
-
-setMaxBufferBehind
-arguments : Number
-Set the maximum kept past buffer, in seconds.
-Everything before that limit (currentPosition - maxBufferBehind
) will be
-automatically garbage collected.
-This feature is not necessary as the browser is already supposed to deallocate
-memory from old segments if/when the memory is scarce.
-However on some custom targets, or just to better control the memory imprint
-of the player, you might want to set this limit. You can set it to
-Infinity
to remove any limit and just let the browser do this job.
-
-⚠️ This option will have no effect for contents loaded in DirectFile
-mode (see loadVideo options ).
-
-
-
-getMaxBufferBehind
-return value : Number
-defaults : Infinity
-Returns the maximum kept past buffer, in seconds.
-
-
-setMaxBufferAhead
-arguments : Number
-Set the maximum kept buffer ahead of the current position, in seconds.
-Everything superior to that limit (currentPosition + maxBufferAhead
) will
-be automatically garbage collected. This feature is not necessary as
-the browser is already supposed to deallocate memory from old segments if/when
-the memory is scarce.
-However on some custom targets, or just to better control the memory imprint of
-the player, you might want to set this limit. You can set it to Infinity
to
-remove any limit and just let the browser do this job.
-The minimum value between this one and the one returned by
-getWantedBufferAhead
will be considered when downloading new segments.
-⚠️ Bear in mind that a too-low configuration there (e.g. inferior to
-10
) might prevent the browser to play the content at all.
-
-⚠️ This option will have no effect for contents loaded in DirectFile
-mode (see loadVideo options ).
-
-
-
-getMaxBufferAhead
-return value : Number
-defaults : Infinity
-Returns the maximum kept buffer ahead of the current position, in seconds.
-
-
-setVolume
-arguments : Number
-Set the new volume, from 0 (no sound) to 1 (the maximum sound level).
-
-
-mute
-Cut the volume. Basically set the volume to 0 while keeping in memory the
-previous volume.
-
-
-unMute
-Restore the volume when it has been muted, to the one previous the mute
-call.
-
-
-isMute
-returns : Boolean
-Returns true if the volume is muted i.e., set to 0.
+
+
+width
(Number|undefined
): The width of video, in pixels.
+
+
+height
(Number|undefined
): The height of video, in pixels.
+
+
+codec
(string|undefined
): The codec given in standard MIME type
+format.
+
+
+frameRate
(string|undefined
): The video frame rate.
+
+
+
+
+signInterpreted
(Boolean|undefined
): If set to true
, the track is
+known to contain an interpretation in sign language.
+If set to false
, the track is known to not contain that type of content.
+If not set or set to undefined we don’t know whether that video track
+contains an interpretation in sign language.
+
+
+undefined
if no video content has been loaded yet or if its information is
+unknown.
+–
+Note for multi-Period contents:
+This method will only return the chosen video track for the
+Period that is currently playing.
+–
+In DirectFile mode
+(see loadVideo options ), if there is
+no video tracks API in the browser, this method will return undefined
.
getAvailableAudioTracks
@@ -806,7 +815,7 @@ getAvailableAudioTracks
active
(Boolean
): Whether the track is the one currently active or
-not.
+not. Only maximum one audio track can be active at a time.
id
(string
): The id used to identify the track. Use it for
@@ -835,9 +844,14 @@
getAvailableAudioTracks
language.
-In DirectFile mode
-(see loadVideo options ), if there are
-no supported tracks in the file or no track management API : returns an empty
+
–
+Note for multi-Period contents:
+This method will only return the available tracks of the
+Period that is currently playing.
+–
+In DirectFile mode (see loadVideo
+options ), if there are no supported
+tracks in the file or no track management API this method will return an empty
Array.
@@ -870,9 +884,14 @@ getAvailableTextTracks
not.
-In DirectFile mode
-(see loadVideo options ), if there are
-no supported tracks in the file or no track management API : returns an empty
+
–
+Note for multi-Period contents:
+This method will only return the available tracks of the
+Period that is currently playing.
+–
+In DirectFile mode (see loadVideo
+options ), if there are no supported
+tracks in the file or no track management API this method will return an empty
Array.
@@ -916,345 +935,869 @@ getAvailableVideoTracks
+
+signInterpreted
(Boolean|undefined
): If set to true
, the track is
+known to contain an interpretation in sign language.
+If set to false
, the track is known to not contain that type of content.
+If not set or set to undefined we don’t know whether that video track
+contains an interpretation in sign language.
+
-In DirectFile mode
-(see loadVideo options ), if there are
-no supported tracks in the file or no track management API : returns an empty
+
–
+Note for multi-Period contents:
+This method will only return the available tracks of the
+Period that is currently playing.
+–
+In DirectFile mode (see loadVideo
+options ), if there are no supported
+tracks in the file or no track management API this method will return an empty
Array.
-
-
-getAudioTrack
-returns : Object|null|undefined
-Get the audio track currently set. null
if no audio track is enabled right
-now.
-The track is an object with the following properties:
+
+
+setAudioTrack
+arguments : string|Number
+Change the current audio track.
+The argument to this method is the wanted track’s id
property. This id
can
+for example be obtained on the corresponding track object returned by the
+getAvailableAudioTracks
method.
+–
+Note for multi-Period contents:
+This method will only have an effect on the Period that is
+currently playing.
+If you want to update the track for other Periods as well, you might want to
+either:
+–
+⚠️ If used on Safari, in DirectFile mode, the track change may change
+the track on other track type (e.g. changing video track may change subtitle
+track too).
+This has two potential reasons :
+
+The HLS defines variants, groups of tracks that may be read together
+Safari may decide to enable a track for accessibility or user language
+convenience (e.g. Safari may switch subtitle to your OS language if you pick
+another audio language)
+You can know if another track has changed by listening to the corresponding
+events that the tracks have changed.
+
+
+
+setTextTrack
+arguments : string
+Change the current text (subtitles) track.
+The argument to this method is the wanted track’s id
property. This id
can
+for example be obtained on the corresponding track object returned by the
+getAvailableTextTracks
method.
+–
+Note for multi-Period contents:
+This method will only have an effect on the Period that is
+currently playing.
+If you want to update the track for other Periods as well, you might want to
+either:
+
+update the preferred text tracks through the
+setPreferredTextTracks method.
+update the current text track once a "periodChange"
event has been
+received.
+
+–
+⚠️ If used on Safari, in DirectFile mode, the track change may change
+the track on other track type (e.g. changing video track may change subtitle
+track too).
+This has two potential reasons :
+
+The HLS defines variants, groups of tracks that may be read together
+Safari may decide to enable a track for accessibility or user language
+convenience (e.g. Safari may switch subtitle to your OS language if you pick
+another audio language)
+You can know if another track has changed by listening to the corresponding
+events that the tracks have changed.
+
+
+
+disableTextTrack
+Disable the current text track, if one.
+After calling that method, no subtitles track will be displayed until
+setTextTrack
is called.
+–
+Note for multi-Period contents:
+This method will only have an effect on the Period that is
+currently playing.
+If you want to disable the text track for other Periods as well, you might want
+to either:
+
+update the preferred text tracks through the
+setPreferredTextTracks method to include
+null
(which meaning that you want no text track).
+call disableTextTrack
once a "periodChange"
event has been received.
+
+
+
+setVideoTrack
+arguments : string|Number
+Change the current video track.
+The argument to this method is the wanted track’s id
property. This id
can
+for example be obtained on the corresponding track object returned by the
+getAvailableAudioTracks
method.
+Setting a new video track when a previous one was already playing can lead the
+rx-player to “reload” this content.
+During this period of time:
+
+the player will have the state RELOADING
+Multiple APIs linked to the current content might not work.
+Most notably:
+
+play
will not work
+pause
will not work
+seekTo
will not work
+getPosition
will return 0
+getWallClockTime
will return 0
+getVideoDuration
will return NaN
+getAvailableAudioTracks
will return an empty array
+getAvailableTextTracks
will return an empty array
+getAvailableVideoTracks
will return an empty array
+getTextTrack
will return null
+getAudioTrack
will return null
+setAudioTrack
will throw
+setTextTrack
will throw
+
+
+–
+Note for multi-Period contents:
+This method will only have an effect on the Period that is
+currently playing.
+If you want to update the track for other Periods as well, you might want to
+either:
+
+update the preferred video tracks through the
+setPreferredVideoTracks method.
+update the current video track once a "periodChange"
event has been
+received.
+
+–
+⚠️ This option will have no effect in DirectFile mode
+(see loadVideo options ) when either :
+
+No video track API is supported on the current browser
+The media file tracks are not supported on the browser
+
+
+
+disableVideoTrack
+return value : void
+Disable the current video track, if one.
+Might enter in RELOADING
state for a short period after calling this API.
+–
+Note for multi-Period contents:
+This method will only have an effect on the Period that is
+currently playing.
+If you want to disable the video track for other Periods as well, you might want
+to either:
+
+update the preferred video tracks through the
+setPreferredVideoTracks method to include
+null
(which meaning that you want no video track).
+call disableVideoTrack
once a "periodChange"
event has been received.
+
+–
+⚠️ This option may have no effect in DirectFile mode
+(see loadVideo options ).
+The directfile mode is a special case here because when in it, the RxPlayer
+depends for track selection on the corresponding HTML
+standard as implemented by
+the different browsers.
+Though this standard says nothing about not being able to disable the video
+track (or to stay more in line with their terms: to not select any video track),
+no browser implementation actually seem to be able to do it, even when the
+corresponding browser APIs show that no video track is currently selected.
+This might be a bug on their parts.
+Due to this fact, we do not recommend using this API in directfile mode for
+now. You might even receive a reassuring videoTrackChange
event (with a null
+payload) while the video track is still actually active.
+
+
+setPreferredAudioTracks
+arguments : Array.<Object>
+Allows the RxPlayer to choose an initial audio track, based on language
+preferences, codec preferences or both.
+It is defined as an array of objects, each object describing constraints a
+track should respect.
+If the first object - defining the first set of constraints - cannot be
+respected under the currently available audio tracks, the RxPlayer will skip
+it and check with the second object and so on.
+As such, this array should be sorted by order of preference: from the most
+wanted constraints to the least.
+Here is all the possible constraints you can set in any one of those objects
+(note that all properties are optional here, only those set will have an effect
+on which tracks will be filtered):
+{
+ language : "fra" ,
+
+
+
+
+
+ audioDescription : false
+
+
+
+
+
+ codec : {
+
+
+ test : /ec-3/ ,
+
+ all : true ,
+
+
+
+
+
+
+ }
+}
+
+This logic is ran each time a new Period
with audio tracks is loaded by the
+RxPlayer. This means at the start of the content, but also when [pre-]loading a
+new DASH Period
or a new MetaPlaylist content
.
+Please note that those preferences won’t be re-applied once the logic was
+already run for a given Period
.
+Simply put, once set this preference will be applied to all contents but:
+
-language
(string
): The language the audio track is in, as set in the
-Manifest .
+the current Period being played (or the current loaded content, in the case
+of single-Period contents such as in Smooth streaming).
+In that case, the current audio preference will stay in place.
-normalized
(string
): An attempt to translate the language
-property into an ISO 639-3 language code (for now only support translations
-from ISO 639-1 and ISO 639-3 language codes). If the translation attempt
-fails (no corresponding ISO 639-3 language code is found), it will equal the
-value of language
+the Periods which have already been loaded in the current content.
+Those will keep their last set audio preferences (e.g. the preferred audio
+tracks at the time they were first loaded).
+
+To update the current audio track in those cases, you should use the
+setAudioTrack
method once they are currently playing.
+Examples
+Let’s imagine that you prefer to have french or italian over all other audio
+languages. If not found, you want to fallback to english:
+player.setPreferredAudioTracks([
+ { language : "fra" , audioDescription : false },
+ { language : "ita" , audioDescription : false },
+ { language : "eng" , audioDescription : false }
+])
+
+Now let’s imagine that you want to have in priority a track that contain at
+least one profile in Dolby Digital Plus (ec-3 codec) without caring about the
+language:
+player.setPreferredAudioTracks([ { codec : { all : false , test : /ec-3/ } ]);
+
+At last, let’s combine both examples by preferring french over itialian, italian
+over english while preferring it to be in Dolby Digital Plus:
+
+player.setPreferredAudioTracks([
+ {
+ language : "fra" ,
+ audioDescription : false ,
+ codec : { all : false , test : /ec-3/ }
+ },
+
+
+ { language : "fra" , audioDescription : false },
+
+ {
+ language : "ita" ,
+ audioDescription : false ,
+ codec : { all : false , test : /ec-3/ }
+ },
+ { language : "ita" , audioDescription : false },
+
+ {
+ language : "eng" ,
+ audioDescription : false ,
+ codec : { all : false , test : /ec-3/ }
+ },
+ { language : "eng" , audioDescription : false }
+]);
+
+–
+⚠️ This option will have no effect in DirectFile mode
+(see loadVideo options ) when either :
+
+No audio track API is supported on the current browser
+The media file tracks are not supported on the browser
+
+
+
+getPreferredAudioTracks
+return value : Array.<Object>
+Returns the current list of preferred audio tracks - by order of preference.
+This returns the data in the same format that it was given to either the
+preferredAudioTracks
constructor option or the last setPreferredAudioTracks
+if it was called.
+It will return an empty Array if none of those two APIs were used until now.
+
+
+setPreferredTextTracks
+arguments : Array.<Object|null>
+Update the text track (or subtitles) preferences at any time.
+This method takes an array of objects describing the languages wanted:
+{
+ language : "fra" ,
+
+ closedCaption : false
+
+}
+
+All elements in that Array should be set in preference order: from the most
+preferred to the least preferred. You can set null
for no subtitles.
+When encountering a new Period or a new content, the RxPlayer will then try to
+choose its text track by comparing what is available with your current
+preferences (i.e. if the most preferred is not available, it will look if the
+second one is etc.).
+Please note that those preferences will only apply either to the next loaded
+content or to the next newly encountered Period.
+Simply put, once set this preference will be applied to all contents but:
+
-audioDescription
(Boolean
): Whether the track is an audio
-description (for the visually impaired or not).
+the current Period being played (or the current loaded content, in the case
+of Smooth streaming). In that case, the current text track preference will
+stay in place.
-dub
(Boolean|undefined
): If set to true
, this audio track is a
-“dub”, meaning it was recorded in another language than the original.
-If set to false
, we know that this audio track is in an original language.
-This property is undefined
if we do not known whether it is in an original
-language.
+the Periods which have already been played in the current loaded content.
+Those will keep the last set text track preference at the time it was
+played.
-undefined
if no content has been loaded yet.
-In DirectFile mode
-(see loadVideo options ), if there is
-no audio tracks API in the browser, return undefined
.
-
-
-getTextTrack
-returns : Object|null
-Get the audio track currently set. null
if no text track is enabled right
-now.
-The track is an object with the following properties:
+To update the current text track in those cases, you should use the
+setTextTrack
method.
+Example
+Let’s imagine that you prefer to have french or italian subtitles.If not found,
+you want no subtitles at all.
+You will thus call setPreferredTextTracks
that way.
+player.setPreferredTextTracks([
+ { language : "fra" , closedCaption : false },
+ { language : "ita" , closedCaption : false },
+ null
+])
+
+–
+⚠️ This option will have no effect in DirectFile mode
+(see loadVideo options ) when either :
+
+No text track API is supported on the current browser
+The media file tracks are not supported on the browser
+
+
+
+getPreferredTextTracks
+return value : Array.<Object|null>
+Returns the current list of preferred text tracks - by order of preference.
+This returns the data in the same format that it was given to either the
+preferredTextTracks
constructor option or the last setPreferredTextTracks
if
+it was called:
+{
+ language : "fra" ,
+
+ closedCaption : false
+
+}
+
+
+
+Bitrate selection
+The following methods allows to choose a given bitrate for audio or video
+content. It can also enable or disable an adaptive bitrate logic or influence
+it.
+
+
+getAvailableVideoBitrates
+return value : Array.<Number>
+The different bitrates available for the current video track in bits per
+seconds.
+–
+Note for multi-Period contents:
+This method will only return the available video bitrates of the
+Period that is currently playing.
+–
+In DirectFile mode (see loadVideo
+options ), returns an empty Array.
+Example
+const videoBitrates = player.getAvailableVideoBitrates();
+if (videoBitrates.length) {
+ console .log(
+ "The current video is available in the following bitrates" ,
+ videoBitrates.join(", " )
+ );
+}
+
+
+
+getAvailableAudioBitrates
+return value : Array.<Number>
+The different bitrates available for the current audio track in bits per
+seconds.
+–
+Note for multi-Period contents:
+This method will only return the available audio bitrates of the
+Period that is currently playing.
+–
+In DirectFile mode (see loadVideo
+options ), returns an empty Array.
+Example
+const audioBitrates = player.getAvailableAudioBitrates();
+if (audioBitrates.length) {
+ console .log(
+ "The current audio is available in the following bitrates" ,
+ audioBitrates.join(", " )
+ );
+}
+
+
+
+getVideoBitrate
+return value : Number|undefined
+Returns the bitrate of the video quality currently set, in bits per second.
+Returns undefined
if no content is loaded.
+–
+Note for multi-Period contents:
+This method will only return the chosen video bitrate for the
+Period that is currently playing.
+–
+In DirectFile mode (see loadVideo
+options ), returns undefined
.
+
+
+getAudioBitrate
+return value : Number|undefined
+Returns the bitrate of the audio quality currently set, in bits per second.
+Returns undefined
if no content is loaded.
+–
+Note for multi-Period contents:
+This method will only return the chosen audio bitrate for the
+Period that is currently playing.
+–
+In DirectFile mode (see loadVideo
+options ), returns undefined
.
+
+
+setMaxVideoBitrate
+arguments : Number
+Set the maximum video bitrate reachable through adaptive streaming. The player
+will never automatically switch to a video quality with a higher bitrate.
+This limit can be removed by setting it to Infinity
:
+
+player.setMaxVideoBitrate(Infinity );
+
+The effect of this method is persisted from content to content. As such, it can
+even be called when no content is currently loaded.
+Note that this only affects adaptive strategies (you can bypass this limit by
+calling setVideoBitrate
).
+–
+⚠️ This option will have no effect for contents loaded in DirectFile
+mode (see loadVideo options ).
+
+
+setMaxAudioBitrate
+arguments : Number
+Set the maximum audio bitrate reachable through adaptive streaming. The player
+will never automatically switch to a audio
+Representation with a higher bitrate.
+This limit can be removed by setting it to Infinity
:
+
+player.setMaxAudioBitrate(Infinity );
+
+The effect of this method is persisted from content to content. As such, it can
+even be called when no content is currently loaded.
+Note that this only affects adaptive strategies (you can bypass this limit by
+calling setAudioBitrate
).
+–
+⚠️ This option will have no effect for contents loaded in DirectFile
+mode (see loadVideo options ).
+
+
+getMaxVideoBitrate
+return value : Number
+Returns the maximum video bitrate reachable through adaptive streaming, in bits
+per seconds.
+This limit can be updated by calling the
+setMaxVideoBitrate method.
+This limit only affects adaptive strategies (you can bypass this limit by
+calling setVideoBitrate
), and is set to Infinity
when no limit has been
+set.
+
+
+getMaxAudioBitrate
+return value : Number
+Returns the maximum audio bitrate reachable through adaptive streaming, in bits
+per seconds.
+This limit can be updated by calling the
+setMaxAudioBitrate method.
+This limit only affects adaptive strategies (you can bypass this limit by
+calling setAudioBitrate
), and is set to Infinity
when no limit has been
+set.
+
+
+setVideoBitrate
+arguments : Number
+Force the current video track to be of a certain bitrate.
+If an video quality in the current track is found with the exact same bitrate,
+this quality will be set.
+If no video quality is found with the exact same bitrate, either:
-id
(Number|string
): The id used to identify the track. Use it for
-setting the track via setTextTrack
.
-
-
-language
(string
): The language the text track is in, as set in the
-Manifest .
-
-
-normalized
(string
): An attempt to translate the language
-property into an ISO 639-3 language code (for now only support translations
-from ISO 639-1 and ISO 639-3 language codes). If the translation attempt
-fails (no corresponding ISO 639-3 language code is found), it will equal the
-value of language
+the video quality with the closest bitrate inferior to that value will be
+chosen.
-closedCaption
(Boolean
): Whether the track is specially adapted for
-the hard of hearing or not.
+if no video quality has a bitrate lower than that value, the video
+quality with the lowest bitrate will be chosen instead.
-undefined
if no content has been loaded yet.
-In DirectFile mode
-(see loadVideo options ), if there is
-no text tracks API in the browser, return undefined
.
-
-
-getVideoTrack
-returns : Object|null|undefined
-Get the video track currently set. null
if no video track is enabled right
-now.
-The track is an object with the following properties:
-
-
-id
(string
): The id used to identify the track. Use it for setting
-the track via setVideoTrack
.
-
-
-representations
(Array.<Object>
):
-Representations of this video track, with
-attributes:
+By calling this method with an argument set to -1
, this setting will be
+disabled and the RxPlayer will chose the right quality according to its adaptive
+logic.
+You can use getAvailableVideoBitrates
to get the list of available bitrates
+for the current video track.
+Note that the value set is persistent between loadVideo
calls.
+As such, this method can also be called when no content is playing (the same
+rules apply for future contents).
+–
+⚠️ This option will have no effect for contents loaded in DirectFile
+mode (see loadVideo options ).
+
+
+setAudioBitrate
+arguments : Number
+Force the current audio track to be of a certain bitrate.
+If an audio quality in the current track is found with the exact same bitrate,
+this quality will be set.
+If no audio quality is found with the exact same bitrate, either:
-id
(string
): The id used to identify this Representation.
-
-
-bitrate
(Number
): The bitrate of this Representation, in bits per
-seconds.
-
-
-width
(Number|undefined
): The width of video, in pixels.
-
-
-height
(Number|undefined
): The height of video, in pixels.
-
-
-codec
(string|undefined
): The codec given in standard MIME type
-format.
+the audio quality with the closest bitrate inferior to that value will be
+chosen.
-frameRate
(string|undefined
): The video framerate.
-
-
+if no audio quality has a bitrate lower than that value, the audio
+quality with the lowest bitrate will be chosen instead.
-undefined
if no content has been loaded yet.
-In DirectFile mode
-(see loadVideo options ), if there is
-no video tracks API in the browser, return undefined
.
-
-
-setAudioTrack
-arguments : string|Number
-Set a new audio track from its id, recuperated from getAvailableAudioTracks
.
-
-⚠️ If used on Safari, in DirectFile mode, the track change may change
-the track on other track type (e.g. changing video track may change subtitle
-track too).
-This has two potential reasons :
+By calling this method with an argument set to -1
, this setting will be
+disabled and the RxPlayer will chose the right quality according to its adaptive
+logic.
+You can use getAvailableAudioBitrates
to get the list of available bitrates
+for the current audio track.
+Note that the value set is persistent between loadVideo
calls.
+As such, this method can also be called when no content is playing (the same
+rules apply for future contents).
+–
+⚠️ This option will have no effect for contents loaded in DirectFile
+mode (see loadVideo options ).
+
+
+getManualVideoBitrate
+arguments : Number
+Get the last video bitrate manually set. Either via setVideoBitrate
or via
+the initialVideoBitrate
constructor option.
+This value can be different than the one returned by getVideoBitrate
:
-The HLS defines variants, groups of tracks that may be read together
-Safari may decide to enable a track for accessibility or user language
-convenience (e.g. Safari may switch subtitle to your OS language if you pick
-another audio language)
-The user may know through the [videoTrackChange]
-(./player_events.md#events-videoTrackChange) event that the track has changed.
+getManualVideoBitrate
returns the last bitrate set manually by the user
+getVideoBitrate
returns the actual bitrate of the current video track
-
-
-
-setTextTrack
-arguments : string
-Set a new text track from its id, recuperated from getAvailableTextTracks
.
-
-⚠️ If used on Safari, in DirectFile mode, the track change may change
-the track on other track type (e.g. changing video track may change subtitle
-track too).
-This has two potential reasons :
+-1
when no video bitrate is forced.
+
+
+getManualAudioBitrate
+arguments : Number
+Get the last audio bitrate manually set. Either via setAudioBitrate
or via
+the initialAudioBitrate
constructor option.
+This value can be different than the one returned by getAudioBitrate
:
-The HLS defines variants, groups of tracks that may be read together
-Safari may decide to enable a track for accessibility or user language
-convenience (e.g. Safari may switch subtitle to your OS language if you pick
-another audio language)
-The user may know through the [audioTrackChange]
-(./player_events.md#events-audioTrackChange) event that the track has changed.
+getManualAudioBitrate
returns the last bitrate set manually by the user
+getAudioBitrate
returns the actual bitrate of the current audio track
-
-
-
-disableTextTrack
-Deactivate the current text track, if one.
-
-
-setVideoTrack
-arguments : string|Number
-Set a new video track from its id, recuperated from getAvailableVideoTracks
.
-Setting a new video track when a previous one was already playing can lead the
-rx-player to “reload” this content.
-During this period of time:
-
-the player will have the state RELOADING
-Multiple APIs linked to the current content might not work.
-Most notably:
+-1
when no audio bitrate is forced.
+
+
+Buffer control
+The methods in this chapter allow to get and set limits on how the current
+buffer can grow.
+
+
+setWantedBufferAhead
+arguments : Number
+Set the buffering goal, as a duration ahead of the current position, in seconds.
+Once this size of buffer reached, the player won’t try to download new segments
+anymore.
+–
+⚠️ This option will have no effect for contents loaded in DirectFile
+mode (see loadVideo options ).
+
+
+getWantedBufferAhead
+return value : Number
+defaults : 30
+returns the buffering goal, as a duration ahead of the current position, in
+seconds.
+
+
+setMaxBufferBehind
+arguments : Number
+Set the maximum kept buffer before the current position, in seconds.
+Everything before that limit (currentPosition - maxBufferBehind
) will be
+automatically garbage collected.
+This feature is not necessary as the browser should by default correctly
+remove old segments from memory if/when the memory is scarce.
+However on some custom targets, or just to better control the memory footprint
+of the player, you might want to set this limit.
+You can set it to Infinity
to remove this limit and just let the browser do
+this job instead.
+–
+⚠️ This option will have no effect for contents loaded in DirectFile
+mode (see loadVideo options ).
+
+
+getMaxBufferBehind
+return value : Number
+defaults : Infinity
+Returns the maximum kept buffer before the current position, in seconds.
+This setting can be updated either by:
-play
will not work
-pause
will not work
-seekTo
will not work
-getPosition
will return 0
-getWallClockTime
will return 0
-getVideoDuration
will return NaN
-getAvailableAudioTracks
will return an empty array
-getAvailableTextTracks
will return an empty array
-getAvailableVideoTracks
will return an empty array
-getTextTrack
will return null
-getAudioTrack
will return null
-setAudioTrack
will throw
-setTextTrack
will throw
+calling the setMaxBufferBehind
method.
+instanciating an RxPlayer with a maxBufferBehind
property set.
-
+
+
+setMaxBufferAhead
+arguments : Number
+Set the maximum kept buffer ahead of the current position, in seconds.
+Everything superior to that limit (currentPosition + maxBufferAhead
) will
+be automatically garbage collected.
+This feature is not necessary as the browser should by default correctly
+remove old segments from memory if/when the memory is scarce.
+However on some custom targets, or just to better control the memory footprint
+of the player, you might want to set this limit.
+You can set it to Infinity
to remove any limit and just let the browser do
+this job instead.
+The minimum value between this one and the one returned by
+getWantedBufferAhead
will be considered when downloading new segments.
+⚠️ Bear in mind that a too-low configuration there (e.g. inferior to
+10
) might prevent the browser to play the content at all.
+–
+⚠️ This option will have no effect for contents loaded in DirectFile
+mode (see loadVideo options ).
+
+
+getMaxBufferAhead
+return value : Number
+defaults : Infinity
+Returns the maximum kept buffer ahead of the current position, in seconds.
+This setting can be updated either by:
+
+calling the setMaxBufferAhead
method.
+instanciating an RxPlayer with a maxBufferAhead
property set.
-
-⚠️ This option will have no effect in DirectFile mode
-(see loadVideo options ) when either :
+
+
+Buffer information
+The methods in this chapter allows to retrieve information about what is
+currently buffered.
+
+
+getVideoLoadedTime
+return value : Number
+Returns in seconds the difference between:
-No audio track API was supported on the current browser
-The media file tracks are not supported on the browser
+the start of the current contiguous loaded range.
+the end of it.
-
-
-
-setPreferredAudioTracks
-arguments : Array.<Object>
-Update the audio language preferences at any time.
-This method takes an array of objects describing the languages wanted:
- {
- language : "fra" ,
-
- audioDescription : false
-
-}
-
-All elements in that Array should be set in preference order: from the most
-preferred to the least preferred.
-When encountering a new Period or a new content, the RxPlayer will then try to
-choose its audio track by comparing what is available with your current
-preferences (i.e. if the most preferred is not available, it will look if the
-second one is etc.).
-Please note that those preferences will only apply either to the next loaded
-content or to the next newly encountered Period.
-Simply put, once set this preference will be applied to all contents but:
+
+
+getVideoPlayedTime
+return value : Number
+Returns in seconds the difference between:
-
-the current Period being played (or the current loaded content, in the case
-of Smooth streaming). In that case, the current audio preference will stay
-in place.
-
-
-the Periods which have already been played in the current loaded content.
-Those will keep the last set audio preference at the time it was played.
-
+the start of the current contiguous loaded range.
+the current time.
+
+
+
+getVideoBufferGap
+return value : Number
+Returns in seconds the difference between:
+
+the current time.
+the end of the current contiguous loaded range.
-To update the current audio track in those cases, you should use the
-setAudioTrack
method.
+
+
+Content information
+The methods documented in this chapter allows to obtain general information
+about the current loaded content.
+
+
+isLive
+return value : Boolean
+Returns true
if the content is a “live” content (e.g. a live TV Channel).
+false
otherwise.
+Also false
if no content is loaded yet.
Example
-Let’s imagine that you prefer to have french or italian over all other audio
-languages. If not found, you want to fallback to english.
-You will thus call setPreferredAudioTracks
that way.
-player.setPreferredAudioTracks([
- { language : "fra" , audioDescription : false },
- { language : "ita" , audioDescription : false },
- { language : "eng" , audioDescription : false }
-])
+if (player.isLive()) {
+ console .log("We're playing a live content" );
+}
-
-⚠️ This option will have no effect in DirectFile mode
-(see loadVideo options ) when either :
-
-No audio track API was supported on the current browser
-The media file tracks are not supported on the browser
-
-
-
-
-getPreferredAudioTracks
-return value : Array.<Object>
-Returns the current list of preferred audio tracks - by order of preference.
-This returns the data in the same format that it was given to either the
-preferredAudioTracks
constructor option or the last setPreferredAudioTracks
-if it was called:
-{
- language : "fra" ,
-
- audioDescription : false
-
+
+
+getUrl
+return value : string|undefined
+Returns the URL of the downloaded Manifest .
+In DirectFile mode (see loadVideo
+options ), returns the URL of the content
+being played.
+Returns undefined
if no content is loaded yet.
+Example
+const url = player.getUrl();
+if (url) {
+ console .log("We are playing the following content:" , url);
}
-
-
-setPreferredTextTracks
+
+
+setPreferredVideoTracks
arguments : Array.<Object|null>
-Update the text track (or subtitles) preferences at any time.
-This method takes an array of objects describing the languages wanted:
+Allows the RxPlayer to choose an initial video track.
+It is defined as an array of objects, each object describing constraints a
+track should respect.
+If the first object - defining the first set of constraints - cannot be
+respected under the currently available video tracks, the RxPlayer will skip
+it and check with the second object and so on.
+As such, this array should be sorted by order of preference: from the most
+wanted constraints to the least.
+When the next encountered constraint is set to null
, the player will simply
+disable the video track. If you want to disable the video track by default,
+you can just set null
as the first element of this array (e.g. [null]
).
+Here is all the possible constraints you can set in any one of those objects
+(note that all properties are optional here, only those set will have an effect
+on which tracks will be filtered):
{
- language : "fra" ,
-
- closedCaption : false
-
+ codec : {
+
+
+ test : /hvc/ ,
+
+ all : true ,
+
+
+
+
+
+
+ }
+ signInterpreted : true ,
+
+
+
+
+
+
}
-All elements in that Array should be set in preference order: from the most
-preferred to the least preferred. You can set null
for no subtitles.
-When encountering a new Period or a new content, the RxPlayer will then try to
-choose its text track by comparing what is available with your current
-preferences (i.e. if the most preferred is not available, it will look if the
-second one is etc.).
-Please note that those preferences will only apply either to the next loaded
-content or to the next newly encountered Period.
+
This logic is ran each time a new Period
with video tracks is loaded by the
+RxPlayer. This means at the start of the content, but also when [pre-]loading a
+new DASH Period
or a new MetaPlaylist content
.
+Please note that those preferences won’t be re-applied once the logic was
+already run for a given Period
.
Simply put, once set this preference will be applied to all contents but:
the current Period being played (or the current loaded content, in the case
-of Smooth streaming). In that case, the current text track preference will
-stay in place.
+of single-Period contents such as in Smooth streaming).
+In that case, the current video track preference will stay in place.
-the Periods which have already been played in the current loaded content.
-Those will keep the last set text track preference at the time it was
-played.
+the Periods which have already been loaded in the current content.
+Those will keep their last set video track preferences (e.g. the preferred
+video tracks at the time they were first loaded).
-To update the current text track in those cases, you should use the
-setTextTrack
method.
-Example
-Let’s imagine that you prefer to have french or italian subtitles.If not found,
-you want no subtitles at all.
-You will thus call setPreferredTextTracks
that way.
-player.setPreferredTextTracks([
- { language : "fra" , closedCaption : false },
- { language : "ita" , closedCaption : false },
- null
-])
+To update the current video track in those cases, you should use the
+setVideoTrack
method once they are currently played.
+Examples
+Let’s imagine that you prefer to have a track which contains only H265
+profiles. You can do:
+player.setPreferredVideoTracks([ { codec : { all : false , test : /^hvc/ } } ]);
+
+With that same constraint, let’s no consider that the current user prefer in any
+case to have a sign language interpretation on screen:
+player.setPreferredVideoTracks([
+
+ {
+ codec : { all : false , test : /^hvc/ }
+ signInterpreted : true ,
+ },
+
+
+ { signInterpreted : true },
+
+
+ { codec : { all : false , test : /^hvc/ } },
+
+
+
+]);
+would thus prefer the video to contain a sign language interpretation.
+We could set both the previous and that new constraint that way:
+
+---
+
+For a totally different example, let's imagine you want to start without any
+video track enabled (e.g. to start in an audio-only mode). To do that, you can
+simply do:
+```js
+player.setPreferredVideoTracks([null]);
⚠️ This option will have no effect in DirectFile mode
(see loadVideo options ) when either :
-No text track API was supported on the current browser
+No video track API is supported on the current browser
The media file tracks are not supported on the browser
-
-
-getPreferredTextTracks
-return value : Array.<Object|null>
-Returns the current list of preferred text tracks - by order of preference.
+
+
+getPreferredVideoTracks
+return value : Array.<Object>
+Returns the current list of preferred video tracks - by order of preference.
This returns the data in the same format that it was given to either the
-preferredTextTracks
constructor option or the last setPreferredTextTracks
if
-it was called:
-{
- language : "fra" ,
-
- closedCaption : false
-
-}
-
+preferredVideoTracks
constructor option or the last setPreferredVideoTracks
+if it was called.
+It will return an empty Array if none of those two APIs were used until now.
+
+
+getCurrentKeySystem
+return value : string|undefined
+Returns the type of keySystem used for DRM-protected contents.
+
+
+Deprecated
+The following methods are deprecated. They are still supported but we advise
+users to not use those as they might become not supported in the future.
getManifest
+–
+⚠️ This method is deprecated, it will disappear in the next major
+release v4.0.0
(see Deprecated APIs ).
+–
return value : Manifest|null
Returns the current loaded Manifest if one.
The Manifest object structure is relatively complex and is described in the
@@ -1266,6 +1809,10 @@
getManifest
getCurrentAdaptations
+–
+⚠️ This method is deprecated, it will disappear in the next major
+release v4.0.0
(see Deprecated APIs ).
+–
return value : Object|null
Returns the Adaptations being loaded per type if a
Manifest is loaded. The returned object will have at
@@ -1279,6 +1826,10 @@
getCurrentAdaptations
getCurrentRepresentations
+–
+⚠️ This method is deprecated, it will disappear in the next major
+release v4.0.0
(see Deprecated APIs ).
+–
return value : Object|null
Returns the Representations being loaded per type
if a Manifest is loaded. The returned object will have
@@ -1289,88 +1840,13 @@
getCurrentRepresentations
null
if the current Representations are not known yet.
null
in DirectFile mode (see loadVideo
options ).
-
-
-dispose
-Free the ressources used by the player.
-!warning!: The player won’t work correctly after calling this method.
-
-
-getVideoLoadedTime
-return value : Number
-Returns in seconds the difference between:
-
-the start of the current contiguous loaded range.
-the end of it.
-
-
-
-getVideoPlayedTime
-return value : Number
-Returns in seconds the difference between:
-
-the start of the current contiguous loaded range.
-the current time.
-
-
-
-getVideoBufferGap
-return value : Number
-Returns in seconds the difference between:
-
-the current time.
-the end of the current contiguous loaded range.
-
-
-
-getPlaybackRate
-return value : Number
-Returns the current video normal playback rate (speed when playing). 1
for
-normal playback, 2
when playing *2, etc.
-
-
-setPlaybackRate
-arguments : Number
-Updates the “normal” (when playing) playback rate for the video.
-
-
-getCurrentKeySystem
-return value : string|undefined
-Returns the type of keySystem used for DRM-protected contents.
-
-
-getMinimumPosition
-return value : Number|null
-The minimum seekable player position. null
if no content is loaded.
-This is useful for live contents, where server-side buffer size are often not
-infinite. This method allows thus to seek at the earliest possible time.
-Example
-
-
-player.seekTo({
- position : player.getMinimumPosition()
-});
-
-
-
-getMaximumPosition
-return value : Number|null
-The maximum seekable player position. null
if no content is loaded.
-This is useful for live contents, where the buffer end updates continously.
-This method allows thus to seek directly at the live edge of the content.
-Example
-
-player.seekTo({
- position : player.getMaximumPosition()
-});
-
getImageTrackData
-
+–
⚠️ This method is deprecated, it will disappear in the next major
release v4.0.0
(see Deprecated APIs ).
-
+–
return value : Array.<Object>|null
The current image track’s data, null if no content is loaded / no image track
data is available.
@@ -1381,10 +1857,10 @@ getImageTrackData
setFullscreen
-
+–
⚠️ This method is deprecated, it will disappear in the next major
release v4.0.0
(see Deprecated APIs ).
-
+–
arguments : Boolean
Switch or exit the <video>
element to fullscreen mode. The argument is an
optional boolean:
@@ -1405,18 +1881,18 @@ setFullscreen
exitFullscreen
-
+–
⚠️ This method is deprecated, it will disappear in the next major
release v4.0.0
(see Deprecated APIs ).
-
+–
Exit fullscreen mode. Same than setFullscreen(false)
.
isFullscreen
-
+–
⚠️ This method is deprecated, it will disappear in the next major
release v4.0.0
(see Deprecated APIs ).
-
+–
return value : Boolean
Returns true
if the video element is in fullscreen mode, false
otherwise.
@@ -1428,27 +1904,85 @@ Example
getNativeTextTrack
-
+–
⚠️ This method is deprecated, it will disappear in the next major
release v4.0.0
(see Deprecated APIs ).
-
+–
return value : TextTrack|null
Returns the first text track of the video’s element, null if none.
This is equivalent to:
const el = player.getVideoElement();
const textTrack = el.textTracks.length ? el.textTracks[0 ] : null ;
+
+
+Static properties
+This chapter documents the static properties that can be found on the RxPlayer
+class.
+
+
+version
+type : Number
+The current version of the RxPlayer.
+
+
+ErrorTypes
+type : Object
+The different “types” of Error you can get on playback error,
+See the Player Error documentation for more information.
+
+
+ErrorCodes
+type : Object
+The different Error “codes” you can get on playback error,
+See the Player Error documentation for more information.
+
+
+LogLevel
+type : string
+default : "NONE"
+The current level of verbosity for the RxPlayer logs. Those logs all use the
+console.
+From the less verbose to the most:
+
+
+"NONE"
: no log
+
+
+"ERROR"
: unexpected errors (via console.error
)
+
+
+"WARNING"
: The previous level + minor problems encountered (via
+console.warn
)
+
+
+"INFO"
: The previous levels + noteworthy events (via console.info
)
+
+
+"DEBUG"
: The previous levels + normal events of the player (via
+console.log
)
+
+
+If the value set to this property is different than those, it will be
+automatically set to "NONE"
.
+Example
+import RxPlayer from "rx-player" ;
+RxPlayer.LogLevel = "WARNING" ;
+
Tools
+The RxPlayer has several “tools”, which are utils which can be imported without
+importing the whole RxPlayer itself.
+They are all documented here.
MediaCapabilitiesProber
-
+–
⚠️ This tool is experimental. This only means that its API can change at
any new RxPlayer version (with all the details in the corresponding release
note).
-
+–
An experimental tool to probe browser media capabilities:
Decoding capabilities
@@ -1460,11 +1994,11 @@ MediaCapabilitiesProber
TextTrackRenderer
-
+–
⚠️ This tool is experimental. This only means that its API can change at
any new RxPlayer version (with all the details in the corresponding release
note).
-
+–
The TextTrackRenderer allows to easily render subtitles synchronized to a video
element.
It allows easily to dynamically add subtitles (as long as it is in one of the
@@ -1473,22 +2007,22 @@
TextTrackRenderer
parseBifThumbnails
-
+–
⚠️ This tool is experimental. This only means that its API can change at
any new RxPlayer version (with all the details in the corresponding release
note).
-
+–
The parseBifThumbnails
function parses BIF files, which is a format created by
Canal+ to declare thumbnails linked to a given content.
This tool is documented here .
createMetaplaylist
-
+–
⚠️ This tool is experimental. This only means that its API can change at
any new RxPlayer version (with all the details in the corresponding release
note).
-
+–
The createMetaplaylist
function build a metaplaylist object from given
informations about contents.
This tool is documented here .
diff --git a/doc/generated/pages/api/local_manifest.html b/doc/generated/pages/api/local_manifest.html
index 8da35206e8..387c228b5e 100644
--- a/doc/generated/pages/api/local_manifest.html
+++ b/doc/generated/pages/api/local_manifest.html
@@ -11,17 +11,17 @@
The period object
the adaptation object
The representation object
the index object
@@ -233,7 +233,7 @@ The period object
],
}
-
+
properties
The following properties are found in a period object:
@@ -329,7 +329,7 @@ the adaptation object
},
Let’s now describes precizely every properties encountered here.
-
+
properties
The following properties are found in an adaptation object:
@@ -416,7 +416,7 @@ The representation object
We’ll now explain what each property is for, before going deeper into the
index
attribute, which allows the RxPlayer to fetch the media segments.
-
+
properties
@@ -117,7 +117,7 @@ Structure of a Period Object
broadcasting multiple foreign films. Each film, being in a different language,
will need to be part of a new Period.
-
+
properties
id
type : string
@@ -153,7 +153,7 @@ Structure of an Adaptation Object
audio ones, one for each language available.
As such, it is also often called in the API a track
.
-
+
properties
id
type : string
@@ -206,7 +206,7 @@ Structure of a Representation Object
multiple values (a codec, a bitrate). Only some of them are documented here (as
stated before, open an issue if you would like to access other properties).
-
+
properties
id
type : string
@@ -254,7 +254,7 @@ Structure of a RepresentationIndex Object
can be different depending on the type of contents/transport most interactions
here are done through few methods which hide the complexity underneath.
-
+
methods
getSegments
arguments :
@@ -278,7 +278,7 @@ Structure of a Segment Object
Those segments can have multiple useful properties which for the most part are
described here.
-
+
properties
id
type : string
diff --git a/doc/generated/pages/api/minimal_player.html b/doc/generated/pages/api/minimal_player.html
index b79fdc5e5d..3b3d88462d 100644
--- a/doc/generated/pages/api/minimal_player.html
+++ b/doc/generated/pages/api/minimal_player.html
@@ -10,7 +10,7 @@
Building with environment variables
@@ -248,7 +248,7 @@ Smooth contents with thumbnails (BIF) support
Building with environment variables
-
+
How it works
You can also include only the features you need on the RxPlayer library by
building it while having specific environment variables.
diff --git a/doc/generated/pages/api/player_events.html b/doc/generated/pages/api/player_events.html
index c68ff33315..92126bf52a 100644
--- a/doc/generated/pages/api/player_events.html
+++ b/doc/generated/pages/api/player_events.html
@@ -320,6 +320,12 @@ videoTrackChange
+A null
payload means that video track has been disabled.
+⚠️ In DirectFile mode, a null
payload may be received even if the
+video track is still visually active.
+This seems due to difficult-to-detect browser bugs. We recommend not disabling
+the video track when in directfile mode to avoid that case (this is documented
+in the corresponding APIs).
availableAudioBitratesChange
diff --git a/doc/generated/pages/api/player_options.html b/doc/generated/pages/api/player_options.html
index 739cb971da..b657357d7c 100644
--- a/doc/generated/pages/api/player_options.html
+++ b/doc/generated/pages/api/player_options.html
@@ -11,6 +11,7 @@
wantedBufferAhead
preferredAudioTracks
preferredTextTracks
+preferredVideoTracks
maxBufferAhead
maxBufferBehind
limitVideoWidth
@@ -25,10 +26,10 @@ Player Options
Overview
-Player options are options given to the player on instantiation. It’s an object
-with multiple properties.
-None of them are mandatory. For most usecase though, you might want to set at
-least the associated video element via the videoElement
property.
+Player options are options given to the player on instantiation.
+It’s an object with multiple properties. None of them are mandatory.
+For most usecase though, you might want to set at least the associated video
+element via the videoElement
property.
Properties
@@ -36,19 +37,20 @@ Properties
videoElement
type : HTMLMediaElement|undefined
-The video element the player will use.
+The media element the player will use.
+Note that this can be a <video>
or an <audio>
element.
const player = new Player({
videoElement : document .getElementsByTagName("VIDEO" )[0 ]
});
-If not defined, a new video element will be created without being inserted in
-the document, you will have to do it yourself through the getVideoElement
-method:
+If not defined, a <video>
element will be created without being inserted in
+the document. You will have to do it yourself through the getVideoElement
+method to add it yourself:
const player = new Player();
const videoElement = player.getVideoElement();
-document .appendChild(videoElement);
+document .body.appendChild(videoElement);
@@ -57,15 +59,11 @@ initialVideoBitrate
defaults : 0
This is a ceil value for the initial video bitrate chosen.
That is, the first video Representation chosen
-will be:
+will be both:
If no Representation is found to respect those rules, the Representation with
the lowest bitrate will be chosen instead. Thus, the default value - 0
-
@@ -76,10 +74,9 @@
initialVideoBitrate
initialVideoBitrate : 700000
});
-
+–
⚠️ This option will have no effect for contents loaded in DirectFile
mode (see loadVideo options ).
-
initialAudioBitrate
@@ -89,13 +86,9 @@ initialAudioBitrate
That is, the first audio Representation chosen
will be:
If no Representation is found to respect those rules, the Representation with
the lowest bitrate will be chosen instead. Thus, the default value - 0
-
@@ -106,10 +99,9 @@
initialAudioBitrate
initialAudioBitrate : 5000
});
-
+–
⚠️ This option will have no effect for contents loaded in DirectFile
mode (see loadVideo options ).
-
maxVideoBitrate
@@ -127,10 +119,9 @@ maxVideoBitrate
call.
This limit can be removed by setting it to Infinity
(which is the default
value).
-
+–
⚠️ This option will have no effect for contents loaded in DirectFile
mode (see loadVideo options ).
-
maxAudioBitrate
@@ -148,10 +139,9 @@ maxAudioBitrate
call.
This limit can be removed by setting it to Infinity
(which is the default
value).
-
+–
⚠️ This option will have no effect for contents loaded in DirectFile
mode (see loadVideo options ).
-
wantedBufferAhead
@@ -159,35 +149,60 @@ wantedBufferAhead
defaults : 30
Set the default buffering goal, as a duration ahead of the current position, in
seconds.
-Once this size of buffer is reached, the player won’t try to download new video
+
Once this size of buffer is reached, the player won’t try to download new
segments anymore.
-
+–
⚠️ This option will have no effect for contents loaded in DirectFile
mode (see loadVideo options ).
-
preferredAudioTracks
-type : Array.<Object>
+type : Array.<Object|null>
defaults : []
-Set the initial audio tracks preferences.
-This option takes an array of objects describing the languages wanted:
+This option allows to help the RxPlayer choose an initial audio track based on
+either language preferences, codec preferences or both.
+It is defined as an array of objects, each object describing constraints a
+track should respect.
+If the first object - defining the first set of constraints - cannot be
+respected under the currently available audio tracks, the RxPlayer will skip
+it and check with the second object and so on.
+As such, this array should be sorted by order of preference: from the most
+wanted constraints to the least.
+Here is all the possible constraints you can set in any one of those objects
+(note that all properties are optional here, only those set will have an effect
+on which tracks will be filtered):
{
- language : "fra" ,
-
- audioDescription : false
-
+ language : "fra" ,
+
+
+
+
+
+ audioDescription : false
+
+
+
+
+
+ codec : {
+
+
+ test : /ec-3/ ,
+
+ all : true ,
+
+
+
+
+
+
+ }
}
-All elements in that Array should be set in preference order: from the most
-preferred to the least preferred.
-When loading a content, the RxPlayer will then try to choose its audio track by
-comparing what is available with your current preferences (i.e. if the most
-preferred is not available, it will look if the second one etc.).
This array of preferrences can be updated at any time through the
setPreferredAudioTracks
method, documented
here .
-Example
+Examples
Let’s imagine that you prefer to have french or italian over all other audio
languages. If not found, you want to fallback to english:
const player = new RxPlayer({
@@ -198,10 +213,48 @@ Example
]
});
-
-⚠️ This option will have no effect for contents loaded in DirectFile
-mode (see loadVideo options ).
-
+Now let’s imagine that you want to have in priority a track that contain at
+least one profile in Dolby Digital Plus (ec-3 codec) without caring about the
+language:
+const player = new RxPlayer({
+ preferredAudioTracks : [ { codec : { all : false , test : /ec-3/ } ]
+});
+
+At last, let’s combine both examples by preferring french over itialian, italian
+over english while preferring it to be in Dolby Digital Plus:
+const player = new RxPlayer({
+ preferredAudioTracks : [
+ {
+ language : "fra" ,
+ audioDescription : false ,
+ codec : { all : false , test : /ec-3/ }
+ },
+
+
+ { language : "fra" , audioDescription : false },
+
+ {
+ language : "ita" ,
+ audioDescription : false ,
+ codec : { all : false , test : /ec-3/ }
+ },
+ { language : "ita" , audioDescription : false },
+
+ {
+ language : "eng" ,
+ audioDescription : false ,
+ codec : { all : false , test : /ec-3/ }
+ },
+ { language : "eng" , audioDescription : false }
+ ]
+
+–
+⚠️ This option will have no effect in DirectFile mode
+(see loadVideo options ) when either :
+
+No audio track API is supported on the current browser
+The media file tracks are not supported on the browser
+
preferredTextTracks
@@ -237,44 +290,145 @@ Example
]
});
+–
+⚠️ This option will have no effect in DirectFile mode
+(see loadVideo options ) when either :
+
+No text track API is supported on the current browser
+The media file tracks are not supported on the browser
+
-⚠️ This option will have no effect for contents loaded in DirectFile
-mode (see loadVideo options ).
+
+
+preferredVideoTracks
+type : Array.<Object|null>
+defaults : []
+This option allows to help the RxPlayer choose an initial video track.
+It is defined as an array of objects, each object describing constraints a
+track should respect.
+If the first object - defining the first set of constraints - cannot be
+respected under the currently available video tracks, the RxPlayer will skip
+it and check with the second object and so on.
+As such, this array should be sorted by order of preference: from the most
+wanted constraints to the least.
+When the next encountered constraint is set to null
, the player will simply
+disable the video track. If you want to disable the video track by default,
+you can just set null
as the first element of this array (e.g. [null]
).
+Here is all the possible constraints you can set in any one of those objects
+(note that all properties are optional here, only those set will have an effect
+on which tracks will be filtered):
+{
+ codec : {
+
+
+ test : /hvc/ ,
+
+ all : true ,
+
+
+
+
+
+
+ }
+ signInterpreted : true ,
+
+
+
+
+
+
+}
+
+This array of preferrences can be updated at any time through the
+setPreferredVideoTracks
method, documented
+here .
+Examples
+Let’s imagine that you prefer to have a track which contains at least one H265
+profile. You can do:
+const player = new RxPlayer({
+ preferredVideoTracks : [ { codec : { all : false , test : /^hvc/ } } ]
+});
+
+With that same constraint, let’s no consider that the current user is deaf and
+would thus prefer the video to contain a sign language interpretation.
+We could set both the previous and that new constraint that way:
+const player = new RxPlayer({
+ preferredVideoTracks : [
+
+ {
+ codec : { all : false , test : /^hvc/ }
+ signInterpreted : true ,
+ },
+
+
+ { signInterpreted : true },
+
+
+ { codec : { all : false , test : /^hvc/ } },
+
+
+
+ ]
+});
+
+For a totally different example, let’s imagine you want to start without any
+video track enabled (e.g. to start in an audio-only mode). To do that, you can
+simply do:
+const player = new RxPlayer({
+ preferredVideoTracks : [null ]
+});
+
+
+⚠️ This option will have no effect in DirectFile mode
+(see loadVideo options ) when either :
+
+No video track API is supported on the current browser
+The media file tracks are not supported on the browser
+
maxBufferAhead
type : Number|undefined
defaults : Infinity
-Set the default maximum kept buffer ahead of the current position, in seconds.
-Everything superior to that limit (currentPosition + maxBufferAhead
) will be
-automatically garbage collected. This feature is not necessary as
-the browser is already supposed to deallocate memory from old segments if/when
-the memory is scarce.
-However on some custom targets, or just to better control the memory imprint of
-the player, you might want to set this limit. You can set it to Infinity
to
-remove any limit and just let the browser do this job.
-
+Set the maximum kept buffer ahead of the current position, in seconds.
+Everything superior to that limit (currentPosition + maxBufferAhead
) will
+be automatically garbage collected.
+This feature is not necessary as the browser should by default correctly
+remove old segments from memory if/when the memory is scarce.
+However on some custom targets, or just to better control the memory footprint
+of the player, you might want to set this limit.
+Its default value, Infinity
, will remove this limit and just let the browser
+do this job instead.
+The minimum value between this one and the one returned by
+getWantedBufferAhead
will be considered when downloading new segments.
+⚠️ Bear in mind that a too-low configuration there (e.g. inferior to
+10
) might prevent the browser to play the content at all.
+You can update that limit at any time through the setMaxBufferAhead
+method .
+–
⚠️ This option will have no effect for contents loaded in DirectFile
mode (see loadVideo options ).
-
maxBufferBehind
type : Number|undefined
defaults : Infinity
-Set the default maximum kept past buffer, in seconds.
-Everything before that limit (currentPosition - maxBufferBehind
) will be
+
Set the maximum kept buffer before the current position, in seconds.
+Everything before that limit (currentPosition - maxBufferBehind
) will be
automatically garbage collected.
-This feature is not necessary as the browser is already supposed to deallocate
-memory from old segments if/when the memory is scarce.
-However on some custom targets, or just to better control the memory imprint of
-the player, you might want to set this limit. You can set it to Infinity
to
-remove any limit and just let the browser do this job.
-
+This feature is not necessary as the browser should by default correctly
+remove old segments from memory if/when the memory is scarce.
+However on some custom targets, or just to better control the memory footprint
+of the player, you might want to set this limit.
+Its default value, Infinity
, will remove this limit and just let the browser
+do this job instead.
+You can update that limit at any time through the setMaxBufferBehind
+method .
+–
⚠️ This option will have no effect for contents loaded in DirectFile
mode (see loadVideo options ).
-
limitVideoWidth
@@ -294,10 +448,9 @@ limitVideoWidth
For some reasons (displaying directly a good quality when switching to
fullscreen, specific environments), you might not want to activate this limit.
-
+–
⚠️ This option will have no effect for contents loaded in DirectFile
mode (see loadVideo options ).
-
throttleVideoBitrateWhenHidden
@@ -312,10 +465,9 @@ throttleVideoBitrateWhenHidden
throttleVideoBitrateWhenHidden : true
});
-
+–
⚠️ This option will have no effect for contents loaded in DirectFile
mode (see loadVideo options ).
-
stopAtEnd
@@ -335,10 +487,13 @@ stopAtEnd
throttleWhenHidden
-
+–
⚠️ This option is deprecated, it will disappear in the next major release
v4.0.0
(see Deprecated APIs ).
-
+Please use the
+throttleVideoBitrateWhenHidden property
+instead, which is better defined for advanced cases, such as Picture-In-Picture.
+–
type : Boolean
defaults : false
The player has a specific feature which throttle the video to the minimum
@@ -348,8 +503,7 @@
throttleWhenHidden
throttleWhenHidden : true
});
-
+–
⚠️ This option will have no effect for contents loaded in DirectFile
mode (see loadVideo options ).
-