Skip to content
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

ExoPlayer 2.17.1 update and MediaSource management rework #8020

Merged
merged 5 commits into from
Apr 2, 2022

Conversation

karyogamy
Copy link
Contributor

@karyogamy karyogamy commented Mar 12, 2022

What is it?

  • Bugfix (user facing)
  • Feature (user facing)
  • Codebase improvement (dev facing)
  • Meta improvement to the project (dev facing)

Description of the changes in your PR

Hi team,
I took a shot at upgrading ExoPlayer to 2.17.0 and realized that quite a few API has been made obsolete. For example, getTag has been dropped and will need even more hacks to get it to work. Since a rework is needed anyways, I took the liberty to toss out the old custom MediaSource management in favour of a more vanilla ExoPlayer model, following this comment. This is an attempt to help with refactoring the player so we can shift to expose ExoPlayer interface for player management instead of managing our own PlayQueue for playlist modifications.

This means ExoPlayer will now be responsible for managing the internal playlist, and PlayQueue will no longer play a central role to playback management. It will become a player proxy for user issued commands, and acts as a view for the player playlist, since ExoPlayer doesn't provide an API for the entire playlist. As an added bonus, shuffle now works properly. You may also notice buffer will start immediately when seeking to an unprepared stream, as opposed to the old model, where playback resumes until StreamInfo is fetched before buffering starts. This is because StreamInfo are now loaded on-demand inside the MediaSource in a custom CompositeMediaSource that dispatches a call to resolve/build the actual MediaSource using the PlaybackResolvers.

Also, each MediaSource is now associated with a MediaItem that holds metadata. Tag objects are now contained inside this MediaItem instead. As a result, the old MediaSourceTag are now revamped into a generic interface that bridges between PlayQueueItem and StreamInfo since they both hold similar metadata. This can be expanded later so this interface object is exposed to external adapters and properly display if a stream is properly loaded or has errors (e.g. content error).

Going back to MediaSource management, a challenge arising from the new model is its lifecycle, where MediaSource in ExoPlayer aren't really allowed to self update. This means when a stream expires, we cannot update the Playlist MediaSource with a new one, nor should we since ExoPlayer should take care of it instead. This brings in DataSpec resolution, which is an interface ExoPlayer exposes that lets us examine an URI and replace it with another. So the solution here is to update our PlaybackResolver to encapsulate the Extractor Stream metadata (e.g. AudioStream, VideoStream, SubtitleStream, etc.) in an URI, and deliver that to the new DataSpecResolver to convert our custom URI to a real stream URI. This means we can rely on our own Extractor cache to handle expiration and resolve stream metadata into the real URL and ExoPlayer will handle the rest.

Concerns:

  1. Because ExoPlayer now takes care of managing its own playlist, the MediaSourceManager (now PlayQueueReactor) no longer correct desynchronizations between the PlayQueue and ExoPlayer's own playlist. Since PlayQueue and PlayQueueReactor communicate with a message bus, non-atomic calls to PlayQueue can cause desyncs. For example, Enqueue next was broken because it works by appending to the queue and moving the last item to the current playing. When the append event message gets to the reactor, the item has already moved on the play queue. This is temporarily fixed by introducing a new event Insert to avoid mixing logic with auto-queuing on Append events. But a proper solution would be to refactor PlayQueue into an interface and create a new wrapper ActivePlayQueue on the base PlayQueue to handle all the player controlled play queue logic. The ultimate end-state for PlayQueue in this new model is to mainly act as a quick-access view for the adapters, and all actions to the queue are exposed via ExoPlayer interface instead.

  2. Since ExoPlayer controls the shuffling now, we also lose some control (for the time being) in shuffled mode. For example, currently PlayQueue shuffle reorders the queue and is presented to the user (I actually like this better) instead of ExoPlayer's model of jumping to a random index. But this now also breaks the Enqueue next feature when used in shuffle mode. We can potentially create our own shuffle order as an individual component, though it means more code and kind of defeats the purpose of lifting the feature to ExoPlayer.

  3. As mentioned before, StreamInfo preparation will now also be part of the buffering. This means, when selecting a stream that is not yet loadedm at all, buffering will feel a bit slower than before. Whereas before, the StreamInfo would load in the background while the stream continues to play before the buffering starts. I tried to mitigate this by pre-emptively loading the StreamInfo of the next MediaItem, but this issue will never fully go away since we can't load every item on the queue.

As you can see, there are quite a few more things that can be worked on to get this to a perfect state. But since the PR size is getting larger, I wanted to get this PR out now to get some opinion on if we want to make this shift in player modelling as part of the greater refactor work (or if this path isn't really worth proceeding). And if we want to make the shift, have the changes tested by more people and the code reviewed since it might break existing features.

APK testing

The APK can be found by going to the "Checks" tab below the title. On the left pane, click on "CI", scroll down to "artifacts" and click "app" to download the zip file which contains the debug APK of this PR.

Fixes the following issue(s)

Due diligence

@AudricV AudricV added feature request Issue is related to a feature in the app player Issues related to any player (main, popup and background) labels Mar 12, 2022
Copy link
Member

@litetex litetex left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the PR!

A ExoPlayer update was long overdue in my opinion, good that you picked it up.

Very good PR in my opinion, I like the coding style and the usage of new Java 8 features such as the Streams API 👍

Didn't test it yet but I found some things in the review that should be processed:

Update: Also please have a look at the Sonarcloud warnings

@@ -798,7 +816,6 @@ public void handleIntent(@NonNull final Intent intent) {
if (oldPlayerType != playerType && playQueue != null) {
// If playerType changes from one to another we should reload the player
// (to disable/enable video stream or to set quality)
setRecovery();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this removed?

app/src/main/java/org/schabi/newpipe/player/Player.java Outdated Show resolved Hide resolved
@karyogamy
Copy link
Contributor Author

karyogamy commented Mar 15, 2022

Many thanks for the thorough review @litetex.

Given the serialization usage coincides with the DataSpec issue that might conflict with @TiA4f8R's delivery-methods-v2, I'd recommend not going forward with this PR as-is, and instead continue with the existing model where we have control MediaSource. The player changes though will largely remain the same, so the review is still effective. I've acked most of the change requests, and will make the change and add docs when time permits. You can check out the diff here.

Eventually when we decide to move to the ExoPlayer model, we will have the same issue using DataSpecs and need to choose to go with serialization or cache.

@karyogamy karyogamy force-pushed the exo-update-v17 branch 2 times, most recently from 05dad02 to 9fb95e3 Compare March 17, 2022 00:55
@litetex litetex marked this pull request as draft March 17, 2022 16:56
@karyogamy karyogamy force-pushed the exo-update-v17 branch 4 times, most recently from ed0e8bb to 6554599 Compare March 19, 2022 19:45
added: MediaItemTag for ManagedMediaSources.
added: silent track for FailedMediaSource.
added: keyframe fast forward at initial playback buffer.
added: error notification on silently skipped streams.
…ediaSource.

fixed: onPlaybackSynchronize to rewind when not playing, which was incorrectly removed in previous commit.
fixed: sonar and checkstyle issues.
Copy link
Member

@litetex litetex left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some minor things I noticed:

app/src/main/java/org/schabi/newpipe/player/Player.java Outdated Show resolved Hide resolved
app/src/main/java/org/schabi/newpipe/player/Player.java Outdated Show resolved Hide resolved
app/src/main/java/org/schabi/newpipe/player/Player.java Outdated Show resolved Hide resolved
app/src/main/java/org/schabi/newpipe/player/Player.java Outdated Show resolved Hide resolved
app/src/main/java/org/schabi/newpipe/player/Player.java Outdated Show resolved Hide resolved
app/src/main/java/org/schabi/newpipe/player/Player.java Outdated Show resolved Hide resolved
app/src/main/java/org/schabi/newpipe/player/Player.java Outdated Show resolved Hide resolved
…fications are created.

updated: Throwable usage to Exceptions.
updated: minor styles and documentations.
Copy link
Member

@litetex litetex left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nearly perfect, only minor things:

…onds before allowing retry.

updated: minor style fixes.
@sonarcloud
Copy link

sonarcloud bot commented Mar 27, 2022

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 10 Code Smells

0.0% 0.0% Coverage
0.0% 0.0% Duplication

Copy link
Member

@litetex litetex left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Found one problem while testing (however this is likely unrelated): When enqueuing a video it gets enqueued 2x.

Otherwise it looks like it' working fine 👍

@karyogamy
Copy link
Contributor Author

Other issues that should be fixed in tandem:

@AudricV
Copy link
Member

AudricV commented Apr 3, 2022

It seems I found a regression with this PR: use the Play all button on the Creative Commons channel, open the video with Stacy Allison-Cassin, enable autogenerated subtitles, then switch to the next video. Wait for lyrics and you will notice that English subtitles are enabled, even if subtitles are marked as disabled by the player.

You need to switch to another subtitle language and then go back to the English one to make the player show that English subtitles are enabled.

@karyogamy
Copy link
Contributor Author

Confirmed - can reproduce by queuing any 2 videos, first with a manual subtitle (e.g. english) and the second with only auto-generated subtitle of the previous language (e.g. english auto-generated) and the rest is the same as @TiA4f8R describes.

I suspect this is because of how ExoPlayer compare language names, we can probably fix this by updating the selected ExoPlayer subtitle track according to the selected caption dropdown on metadata update. Will take a look when next available.

if (surface.isValid()) {
// initially set the surface manually otherwise
// onRenderedFirstFrame() will not be called
simpleExoPlayer.setVideoSurface(surface);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be replaced by simpleExoPlayer.setVideoSurfaceHolder(binding.surfaceView.getHolder());, or would there be issues? After some testing in #8170 I couldn't notice any difference.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolutely, that is the better solution. setVideoSurfaceHolder is doing the same null checks here: https://github.com/google/ExoPlayer/blob/release-v2/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java#L1262

onMetadataChanged(metadata);
maybeAutoQueueNextStream(streamInfo);
onMetadataChanged(streamInfo);
NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, true);
Copy link
Member

@Stypox Stypox Apr 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, why does this now have forceRecreate = true? Also, it seems like you are calling createNotificationIfNeededAndUpdate twice when updateMetadataWith is called, since there is a createNotificationIfNeededAndUpdate(this, false); also inside onMetadataChanged. And as you can see that one has forceRecreate = false.

Note for myself: check NotificationPlayerUi.onMetadataChanged after getting a response here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oversight on my part. createNotificationIfNeededAndUpdate in onMetadataChanged should be removed.

app/src/main/java/org/schabi/newpipe/player/Player.java Outdated Show resolved Hide resolved
onMetadataChanged(metadata);
maybeAutoQueueNextStream(streamInfo);
onMetadataChanged(streamInfo);
NotificationUtil.getInstance().createNotificationIfNeededAndUpdate(this, true);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oversight on my part. createNotificationIfNeededAndUpdate in onMetadataChanged should be removed.

if (surface.isValid()) {
// initially set the surface manually otherwise
// onRenderedFirstFrame() will not be called
simpleExoPlayer.setVideoSurface(surface);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolutely, that is the better solution. setVideoSurfaceHolder is doing the same null checks here: https://github.com/google/ExoPlayer/blob/release-v2/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java#L1262

@karyogamy
Copy link
Contributor Author

Sorry about that folks, got too used to Stash PR workflow and didn't realize on Github all my review replies stayed in Pending and never got out =\

@litetex litetex mentioned this pull request Apr 16, 2022
5 tasks
ktprograms added a commit to ktprograms/NewPipe that referenced this pull request Apr 30, 2022
DefaultControlDispatcher was removed in ExoPlayer 2.16.0, so the class
extending it that handled play/pause was removed in TeamNewPipe#8020.

The new solution is to use an instance of ForwardingPlayer. Call
sessionConnector.setPlayer with an instance of ForwardingPlayer that
overrides play() and pause() and calls the callback methods.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature request Issue is related to a feature in the app player Issues related to any player (main, popup and background)
Projects
None yet
5 participants