-
Notifications
You must be signed in to change notification settings - Fork 133
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat: add an option to play without audio or video if audio or video failed #1624
base: dev
Are you sure you want to change the base?
Conversation
724b4a3
to
5a073f9
Compare
relates to #1602 |
Shouldn't it be "tracks" here?
Doesn't it break the API this way? Its complex but it changes the default behavior on which some applications may rely? Wouldn't it be safer to default it to |
doc/api/Loading_a_Content.md
Outdated
|
||
_defaults_: `"continue"` | ||
|
||
Specifies the behavior when all audio tracks are not playable. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could clarify by giving some example cases below
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What kind of example could be relevant here? Doesn't the description of the option provides clear indication on what the player behavior is?
"continue"
: The player will proceed to play the content without audio.
"error"
: The player will throw an error to indicate that the audio tracks could not be
played.
Or maybe you want me to add an example that describes how all tracks can be unplayable (codec not supported, license not containing the keys for audio/video, ...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or maybe you want me to add an example that describes how all tracks can be unplayable (codec not supported, license not containing the keys for audio/video, ...)
Yes, this
Yes I think I should default it to "error" instead. |
I have updated it. |
ef25c83
to
4b6acd7
Compare
return representations[0].getMimeTypeString(); | ||
} else if (adaptation.representations.length > 0) { | ||
return adaptation.representations[0].getMimeTypeString(); | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to be sure, this one else should never be able to be entered no?
@@ -1469,6 +1486,25 @@ function toExposedPeriod(p: IPeriodMetadata): IPeriod { | |||
return { start: p.start, end: p.end, id: p.id }; | |||
} | |||
|
|||
function findNextPlayableAdaptation( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From that function's point of view, I'm under the impression that this is not really the "next" playable Adaptation, just the first playable adaptation, no?
nextAdaptation === undefined && | ||
bufferType === "audio" && | ||
this.onAudioTracksNotPlayable === "continue" && | ||
findNextPlayableAdaptation(period, "video") !== undefined |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we doing this call? We just have to check that the current video track is defined no?
Also what happens if we were in audio-only mode here?
nextAdaptation === undefined && | ||
bufferType === "video" && | ||
this.onVideoTracksNotPlayable === "continue" && | ||
findNextPlayableAdaptation(period, "audio") !== undefined |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same, we should just check that there's currently an audio track no?
As this adds a similar logic in the TrackStore + Manifest + Init, and as the manifest is more or less intended to just regroup metadata, I'm wondering if it would not be easier to follow if that option was mainly handled by a part of the What do you think? |
Automated performance checks have been performed on commit Tests results✅ Tests have passed. Performance tests 1st run outputNo significative change in performance for tests:
If you want to skip performance checks for latter commits, add the |
…o is not supported
…tPlayable to "error"
Automated performance checks have been performed on commit Tests results✅ Tests have passed. Performance tests 1st run outputNo significative change in performance for tests:
If you want to skip performance checks for latter commits, add the |
5eb15f4
to
5e29ebe
Compare
…dia has no track.
5e29ebe
to
dc7bfe7
Compare
Automated performance checks have been performed on commit Tests results✅ Tests have passed. Performance tests 1st run outputNo significative change in performance for tests:
If you want to skip performance checks for latter commits, add the |
|
||
_payload type_: `Object` | ||
|
||
Emitted when no tracks of a particular type can be selected for a period. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might want to indicate that this happen when the corresponding options are set, and in which condition it arises
Else a developer won't understand what this event is for and whether it should handle it
this._priv_onFatalError(err, contentInfos); | ||
}); | ||
|
||
contentInfos.tracksStore.onManifestUpdate(manifest); | ||
tracksStore.addEventListener("noPlayableTrack", (trackType) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The payload name is not right
@@ -135,6 +98,30 @@ export default class Period implements IPeriodMetadata { | |||
this.streamEvents = args.streamEvents === undefined ? [] : args.streamEvents; | |||
} | |||
|
|||
createAdaptationsObject( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the intention to create a private method that is only called at the start of the Period's instantiation?
If it is, I have some issues:
- it's not marked as a private method
- I tend to think that it is a bad idea to call a method before the instantiation was succesfully done because we're in a partial state in which even TypeScript might not see that the current instance is not fully constructed / initialized. It's a difficult state to think about.
As we're not even relying on the Period's context it seems here, can't we just make that a regular function?
}, {}); | ||
// this.checkIfStreamIsSupported(hasSupportedMedia); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
gné?
@@ -158,6 +171,49 @@ export default class TracksStore extends EventEmitter<ITracksStoreEvents> { | |||
/** Periods which have just been added. */ | |||
const addedPeriods: ITSPeriodObject[] = []; | |||
|
|||
for (const period of periods) { | |||
["audio" as const, "video" as const].forEach((ttype: ITrackType) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we could just isolate a good chunk of it to its own function
if ( | ||
firstPlayableAdaptation === undefined && | ||
ttype === "audio" && | ||
this.onAudioTracksNotPlayable === "continue" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might have been easier to follow as something like this.onTracksNotPlayable[ttype] === continue
? (suggestion)
// Video is not playable but audio may be playable, let's continue the playback. | ||
this.trigger("warning", err); | ||
} else if (firstPlayableAdaptation !== undefined) { | ||
this.trigger("warning", err); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We cannot make TypeScript understand that this does not happen?
} else if (firstPlayableAdaptation !== undefined) { | ||
this.trigger("warning", err); | ||
} else { | ||
this.trigger("error", err); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't we also just abort the trackstore here? That's what seem to be done for NO_PLAYABLE_REPRESENTATION
adaptation, | ||
switchingMode: DEFAULT_VIDEO_TRACK_SWITCHING_MODE, | ||
lockedRepresentations, | ||
} as IVideoStoredSettings; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is the as
needed here?
* @param type | ||
* @returns | ||
*/ | ||
private resetSelectedTrackIfNotAvailableAnymore( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice
@@ -481,17 +498,119 @@ export default class TracksStore extends EventEmitter<ITracksStoreEvents> { | |||
if ( | |||
periodObj.isPeriodAdvertised && | |||
trackObj.dispatcher !== null && | |||
!trackObj.dispatcher.hasSetTrack() && | |||
trackObj.storedSettings !== undefined |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wait why are we removing this? I guess (though I don't remember why) it was placed on purpose as a guard and it has different semantics than null
) { | ||
trackObj.dispatcher.updateTrack(trackObj.storedSettings); | ||
trackObj.dispatcher.updateTrack(trackObj.storedSettings ?? null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I get it here.
To me, if you don't want a type of media, storedSettings
should always be set explicitely to null
. If storedSettings
is ever undefined
, it should always mean that we don't know yet
noPlayableTrack: INoPlayableTrack; | ||
} | ||
|
||
interface INoPlayableTrack { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With an EventPayload
suffix maybe?
period.adaptations[bufferType].length > 0; | ||
const firstPlayableAdaptation = findFirstPlayableAdaptation(period, bufferType); | ||
|
||
if (!periodHasAdaptationForType) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Weird case we would be in, an "onNoPlayableRepresentation" call for a buffer type we don't have
} | ||
|
||
if (bufferType === "text") { | ||
return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why
) { | ||
// Audio is not playable but video may be playable, let's continue the playback. | ||
log.warn(`TS: No playable audio, continuing without audio`); | ||
this.trigger("noPlayableTrack", { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/!\ this event might call the application's code synchronously and completely change the tracksStore's state:
- it might be disposed
- the track choices might have completely changed
I signal this because I've seen no guard against those possibilities, we just assume we're in the same state.
Currently, the player throws an error if either all audio or all video tracks are not playable. This PR introduces two new options, onAudioTrackNotPlayable and onVideoTrackNotPlayable, to the loadVideo method. These options allow the player to continue playback even when all audio or video tracks are not playable. In such cases, the content will play without audio or video, respectively, instead of failing.
API
loadVideo(options)
options object has two more keys:onAudioTrackNotPlayable
:"continue"
or"error"
. Default if not set:"continue"
onVideoTrackNotPlayable
:"continue"
or"error"
. Default if not set:"continue"
Usage
Example
https://codesandbox.io/p/sandbox/amazing-pare-kdf65f