Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 91 additions & 30 deletions demo/scripts/components/Options/TrackSwitch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,45 +13,67 @@ function TrackSwitchConfig({
onDefaultAudioTrackSwitchingModeChange,
onCodecSwitchChange,
onEnableFastSwitchingChange,
onAudioTracksNotPlayable,
onAudioTracksNotPlayableChange,
onVideoTracksNotPlayable,
onVideoTracksNotPlayableChange,
}: {
defaultAudioTrackSwitchingMode: string;
onDefaultAudioTrackSwitchingModeChange: (newVal: string) => void;
enableFastSwitching: boolean;
onCodecSwitch: string;
onCodecSwitchChange: (val: string) => void;
onEnableFastSwitchingChange: (val: boolean) => void;
onAudioTracksNotPlayable: string;
onAudioTracksNotPlayableChange: (val: string) => void;
onVideoTracksNotPlayable: string;
onVideoTracksNotPlayableChange: (val: string) => void;
}): React.JSX.Element {
let defaultAudioTrackSwitchingModeDescMsg;
switch (defaultAudioTrackSwitchingMode) {
case "reload":
defaultAudioTrackSwitchingModeDescMsg =
"Reloading by default when the audio track is changed";
break;
case "direct":
defaultAudioTrackSwitchingModeDescMsg =
"Directly audible transition when the audio track is changed";
break;
case "seamless":
defaultAudioTrackSwitchingModeDescMsg =
"Smooth transition when the audio track is changed";
break;
default:
defaultAudioTrackSwitchingModeDescMsg = "Unknown value";
break;
}
const defaultAudioTrackSwitchingModeDescMsg = React.useMemo(() => {
switch (defaultAudioTrackSwitchingMode) {
case "reload":
return "Reloading by default when the audio track is changed";
case "direct":
return "Directly audible transition when the audio track is changed";
case "seamless":
return "Smooth transition when the audio track is changed";
default:
return "Unknown value";
}
}, [defaultAudioTrackSwitchingMode]);

let onCodecSwitchDescMsg;
switch (onCodecSwitch) {
case "reload":
onCodecSwitchDescMsg = "Reloading buffers when the codec changes";
break;
case "continue":
onCodecSwitchDescMsg = "Keeping the same buffers even when the codec changes";
break;
default:
onCodecSwitchDescMsg = "Unknown value";
break;
}
const onCodecSwitchDescMsg = React.useMemo(() => {
switch (onCodecSwitch) {
case "reload":
return "Reloading buffers when the codec changes";
case "continue":
return "Keeping the same buffers even when the codec changes";
default:
return "Unknown value";
}
}, [onCodecSwitch]);

const onAudioTracksNotPlayableDescMsg = React.useMemo(() => {
switch (onAudioTracksNotPlayable) {
case "error":
return "Throw an error if no audio track can be played";
case "continue":
return "Continue with video only if no audio track is playable";
default:
return "Unknown value";
}
}, [onAudioTracksNotPlayable]);

const onVideoTracksNotPlayableDescMsg = React.useMemo(() => {
switch (onVideoTracksNotPlayable) {
case "error":
return "Throw an error if no video track can be played";
case "continue":
return "Continue with audio only if no video track is playable";
default:
return "Unknown value";
}
}, [onVideoTracksNotPlayable]);

const onCodecSwitchSelection = React.useCallback(
({ value }: { value: string }) => onCodecSwitchChange(value),
Expand All @@ -63,6 +85,16 @@ function TrackSwitchConfig({
[onDefaultAudioTrackSwitchingModeChange],
);

const onAudioTracksNotPlayableSelection = React.useCallback(
({ value }: { value: string }) => onAudioTracksNotPlayableChange(value),
[onAudioTracksNotPlayableChange],
);

const onVideoTracksNotPlayableSelection = React.useCallback(
({ value }: { value: string }) => onVideoTracksNotPlayableChange(value),
[onVideoTracksNotPlayableChange],
);

return (
<>
<li>
Expand Down Expand Up @@ -109,6 +141,35 @@ function TrackSwitchConfig({
</Select>
<span className="option-desc">{onCodecSwitchDescMsg}</span>
</li>
<li className="featureWrapperWithSelectMode">
<Select
ariaLabel="Selecting the onAudioTracksNotPlayable attribute"
disabled={false}
className="playerOptionInput"
name="onAudioTracksNotPlayable"
onChange={onAudioTracksNotPlayableSelection}
selected={{ value: onAudioTracksNotPlayable, index: undefined }}
options={["continue", "error"]}
>
On Audio Tracks Not Playable
</Select>
<span className="option-desc">{onAudioTracksNotPlayableDescMsg}</span>
</li>

<li className="featureWrapperWithSelectMode">
<Select
ariaLabel="Selecting the onVideoTracksNotPlayable attribute"
disabled={false}
className="playerOptionInput"
name="onVideoTracksNotPlayable"
onChange={onVideoTracksNotPlayableSelection}
selected={{ value: onVideoTracksNotPlayable, index: undefined }}
options={["continue", "error"]}
>
On Video Tracks Not Playable
</Select>
<span className="option-desc">{onVideoTracksNotPlayableDescMsg}</span>
</li>
</>
);
}
Expand Down
30 changes: 30 additions & 0 deletions demo/scripts/controllers/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ function Settings({
checkManifestIntegrity,
requestConfig,
onCodecSwitch,
onAudioTracksNotPlayable,
onVideoTracksNotPlayable,
} = loadVideoOptions;
const cmcdCommunicationMethod = cmcd?.communicationType ?? "disabled";
const { manifest: manifestRequestConfig, segment: segmentRequestConfig } =
Expand Down Expand Up @@ -301,6 +303,30 @@ function Settings({
[updateLoadVideoOptions],
);

const onAudioTracksNotPlayableChange = useCallback(
(value: string) => {
updateLoadVideoOptions((prevOptions) => {
if (value === prevOptions.onAudioTracksNotPlayable) {
return prevOptions;
}
return Object.assign({}, prevOptions, { onAudioTracksNotPlayable: value });
});
},
[updateLoadVideoOptions],
);

const onVideoTracksNotPlayableChange = useCallback(
(value: string) => {
updateLoadVideoOptions((prevOptions) => {
if (value === prevOptions.onVideoTracksNotPlayable) {
return prevOptions;
}
return Object.assign({}, prevOptions, { onVideoTracksNotPlayable: value });
});
},
[updateLoadVideoOptions],
);

const onWantedBufferAheadChange = useCallback(
(wantedBufferAhead: number) => {
updatePlayerOptions((prevOptions) => {
Expand Down Expand Up @@ -424,6 +450,10 @@ function Settings({
}
onEnableFastSwitchingChange={onEnableFastSwitchingChange}
onCodecSwitchChange={onCodecSwitchChange}
onAudioTracksNotPlayable={onAudioTracksNotPlayable}
onAudioTracksNotPlayableChange={onAudioTracksNotPlayableChange}
onVideoTracksNotPlayable={onVideoTracksNotPlayable}
onVideoTracksNotPlayableChange={onVideoTracksNotPlayableChange}
/>
</Option>
<Option title="Buffer Options">
Expand Down
2 changes: 2 additions & 0 deletions demo/scripts/lib/defaultOptionsValues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ const defaultOptionsValues = {
},
},
onCodecSwitch: "continue",
onAudioTracksNotPlayable: "error",
onVideoTracksNotPlayable: "error",
},
} satisfies {
player: IConstructorOptions;
Expand Down
6 changes: 6 additions & 0 deletions demo/scripts/modules/player/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ export interface IPlayerModuleState {
livePosition: null | undefined | number;
maximumPosition: null | undefined | number;
minimumPosition: null | undefined | number;
onAudioTracksNotPlayable: "continue" | "error";
onVideoTracksNotPlayable: "continue" | "error";
playbackRate: number;
/** Try to play contents in "multithread" mode when possible. */
relyOnWorker: boolean;
Expand Down Expand Up @@ -196,6 +198,8 @@ const PlayerModule = declareModule(
livePosition: undefined,
maximumPosition: undefined,
minimumPosition: undefined,
onAudioTracksNotPlayable: "error",
onVideoTracksNotPlayable: "error",
playbackRate: 1,
relyOnWorker: false,
useWorker: false,
Expand Down Expand Up @@ -289,6 +293,8 @@ const PlayerModule = declareModule(
{
mode: state.get("relyOnWorker") ? "auto" : "main",
textTrackElement,
onAudioTracksNotPlayable: state.get("onAudioTracksNotPlayable"),
onVideoTracksNotPlayable: state.get("onVideoTracksNotPlayable"),
},
arg,
) as ILoadVideoOptions,
Expand Down
59 changes: 58 additions & 1 deletion doc/api/Loading_a_Content.md
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,63 @@ Those are the possible values for that option:
More information about the `"RELOADING"` state can be found in
[the player states documentation](./Player_States.md).

### onAudioTracksNotPlayable

_type_: `string|undefined`

_defaults_: `"error"`

Specifies the behavior when all audio tracks are not playable - This can occur if the
device does not support the required audio codecs, or if the content cannot be decrypted,
for example, due to an insufficient security level.

Those are the possible values for that option:

- `"continue"`: The player will proceed to play the content without audio.

- `"error"`: The player will throw an error `MediaError` with the code
`MANIFEST_INCOMPATIBLE_CODECS_ERROR`, `NO_AUDIO_VIDEO_TRACKS` or
`NO_PLAYABLE_REPRESENTATION` to indicate that the audio tracks could not be played.

<div class="note">

- An event [`noPlayableTrack`](./Player_Events.md) will be emitted if no audio tracks are
playable.

- If neither the audio nor video tracks are playable, a `"NO_AUDIO_VIDEO_TRACKS"` error
will be thrown regardless of this setting.

</div>

### onVideoTracksNotPlayable

_type_: `string|undefined`

_defaults_: `"error"`

Specifies the behavior when all video tracks are not playable - This can occur if the
device does not support the required video codecs, or if the content cannot be decrypted,
for example, due to an insufficient security level.

Those are the possible values for that option:

- `"continue"`: The player will proceed to play the content without video. (i.e.,
audio-only playback).

- `"error"`: The player will throw an error `MediaError` with the code
`MANIFEST_INCOMPATIBLE_CODECS_ERROR`, `NO_AUDIO_VIDEO_TRACKS` or
`NO_PLAYABLE_REPRESENTATION` to indicate that the video tracks could not be played.

<div class="note">

- An event [`noPlayableTrack`](./Player_Events.md#noplayabletrack) will be emitted if no
video tracks are playable.

- If neither the audio nor video tracks are playable, a `"NO_AUDIO_VIDEO_TRACKS"` error
will be thrown regardless of this setting.

</div>

### lowLatencyMode

_type_: `Boolean|undefined`
Expand Down Expand Up @@ -927,7 +984,7 @@ The `serverSyncInfos` object contains two keys:
<div class="note">
The `performance.now()` API is used here because it is the main API to
obtain a monotically increasing clock on the client-side.
</div</div>
</div>

Example:

Expand Down
3 changes: 3 additions & 0 deletions doc/api/Player_Errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,9 @@ An error of `type` `MEDIA_ERROR` can have the following codes (`code` property):
For those errors, you may be able to know the characteristics of the corresponding
track(s) by inspecting the error's `tracksInfo` property, described below.

- `"NO_AUDIO_VIDEO_TRACKS"`: No audio and video tracks were selected. This can happen if
the application has disabled both audio and video.

- `"MANIFEST_UPDATE_ERROR"`: This error should never be emitted as it is handled
internally by the RxPlayer. Please open an issue if you encounter it.

Expand Down
42 changes: 42 additions & 0 deletions doc/api/Player_Events.md
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,48 @@ video track when in directfile mode to avoid that case (this is documented
in the corresponding APIs).
</div>

### noPlayableTrack

_payload type_: `Object`

Emitted when no tracks of a given type can be selected for a given period.

The payload is an object with the following properties:

- `trackType` (`"audio" | "video" | "text"`): The type of the track that appears to have
no playable options available.

- `period`: (`Object`): The period in which the track is not playable. This object has the
following properties:

- `id`: (`"string"`): The ID of the period.

- `start`: (`"number"`): The start time of the period.

- `end`: (`"number" | undefined`): The end time of the period. This value may be
`undefined` either if not known or if the Period has no end yet (e.g. for live
contents, the end might not be known for now)

<div class="note">

The `noPlayableTrack` event is emitted when none of the tracks for a specific type (audio,
video, or subtitles) are playable for a given period.

For example, this might occur if a content only has audio codecs that the device does not
support, or if a media cannot be decrypted due to an insufficient security level.

By listening for this event, the application can respond appropriately, such as notifying
the user that the given track type is not playable, or taking corrective actions such as
continuing playback with available tracks (e.g., playing video-only if no audio track is
available).

The `loadVideo()` options
[`onAudioTracksNotPlayable`](./Loading_a_Content.md#onaudiotracksnotplayable) and
[`onVideoTracksNotPlayable`](./Loading_a_Content.md#onvideotracksnotplayable) defines the
player behavior when a track is not playable.

</div>

## Representation selection events

This chapter describes events linked to the current audio, video or Representation /
Expand Down
6 changes: 6 additions & 0 deletions doc/reference/API_Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,12 @@ events and so on.
- [`defaultAudioTrackSwitchingMode`](../api/Loading_a_Content.md#defaultaudiotrackswitchingmode):
Default behavior when switching the audio track.

- [`onAudioTracksNotPlayable`](../api/Loading_a_Content.md#onaudiotracksnotplayable):
Specifies the behavior when all audio tracks are not playable.

- [`onVideoTracksNotPlayable`](../api/Loading_a_Content.md#onvideotracksnotplayable):
Specifies the behavior when all video tracks are not playable.

- [`lowLatencyMode`](../api/Loading_a_Content.md#lowlatencymode): Allows to play
low-latency contents efficiently.

Expand Down
Loading