Skip to content

Feature implementation from commits a0b76c3..f39eda0#1

Open
yashuatla wants to merge 26 commits intofeature-base-1from
feature-head-1
Open

Feature implementation from commits a0b76c3..f39eda0#1
yashuatla wants to merge 26 commits intofeature-base-1from
feature-head-1

Conversation

@yashuatla
Copy link
Owner

@yashuatla yashuatla commented Jun 27, 2025

PR Summary

Player Service and UI Enhancements with Download Date Display

Overview

This PR refactors the PlayerService to extend MediaBrowserServiceCompat for Android Auto support, improves player lifecycle management, enhances playlist functionality, and adds date display for downloads.

Change Types

Type Description
Enhancement Refactored PlayerService to extend MediaBrowserServiceCompat for Android Auto support
Enhancement Improved player lifecycle management and service binding
Feature Added thumbnail URL support to playlist interfaces
Feature Added date display for completed downloads
Bugfix Fixed potential NullPointerExceptions in player components

Affected Modules

Module / File Change Description
PlayerService.java Refactored to extend MediaBrowserServiceCompat with improved lifecycle management
PlayerHolder.java Enhanced service connection handling with Optional pattern
Player.java Modified constructor and methods for better service integration
MainActivity.java Improved mini player handling and service binding
VideoDetailFragment.java Split service connection methods and added null checks
PlaylistLocalItem.java Added thumbnail URL support
MissionAdapter.java Added date display for completed downloads
MediaSessionPlayerUi.java Updated to use injected MediaSession components

Notes for Reviewers

  • The PlayerService refactoring significantly changes how the player is created and managed
  • The Optional pattern is now used throughout PlayerHolder for safer null handling
  • Service binding and player connection are now treated as separate concerns

Stypox and others added 26 commits February 15, 2025 17:48
Read the comments in the lines changed to understand more
This changes significantly how the MediaSessionCompat and MediaSessionConnector objects are used:
- now they are tied to the service and not to the player, and so they might be reused with multiple players (which should be allowed)
- now they can exist even if there is no player (which is fundamental to be able to answer media browser queries)
This class will receive the media URLs generated by [MediaBrowserImpl] and will start playback of the corresponding streams or playlists.

Co-authored-by: Haggai Eran <[email protected]>
Co-authored-by: Profpatsch <[email protected]>
This class implements the media browser service interface as a standalone class for clearer separation of concerns (otherwise everything would need to go in PlayerService, since PlayerService overrides MediaBrowserServiceCompat)

Co-authored-by: Haggai Eran <[email protected]>
Co-authored-by: Profpatsch <[email protected]>
Now the media browser queries are replied to by MediaBrowserImpl

Co-authored-by: Haggai Eran <[email protected]>
If a playbackPreparer is set, then instead of calling `player.prepare()`, the MediaSessionConnector will call `playbackPreparer.onPrepare(true)` instead, as seen below.
This commit makes it so that playbackPreparer.onPrepare(true) restores the original behavior of just calling player.prepare().

From MediaSessionConnector -> MediaSessionCompat.Callback implementation:
```java
    @OverRide
    public void onPlay() {
      if (canDispatchPlaybackAction(PlaybackStateCompat.ACTION_PLAY)) {
        if (player.getPlaybackState() == Player.STATE_IDLE) {
          if (playbackPreparer != null) {
            playbackPreparer.onPrepare(/* playWhenReady= */ true);
          } else {
            player.prepare();
          }
        } else if (player.getPlaybackState() == Player.STATE_ENDED) {
          seekTo(player, player.getCurrentMediaItemIndex(), C.TIME_UNSET);
        }
        Assertions.checkNotNull(player).play();
      }
    }
```
This commit is a consequence of the commit "Drop some assumptions on how PlayerService is started and reused". Since the assumptions on how the PlayerService is started and reused have changed, we also need to adapt the way it is stopped. This means allowing the service to remain alive even after the player is destroyed, in case the system is still accessing PlayerService e.g. through the media browser interface. The foreground service needs to be stopped and the notification removed in any case.
Non-item errors, i.e. critical parsing errors of the page, are still handled properly.
Fixes mini-player not appearing on app start if the player service is already playing something.

The PlayerService (and the player) may be started from an external intent that does not involve the MainActivity (e.g. RouterActivity or Android Auto's media browser interface).
This PR tries to bind to the PlayerService as soon as the MainActivity starts, but only does so in a passive way, i.e. if the service is not already running it is not started.
Once the connection between PlayerHolder and PlayerService is setup, the ACTION_PLAYER_STARTED broadcast is sent to MainActivity so that it can setup the bottom mini-player.
Another important thing this commit does is to check whether the player is open before actually adding the mini-player view, since the PlayerService could be bound even without a running player (e.g. Android Auto's media browser is being used). This is a consequence of commit "Drop some assumptions on how PlayerService is started and reused".
This bug started appearing because the way to close the player is now unified in PlayerHolder.stopService(), which causes the player to reach back to the video detail fragment with a notification of the shutdown (i.e. onServiceStopped() is called). This is fixed by adding a nullability check on the binding.
This is, again, a consequence of the commit "Drop some assumptions on how PlayerService is started and reused".
This commit notified VideoDetailFragment of player starting and stopping independently of the player.
Read the comments in the code changes for more information.
Update NewPipe Extractor and add new proguard rules
Add support for Android Auto *(season 2)*
@Query("SELECT * FROM " + REMOTE_PLAYLIST_TABLE + " WHERE "
+ REMOTE_PLAYLIST_ID + " = :playlistId")
Flowable<List<PlaylistRemoteEntity>> getPlaylist(long playlistId);
Flowable<PlaylistRemoteEntity> getPlaylist(long playlistId);
Copy link

Choose a reason for hiding this comment

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

🐛 Correctness Issue

Breaking API Change: Return Type Modification.

Changing return type from Flowable<List> to Flowable will break code that expects a list structure.

Current Code (Diff):

- Flowable<PlaylistRemoteEntity> getPlaylist(long playlistId);
+ Flowable<List<PlaylistRemoteEntity>> getPlaylist(long playlistId);
📝 Committable suggestion

‼️ IMPORTANT
Trust, but verify! 🕵️ Please review this suggestion with the care of a code archaeologist - check that it perfectly replaces the highlighted code, preserves all lines, maintains proper indentation, and won't break anything in production. Your future self will thank you! 🚀

Suggested change
Flowable<PlaylistRemoteEntity> getPlaylist(long playlistId);
Flowable<List<PlaylistRemoteEntity>> getPlaylist(long playlistId);

* @param sessionConnector used to build the {@link MediaSessionPlayerUi}, lives in the service
* and could possibly be reused with multiple player instances
*/
public Player(@NonNull final PlayerService service,
Copy link

Choose a reason for hiding this comment

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

🐛 Correctness Issue

Breaking Constructor Signature Change.

The Player constructor now requires two additional parameters which will break all existing code that instantiates this class.

Proposed Code:

    public Player(@NonNull final PlayerService service,
                   @NonNull final MediaSessionCompat mediaSession,
                   @NonNull final MediaSessionConnector sessionConnector) {

*
* @param playerService the newly connected player service
*/
void onServiceConnected(@NonNull PlayerService playerService);
Copy link

Choose a reason for hiding this comment

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

🐛 Correctness Issue

Breaking interface change requires implementation updates.

The interface contract has been significantly changed, which will break all existing implementations until they're updated to match the new method signatures.

sessionConnector.setMediaButtonEventHandler(null);
sessionConnector.setPlayer(null);
sessionConnector.setQueueNavigator(null);
mediaSession.setActive(false);
Copy link

Choose a reason for hiding this comment

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

🐛 Correctness Issue

Missing mediaSession.release() call.

The mediaSession.release() call was removed, which could lead to resource leaks as MediaSessionCompat resources won't be properly released.

Current Code (Diff):

        mediaSession.setActive(false);
+        mediaSession.release();
📝 Committable suggestion

‼️ IMPORTANT
Trust, but verify! 🕵️ Please review this suggestion with the care of a code archaeologist - check that it perfectly replaces the highlighted code, preserves all lines, maintains proper indentation, and won't break anything in production. Your future self will thank you! 🚀

Suggested change
mediaSession.setActive(false);
mediaSession.setActive(false);
mediaSession.release();

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants