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 26 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
c73763d
Keep MediaSessionCompat and MediaSessionConnector in a separate class
haggaie Jan 2, 2023
34a4a27
Simple playback status and controls in Android Auto
haggaie Dec 25, 2022
4e64b87
Manifest and metadata for Android Auto
haggaie Dec 25, 2022
22f2351
player: seek to new index when given a new playqueue with a different…
haggaie Feb 4, 2023
a43980d
Media browser interface to show playlists on Android Auto
haggaie Jan 14, 2023
2d6a99c
StreamHistoryEntry: convert to StreamInfoItem
haggaie Feb 4, 2023
b4ce702
MediaBrowser: expose search history
haggaie Feb 4, 2023
fd0ca90
Pass media browser error as ErrorInfo
haggaie Jun 2, 2023
f4e5920
Improve code formatting, annotate more fields and methods
AudricV Aug 11, 2023
4fc92cb
Add icons to root media items
AudricV Aug 11, 2023
c916608
Add uploader name of streams as subtitle of MediaItems
AudricV Aug 20, 2023
0af4c34
Update media browsers when the list of local playlist changes
haggaie Aug 22, 2023
a1a2885
android auto: fix navigation tab colors and cut text
haggaie Jul 24, 2024
f6dd49b
PlaylistMetadataEntry: add interface method to get the thumbnail Url
haggaie Aug 2, 2024
f53ee4b
RemotePlaylistManager: add helper method to get a playlist by its uid
haggaie Aug 2, 2024
0c6387a
media browser: expose remote playlists together with local playlists
haggaie Aug 2, 2024
189c70f
media browser: support searching
haggaie Aug 4, 2024
89bdfef
media browser: clean up Uri.parse() null checks
haggaie Aug 16, 2024
bf59f1e
Convert new and important files to Kotlin and optimize
snaik20 Sep 2, 2024
9f26137
media browser: kotlin fixes
haggaie Sep 5, 2024
7062009
media browser: remove leftover Java comments
haggaie Sep 17, 2024
fad463a
media browser: rename remote -> isRemote
haggaie Sep 17, 2024
b462c97
media browser: pass media ID to parsing error exceptions
haggaie Sep 17, 2024
c8ccc60
Addressed review comments
snaik20 Oct 12, 2024
9d750ed
Addressed review comments
snaik20 Dec 6, 2024
f6f7c37
PlayerService: return appropriate IBinder depending on the action
haggaie Dec 14, 2024
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
8 changes: 8 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService"/>
</intent-filter>
</service>

<activity
Expand Down Expand Up @@ -424,5 +427,10 @@
<meta-data
android:name="com.samsung.android.multidisplay.keep_process_alive"
android:value="true" />
<!-- Android Auto -->
<meta-data android:name="com.google.android.gms.car.application"
android:resource="@xml/automotive_app_desc" />
<meta-data android:name="com.google.android.gms.car.notification.SmallIcon"
android:resource="@mipmap/ic_launcher" />
</application>
</manifest>
13 changes: 0 additions & 13 deletions app/src/main/java/org/schabi/newpipe/database/LocalItem.java

This file was deleted.

18 changes: 18 additions & 0 deletions app/src/main/java/org/schabi/newpipe/database/LocalItem.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.schabi.newpipe.database

/**
* Represents a generic item that can be stored locally. This can be a playlist, a stream, etc.
*/
interface LocalItem {
/**
* The type of local item. Can be null if the type is unknown or not applicable.
*/
val localItemType: LocalItemType?

enum class LocalItemType {
PLAYLIST_LOCAL_ITEM,
PLAYLIST_REMOTE_ITEM,
PLAYLIST_STREAM_ITEM,
STATISTIC_STREAM_ITEM,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package org.schabi.newpipe.database.history.model
import androidx.room.ColumnInfo
import androidx.room.Embedded
import org.schabi.newpipe.database.stream.model.StreamEntity
import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.util.image.ImageStrategy
import java.time.OffsetDateTime

data class StreamHistoryEntry(
Expand All @@ -27,4 +29,17 @@ data class StreamHistoryEntry(
return this.streamEntity.uid == other.streamEntity.uid && streamId == other.streamId &&
accessDate.isEqual(other.accessDate)
}

fun toStreamInfoItem(): StreamInfoItem =
StreamInfoItem(
streamEntity.serviceId,
streamEntity.url,
streamEntity.title,
streamEntity.streamType,
).apply {
duration = streamEntity.duration
uploaderName = streamEntity.uploader
uploaderUrl = streamEntity.uploaderUrl
thumbnails = ImageStrategy.dbUrlToImageList(streamEntity.thumbnailUrl)
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.schabi.newpipe.database.playlist

import org.schabi.newpipe.database.LocalItem

/**
* Represents a playlist item stored locally.
*/
interface PlaylistLocalItem : LocalItem {
/**
* The name used for ordering this item within the playlist. Can be null.
*/
val orderingName: String?

/**
* The index used to display this item within the playlist.
*/
var displayIndex: Long

/**
* The unique identifier for this playlist item.
*/
val uid: Long

/**
* The URL of the thumbnail image for this playlist item. Can be null.
*/
val thumbnailUrl: String?
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,9 @@ public long getUid() {
public void setDisplayIndex(final long displayIndex) {
this.displayIndex = displayIndex;
}

@Override
public String getThumbnailUrl() {
return thumbnailUrl;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,20 @@ data class PlaylistStreamEntry(
@ColumnInfo(name = PlaylistStreamEntity.JOIN_INDEX)
val joinIndex: Int
) : LocalItem {

@Throws(IllegalArgumentException::class)
fun toStreamInfoItem(): StreamInfoItem {
val item = StreamInfoItem(streamEntity.serviceId, streamEntity.url, streamEntity.title, streamEntity.streamType)
item.duration = streamEntity.duration
item.uploaderName = streamEntity.uploader
item.uploaderUrl = streamEntity.uploaderUrl
item.thumbnails = ImageStrategy.dbUrlToImageList(streamEntity.thumbnailUrl)

return item
}

override fun getLocalItemType(): LocalItem.LocalItemType {
return LocalItem.LocalItemType.PLAYLIST_STREAM_ITEM
}
fun toStreamInfoItem() =
StreamInfoItem(
streamEntity.serviceId,
streamEntity.url,
streamEntity.title,
streamEntity.streamType,
).apply {
duration = streamEntity.duration
uploaderName = streamEntity.uploader
uploaderUrl = streamEntity.uploaderUrl
thumbnails = ImageStrategy.dbUrlToImageList(streamEntity.thumbnailUrl)
}

override val localItemType: LocalItem.LocalItemType
get() = LocalItem.LocalItemType.PLAYLIST_STREAM_ITEM
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,21 @@ class StreamStatisticsEntry(
@ColumnInfo(name = STREAM_WATCH_COUNT)
val watchCount: Long
) : LocalItem {
fun toStreamInfoItem(): StreamInfoItem {
val item = StreamInfoItem(streamEntity.serviceId, streamEntity.url, streamEntity.title, streamEntity.streamType)
item.duration = streamEntity.duration
item.uploaderName = streamEntity.uploader
item.uploaderUrl = streamEntity.uploaderUrl
item.thumbnails = ImageStrategy.dbUrlToImageList(streamEntity.thumbnailUrl)

return item
}

override fun getLocalItemType(): LocalItem.LocalItemType {
return LocalItem.LocalItemType.STATISTIC_STREAM_ITEM
}
fun toStreamInfoItem() =
StreamInfoItem(
streamEntity.serviceId,
streamEntity.url,
streamEntity.title,
streamEntity.streamType,
).apply {
duration = streamEntity.duration
uploaderName = streamEntity.uploader
uploaderUrl = streamEntity.uploaderUrl
thumbnails = ImageStrategy.dbUrlToImageList(streamEntity.thumbnailUrl)
}

override val localItemType: LocalItem.LocalItemType
get() = LocalItem.LocalItemType.STATISTIC_STREAM_ITEM

companion object {
const val STREAM_LATEST_DATE = "latestAccess"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,14 +229,15 @@ public final class VideoDetailFragment
private ContentObserver settingsContentObserver;
@Nullable
private PlayerService playerService;
@Nullable
private Player player;
private final PlayerHolder playerHolder = PlayerHolder.getInstance();

/*//////////////////////////////////////////////////////////////////////////
// Service management
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onServiceConnected(final Player connectedPlayer,
public void onServiceConnected(@Nullable final Player connectedPlayer,
final PlayerService connectedPlayerService,
final boolean playAfterConnect) {
player = connectedPlayer;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ public static Disposable createCorrespondingDialog(
* @return the disposable that was created
*/
public static Disposable showForPlayQueue(
final Player player,
@NonNull final Player player,
@NonNull final FragmentManager fragmentManager) {

final List<StreamEntity> streamEntities = Stream.of(player.getPlayQueue())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ public Flowable<List<PlaylistRemoteEntity>> getPlaylists() {
return playlistRemoteTable.getPlaylists().subscribeOn(Schedulers.io());
}

public Flowable<List<PlaylistRemoteEntity>> getPlaylist(final long playlistId) {
return playlistRemoteTable.getPlaylist(playlistId).subscribeOn(Schedulers.io());
}

public Flowable<List<PlaylistRemoteEntity>> getPlaylist(final PlaylistInfo info) {
return playlistRemoteTable.getPlaylist(info.getServiceId(), info.getUrl())
.subscribeOn(Schedulers.io());
Expand Down
27 changes: 18 additions & 9 deletions app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public final class PlayQueueActivity extends AppCompatActivity

private static final int MENU_ID_AUDIO_TRACK = 71;

@Nullable
private Player player;

private boolean serviceBound;
Expand Down Expand Up @@ -137,30 +138,38 @@ public boolean onOptionsItemSelected(final MenuItem item) {
NavigationHelper.openSettings(this);
return true;
case R.id.action_append_playlist:
PlaylistDialog.showForPlayQueue(player, getSupportFragmentManager());
if (player != null) {
PlaylistDialog.showForPlayQueue(player, getSupportFragmentManager());
}
return true;
case R.id.action_playback_speed:
openPlaybackParameterDialog();
return true;
case R.id.action_mute:
player.toggleMute();
if (player != null) {
player.toggleMute();
}
return true;
case R.id.action_system_audio:
startActivity(new Intent(Settings.ACTION_SOUND_SETTINGS));
return true;
case R.id.action_switch_main:
this.player.setRecovery();
NavigationHelper.playOnMainPlayer(this, player.getPlayQueue(), true);
if (player != null) {
this.player.setRecovery();
NavigationHelper.playOnMainPlayer(this, player.getPlayQueue(), true);
}
return true;
case R.id.action_switch_popup:
if (PermissionHelper.isPopupEnabledElseAsk(this)) {
if (PermissionHelper.isPopupEnabledElseAsk(this) && player != null) {
this.player.setRecovery();
NavigationHelper.playOnPopupPlayer(this, player.getPlayQueue(), true);
}
return true;
case R.id.action_switch_background:
this.player.setRecovery();
NavigationHelper.playOnBackgroundPlayer(this, player.getPlayQueue(), true);
if (player != null) {
this.player.setRecovery();
NavigationHelper.playOnBackgroundPlayer(this, player.getPlayQueue(), true);
}
return true;
}

Expand Down Expand Up @@ -309,7 +318,7 @@ public void onMove(final int sourceIndex, final int targetIndex) {

@Override
public void onSwiped(final int index) {
if (index != -1) {
if (index != -1 && player != null) {
player.getPlayQueue().remove(index);
}
}
Expand Down Expand Up @@ -659,7 +668,7 @@ private void buildAudioTrackMenu() {
* @param itemId index of the selected item
*/
private void onAudioTrackClick(final int itemId) {
if (player.getCurrentMetadata() == null) {
if (player == null || player.getCurrentMetadata() == null) {
return;
}
player.getCurrentMetadata().getMaybeAudioTrack().ifPresent(audioTrack -> {
Expand Down
13 changes: 10 additions & 3 deletions app/src/main/java/org/schabi/newpipe/player/Player.java
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@
import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.stream.AudioStream;
import org.schabi.newpipe.extractor.Image;
import org.schabi.newpipe.extractor.stream.AudioStream;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamType;
import org.schabi.newpipe.extractor.stream.VideoStream;
Expand Down Expand Up @@ -118,9 +118,9 @@
import org.schabi.newpipe.util.DependentPreferenceHelper;
import org.schabi.newpipe.util.ListHelper;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.image.PicassoHelper;
import org.schabi.newpipe.util.SerializedCache;
import org.schabi.newpipe.util.StreamTypeUtil;
import org.schabi.newpipe.util.image.PicassoHelper;

import java.util.List;
import java.util.Optional;
Expand Down 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 Expand Up @@ -415,6 +415,13 @@ public void handleIntent(@NonNull final Intent intent) {
== com.google.android.exoplayer2.Player.STATE_IDLE) {
simpleExoPlayer.prepare();
}
// Seeks to a specific index and position in the player if the queue index has changed.
if (playQueue.getIndex() != newQueue.getIndex()) {
final PlayQueueItem queueItem = newQueue.getItem();
if (queueItem != null) {
simpleExoPlayer.seekTo(newQueue.getIndex(), queueItem.getRecoveryPosition());
}
}
simpleExoPlayer.setPlayWhenReady(playWhenReady);

} else if (intent.getBooleanExtra(RESUME_PLAYBACK, false)
Expand Down
Loading
Loading