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

Add support for Android Auto #9592

Open
wants to merge 31 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
1471957
Keep MediaSessionCompat and MediaSessionConnector in a separate class
haggaie Jan 2, 2023
31d7bfa
Simple playback status and controls in Android Auto
haggaie Dec 25, 2022
90efadb
Manifest and metadata for Android Auto
haggaie Dec 25, 2022
6822362
player: seek to new index when given a new playqueue with a different…
haggaie Feb 4, 2023
485635a
Media browser interface to show playlists on Android Auto
haggaie Jan 14, 2023
c1d24ad
StreamHistoryEntry: convert to StreamInfoItem
haggaie Feb 4, 2023
97a0e30
MediaBrowser: expose search history
haggaie Feb 4, 2023
4f8cca7
Pass media browser error as ErrorInfo
haggaie Jun 2, 2023
b3226f7
Improve code formatting, annotate more fields and methods
AudricV Aug 11, 2023
a526319
Add icons to root media items
AudricV Aug 11, 2023
511b014
Add uploader name of streams as subtitle of MediaItems
AudricV Aug 20, 2023
148b050
Update media browsers when the list of local playlist changes
haggaie Aug 22, 2023
2a01f37
android auto: fix navigation tab colors and cut text
haggaie Jul 24, 2024
6871c96
PlaylistMetadataEntry: add interface method to get the thumbnail Url
haggaie Aug 2, 2024
b795f4d
RemotePlaylistManager: add helper method to get a playlist by its uid
haggaie Aug 2, 2024
e9536c1
media browser: expose remote playlists together with local playlists
haggaie Aug 2, 2024
6cc811f
media browser: support searching
haggaie Aug 4, 2024
8fc659c
media browser: clean up Uri.parse() null checks
haggaie Aug 16, 2024
715e829
LocalItem/PlaylistLocalItem: convert to Kotlin
snaik20 Sep 2, 2024
83c7114
PlayerService: convert to Kotlin (mechanical)
Profpatsch Feb 5, 2025
aa750d0
MediaBrowserConnector: convert to Kotlin (mechanical)
Profpatsch Feb 5, 2025
0594b65
MediaBrowserConnector: convert to Kotlin (minimal compilation fixes)
Profpatsch Feb 7, 2025
8584f03
MediaBrowserConnector: simplify index, remove unstable warning
haggaie Sep 17, 2024
ae2003a
media browser: rename remote -> isRemote
haggaie Sep 17, 2024
718ff0f
media browser: pass media ID to parsing error exceptions
haggaie Sep 17, 2024
f974bf1
Addressed review comments
snaik20 Oct 12, 2024
c0d229a
Addressed review comments
snaik20 Dec 6, 2024
92e7feb
PlayerService: return appropriate IBinder depending on the action
haggaie Dec 14, 2024
85b00f5
MediaBrowserConnector: separate context from playerService
Profpatsch Feb 7, 2025
10458b9
MediaBrowserConnector: move init to top & simplify
Profpatsch Feb 7, 2025
df29bea
MediaBrowserConnector: simplify database methods
Profpatsch Feb 7, 2025
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
2 changes: 1 addition & 1 deletion app/src/main/java/org/schabi/newpipe/player/Player.java
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ public Player(@NonNull final PlayerService service) {
// notification ui in the UIs list, since the notification depends on the media session in
// PlayerUi#initPlayer(), and UIs.call() guarantees UI order is preserved.
UIs = new PlayerUiList(
new MediaSessionPlayerUi(this),
new MediaSessionPlayerUi(this, service.getSessionConnector()),
new NotificationPlayerUi(this)
);
}
Expand Down
46 changes: 32 additions & 14 deletions app/src/main/java/org/schabi/newpipe/player/PlayerService.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@
import android.os.IBinder;
import android.util.Log;

import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector;

import org.schabi.newpipe.player.mediabrowser.MediaBrowserConnector;
import org.schabi.newpipe.player.mediasession.MediaSessionPlayerUi;
import org.schabi.newpipe.player.notification.NotificationPlayerUi;
import org.schabi.newpipe.util.ThemeHelper;
Expand All @@ -47,6 +50,9 @@ public final class PlayerService extends Service {
private final IBinder mBinder = new PlayerService.LocalBinder(this);


private MediaBrowserConnector mediaBrowserConnector;


/*//////////////////////////////////////////////////////////////////////////
// Service's LifeCycle
//////////////////////////////////////////////////////////////////////////*/
Expand All @@ -59,15 +65,21 @@ public void onCreate() {
assureCorrectAppLanguage(this);
ThemeHelper.setTheme(this);

player = new Player(this);
/*
Create the player notification and start immediately the service in foreground,
otherwise if nothing is played or initializing the player and its components (especially
loading stream metadata) takes a lot of time, the app would crash on Android 8+ as the
service would never be put in the foreground while we said to the system we would do so
*/
player.UIs().get(NotificationPlayerUi.class)
.ifPresent(NotificationPlayerUi::createNotificationAndStartForeground);
mediaBrowserConnector = new MediaBrowserConnector(this);
}

private void initializePlayer() {
if (player == null) {
player = new Player(this);
/*
Create the player notification and start immediately the service in foreground,
otherwise if nothing is played or initializing the player and its components (especially
loading stream metadata) takes a lot of time, the app would crash on Android 8+ as the
service would never be put in the foreground while we said to the system we would do so
*/
player.UIs().get(NotificationPlayerUi.class)
.ifPresent(NotificationPlayerUi::createNotificationAndStartForeground);
}
}

@Override
Expand Down Expand Up @@ -104,11 +116,10 @@ public int onStartCommand(final Intent intent, final int flags, final int startI
return START_NOT_STICKY;
}

if (player != null) {
player.handleIntent(intent);
player.UIs().get(MediaSessionPlayerUi.class)
.ifPresent(ui -> ui.handleMediaButtonIntent(intent));
}
initializePlayer();
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it is ill-advised to move the player initialization from onCreate to onStartCommand, as this is a major change to the lifecycle.

player.handleIntent(intent);
player.UIs().get(MediaSessionPlayerUi.class)
.ifPresent(ui -> ui.handleMediaButtonIntent(intent));

return START_NOT_STICKY;
}
Expand Down Expand Up @@ -143,6 +154,10 @@ public void onDestroy() {
Log.d(TAG, "destroy() called");
}
cleanup();
if (mediaBrowserConnector != null) {
mediaBrowserConnector.release();
mediaBrowserConnector = null;
}
}

private void cleanup() {
Expand All @@ -167,6 +182,9 @@ public IBinder onBind(final Intent intent) {
return mBinder;
}

public MediaSessionConnector getSessionConnector() {
return mediaBrowserConnector.getSessionConnector();
}
public static class LocalBinder extends Binder {
private final WeakReference<PlayerService> playerService;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.schabi.newpipe.player.mediabrowser;

import android.support.v4.media.session.MediaSessionCompat;

import androidx.annotation.NonNull;

import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector;

import org.schabi.newpipe.player.PlayerService;

public class MediaBrowserConnector {
private static final String TAG = MediaBrowserConnector.class.getSimpleName();

private final PlayerService playerService;
private final @NonNull MediaSessionConnector sessionConnector;
private final @NonNull MediaSessionCompat mediaSession;

public MediaBrowserConnector(@NonNull final PlayerService playerService) {
this.playerService = playerService;
mediaSession = new MediaSessionCompat(playerService, TAG);
sessionConnector = new MediaSessionConnector(mediaSession);
sessionConnector.setMetadataDeduplicationEnabled(true);
}

public @NonNull MediaSessionConnector getSessionConnector() {
return sessionConnector;
}

public void release() {
mediaSession.release();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,11 @@ public class MediaSessionPlayerUi extends PlayerUi
private List<NotificationActionData> prevNotificationActions = List.of();


public MediaSessionPlayerUi(@NonNull final Player player) {
public MediaSessionPlayerUi(@NonNull final Player player,
@NonNull final MediaSessionConnector sessionConnector) {
super(player);
this.mediaSession = sessionConnector.mediaSession;
this.sessionConnector = sessionConnector;
ignoreHardwareMediaButtonsKey =
context.getString(R.string.ignore_hardware_media_buttons_key);
}
Expand All @@ -61,10 +64,8 @@ public void initPlayer() {
super.initPlayer();
destroyPlayer(); // release previously used resources

mediaSession = new MediaSessionCompat(context, TAG);
mediaSession.setActive(true);

sessionConnector = new MediaSessionConnector(mediaSession);
sessionConnector.setQueueNavigator(new PlayQueueNavigator(mediaSession, player));
sessionConnector.setPlayer(getForwardingPlayer());

Expand All @@ -77,7 +78,6 @@ public void initPlayer() {
updateShouldIgnoreHardwareMediaButtons(player.getPrefs());
player.getPrefs().registerOnSharedPreferenceChangeListener(this);

sessionConnector.setMetadataDeduplicationEnabled(true);
sessionConnector.setMediaMetadataProvider(exoPlayer -> buildMediaMetadata());

// force updating media session actions by resetting the previous ones
Expand All @@ -89,27 +89,23 @@ public void initPlayer() {
public void destroyPlayer() {
super.destroyPlayer();
player.getPrefs().unregisterOnSharedPreferenceChangeListener(this);
if (sessionConnector != null) {
sessionConnector.setMediaButtonEventHandler(null);
sessionConnector.setPlayer(null);
sessionConnector.setQueueNavigator(null);
sessionConnector = null;
}
if (mediaSession != null) {
mediaSession.setActive(false);
mediaSession.release();
mediaSession = null;
}

sessionConnector.setMediaButtonEventHandler(null);
sessionConnector.setPlayer(null);
sessionConnector.setQueueNavigator(null);
sessionConnector.setMediaMetadataProvider(null);

mediaSession.setActive(false);

prevNotificationActions = List.of();
}

@Override
public void onThumbnailLoaded(@Nullable final Bitmap bitmap) {
super.onThumbnailLoaded(bitmap);
if (sessionConnector != null) {
// the thumbnail is now loaded: invalidate the metadata to trigger a metadata update
sessionConnector.invalidateMediaSessionMetadata();
}

// the thumbnail is now loaded: invalidate the metadata to trigger a metadata update
sessionConnector.invalidateMediaSessionMetadata();
}


Expand All @@ -132,7 +128,7 @@ public void handleMediaButtonIntent(final Intent intent) {
}

public Optional<MediaSessionCompat.Token> getSessionToken() {
return Optional.ofNullable(mediaSession).map(MediaSessionCompat::getSessionToken);
return Optional.of(mediaSession.getSessionToken());
}


Expand Down