-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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: Allow Opt in to Codec Switching #4766
feat: Allow Opt in to Codec Switching #4766
Conversation
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
@tjohnson-nbcuni Thanks for your PR! Can your sign the CLA? Can you rebase and resolve the conflicts? |
9faefb3
to
3de4943
Compare
@avelad - Yep, jumping through the CLA hoops now. Updated and fixed merged conflicts previously. |
@avelad - CLA signed. Rebased and merge conflicts resolved. |
@@ -1304,7 +1308,6 @@ shaka.extern.LcevcConfiguration; | |||
*/ | |||
shaka.extern.OfflineConfiguration; | |||
|
|||
|
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.
Remove this change
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.
Looks like spacing due to merge w LCEVC changes. Has been resolved.
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.
Please, remove this change
Incremental code coverage: 52.34% |
lib/media/streaming_engine.js
Outdated
switchVariant( | ||
variant, clearBuffer = false, safeMargin = 0, force = false, | ||
adaptation = false) { | ||
async switchVariant(variant, clearBuffer = false, |
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.
Also, for reference, I spent a while considering this particular change.
The fact that this method was made async, but none of the callsites for it were made to await
the result, made me kind of nervous.
But all of the asynchronous operations inside this method are just methods on the MediaSourceEngine, which uses a queue to protect against race conditions and other concurrency errors. So it should probably be safe.
Also, actually awaiting this every time would require changing the public interface of the player, which would make this a good bit more complicated to put into a feature release.
…rning on text usage per request.
@@ -1304,7 +1308,6 @@ shaka.extern.LcevcConfiguration; | |||
*/ | |||
shaka.extern.OfflineConfiguration; | |||
|
|||
|
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.
Please, remove this change
shaka.config.CodecSwitchingStrategy.DISABLED) { | ||
this.filterManifestByCurrentVariant_(); | ||
} else { | ||
shaka.log.debug('Skipping re-filter. Not necessary' + |
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.
Here I think it is necessary to do a more complex filtering...
Imagine the next stream:
Video:
360p - h264 - 500kbps
720p - h264 - 1000kbps
720p - h265 - 800kbps
1080p - h265 - 1500kbps
Audio:
en - aac - 2 channels - 128kbps
es - ac3 - 2 channels - 128kbps
es - ac3 - 6 channels - 320kbps
es - aac - 6 channels - 256kbps
What would be expected to return (I would like @joeyparrish to confirm)
Video:
360p - h264 - 500kbps
720p - h265 - 800kbps
1080p - h265 - 1500kbps
Audio:
en - aac - 2 channels - 128kbps
es - ac3 - 2 channels - 128kbps
es - aac - 6 channels - 256kbps
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.
Going to discuss this with @sky-hugolima in the morning. Looking to close out remaining change requests; before re-addressing the filtration logic.
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.
Hey @avelad, sorry for the delay replying on this comment, we had to spend some time refactoring the actual switching logic to make it work correctly for the reload strategy.
Looking at your comment above, it makes sense, and while we actually don't experience those scenarios on our particular Peacock streams, we do understand that's a valid concern.
I think where these changes affect the intended behaviour tho, is not specifically on not calling filterManifestByCurrentVariant_
because that method's single purpose is to filter-out variants that have codecs incompatible with the currently selected variant, and that logic doesn't apply if we can switch codecs, but instead the place you mention bellow chooseCodecsAndFilterManifest
is where I think we need to make some other changes.
It's going to be a somewhat more complex refactoring to the chooseCodecsAndFilterManifest
logic to output the intended result, and filter-out the variants that conflict at the same audio/video specs, but not variants where the non optimal codecs are the only ones available for that particular audio/video spec, but I guess that's what we need to do.
Please advise if the above assumption is incorrect.
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.
That makes sense to me. I haven't finished reviewing the entire PR, but filtering by current variant should be skipped AFAICT. Removal of redundant streams is a separate issue, I think.
lib/util/player_configuration.js
Outdated
@@ -180,6 +181,7 @@ shaka.util.PlayerConfiguration = class { | |||
observeQualityChanges: false, | |||
maxDisabledTime: 30, | |||
parsePrftBox: false, | |||
codecSwitchingStrategy: shaka.config.CodecSwitchingStrategy.DISABLED, |
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 think the default behavior should be SMOOTH since it is a feature that we want to have enabled by default.
// If enforcing a codec switch strategy skip filtration | ||
if (codecSwitchingStrategy && codecSwitchingStrategy !== | ||
shaka.config.CodecSwitchingStrategy.DISABLED) { | ||
shaka.log.debug('Skipping chooseCodecsAndFilterManifest'); |
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.
See my previous comment about the filtering
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 agree. I don't think the filtering here is correct, and I think more work is required.
Codec-switching means we don't have to choose one specific codec, but some care should still be taken to consider codec preferences and to remove redundant streams available in multiple codecs.
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 also breaks the default codec selection when preferredVideoCodecs
and preferredAudioCodecs
are provided.
The PR should also mention that it resolves #1528 |
Hey guys ! I've been thinking about this PR and also testing it, and I think the decision we took to initiate/control the codec switching from inside the streaming engine, doesn't feel right, 1st because needing special logic to switch codecs should be an implementation detail of the media source, and I think only the media source engine, and the main player flow (because of filtering) should be aware of it, and 2nd because of the complexity and async nature of the streaming engine, it's really hard to refactor it so it doesn't break when there's an ongoing media source reset operation originating from a call to I think we should revisit this, to see if we can isolate the code switching logic inside the media source engine, specifically at the call to |
I have not reviewed this PR yet, but @sky-hugolima's comment sounds reasonable to me. |
…gEngine into mediaSourceEngine
…setStreamProperties
|
||
throw error; | ||
} | ||
}; | ||
operations.push(setProperties()); | ||
await setProperties(); |
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 did you change this? With this change, we will strictly wait for setStreamProperties() to complete before beginning to fetch the init segment. I don't see any reason those can't happen in parallel.
shaka.config.CodecSwitchingStrategy.DISABLED) { | ||
this.filterManifestByCurrentVariant_(); | ||
} else { | ||
shaka.log.debug('Skipping re-filter. Not necessary' + |
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.
That makes sense to me. I haven't finished reviewing the entire PR, but filtering by current variant should be skipped AFAICT. Removal of redundant streams is a separate issue, I think.
* @export | ||
*/ | ||
shaka.config.CodecSwitchingStrategy = { | ||
// No codec switching allowed (i.e. current Shaka behaviour). |
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.
nit: "current" should say "default". Also, you should move these comments into jsdoc syntax so that they end up in the public API docs for these values.
// and attempt to use this first, but fall back to reloading `MediaSource` if | ||
// not available / fails. | ||
// | ||
// This case can be implemented as a future improvement once #1528 is |
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 this accurate? It seems like you're implementing it.
/** @type {string} */ | ||
SourceBuffer.prototype.mode; | ||
|
||
// TODO: need to figure out how to correctly override this |
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 does this need to be overridden? It appears to be in the compiler's built-ins already: https://github.com/google/closure-compiler/blob/a4329316423d8478452e6330c6b887dd97ddef00/externs/browser/mediasource.js#L155
const sourceBufferAdded = new shaka.util.PublicPromise(); | ||
const sourceBuffers = | ||
/** @type {EventTarget} */(this.mediaSource_.sourceBuffers); | ||
|
||
this.eventManager_.listenOnce(sourceBuffers, 'addsourcebuffer', () => { | ||
sourceBufferAdded.resolve(); | ||
}); |
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 only waits for a single source buffer to be added, but you add up to two. Perhaps you should track the number of events fired instead, and resolve the promise when the count matches what will be added in the loop below.
sourceBufferAdded.resolve(); | ||
}); | ||
|
||
for (const contentType of [ContentType.AUDIO, ContentType.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.
I don't think this loop is correct. Some content may be audio-only or video-only. You should not assume all content is audio+video.
} | ||
|
||
const type = mimeType + this.config_.sourceBufferExtraFeatures; | ||
const sourceBuffer = this.mediaSource_.addSourceBuffer(type); |
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 duplicates a lot of what init() does, but with less flexibility. I think you should look into factoring out what you need from init() instead, and using something like this.sourceBufferTypes_ to drive the loop.
shaka.log.debug( | ||
`${this.sourceBufferTypes_[contentType]} -> ${newFullMimeType}`); | ||
if (shaka.media.Capabilities.isChangeTypeSupported()) { | ||
this.sourceBuffers_[contentType].changeType(newFullMimeType); |
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.
It is not guaranteed that any call to changeType will work merely because the old and new types are both supported separately. In particular, hardware decoders and EME can have an impact on this capability. You need to use MediaCapabilities to determine which changeType() calls will work, and ideally incorporate that into the filtering logic.
// If enforcing a codec switch strategy skip filtration | ||
if (codecSwitchingStrategy && codecSwitchingStrategy !== | ||
shaka.config.CodecSwitchingStrategy.DISABLED) { | ||
shaka.log.debug('Skipping chooseCodecsAndFilterManifest'); |
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 agree. I don't think the filtering here is correct, and I think more work is required.
Codec-switching means we don't have to choose one specific codec, but some care should still be taken to consider codec preferences and to remove redundant streams available in multiple codecs.
Codec-switching should also impact logic in period-flattening. Currently, multi-period DASH content uses codec compatibility rules to decide what to connect across periods. If codec-switching is allowed, that means many more connections are possible (and consequently that we either need much more removal of redundant streams or much earlier removal of redundancies during the period-processing stage). Addressing that may turn out to be much uglier or more performance-impacting than anything else I've left feedback on, but I think we need to understand how we will address that before this PR can be merged. Thanks so much for your work on this feature, though. I think this is moving in the right direction. |
Hey @joeyparrish ! I think I understand what you mean... so, if we had for example:
should the player consider period 2 audio adaption 1, an extension of audio adaption 1 from the 1st period ? If it should, then the period combining logic would need significant changes... The thing is we don't really expect this scenario to be possible on our streams, but instead we expect all streams to have a matching configuration including the same codec across all periods. This codec switching feature instead for us is mostly to provide a solution for streams that have some audio languages that are encoded in a single codec, different from the main audio but still supported by the decoder. Could you still consider accepting the logic on this PR without support for that more lenient combination of streams ? Thank you for the review. |
Thanks to #4766 This PR adds support for codec hot-switching in MediaSourceEngine. There are two ways to change codec: - Using `Sourcebuffer.changeType` on supported browsers - Reloading the `MediaSource` instance with the new codecs This change does not change the current filter logic, so this code is not used yet. The change in the filtering will be done in a subsequent PR in order to simplify the changes of this PR.
Hey ! Sorry for letting this go stale for so long... we've been testing this feature across our supported devices and are now getting ready to release it to prod using the There were a couple of device/models where we saw unexpected behaviour with the Now, about this pull request, since we've moved on by splitting it into two and already merged the codec switching logic inside the media source and streaming engines (#5217), I think we should close this one. Meanwhile, not sure what're your plans for the filtering logic changes, but we've opted for after the manifest is already filtered of variants unsupported by the device (through media capabilities), not do any further filtering of variants when the codec switching logic is enabled, even on scenarios where both codecs are available for the same lang & role. The reasoning being we do the filtering on the application side based on our rules that give preference to a higher channel count and only then to codecs based on a set order of codec preference, but we're not sure this would be a desirable behaviour for every app. The main issue with our approach, is that the initial track selection logic done by the preference based adaptation set criteria, must match the filtering logic on the app, otherwise that track can potentially be outside the options we provide the user with. A potentially better alternative would be to still run filtering of multiple supported audio tracks that share the same lang & role inside the player, but somehow let the app customize the decisioning logic. For example, by default the player could opt between duplicate tracks, based of the following order:
but if the app provides a custom audio track selection method through config, for example: Thoughts ? |
I'm going to close this PR because the internal codec switching logic is already done, only the variant filtering part is missing to leave variants with different codecs. If anyone wants to work on this, there are several files that need to be modified:
Point 1 and 3 are easy to implement. Point 2 is where all the logic is. |
Reference implementation for codec switching.
Resolves: #1528
Related: #4379
--
DISABLED
is current Shaka behavior.--
SMOOTH
behavior allows for use of SourceBuffer.changeType on environments which support it.--
RELOAD
behavior replaces MediaSource instances.