mirror of
https://github.com/antebudimir/tempus.git
synced 2026-01-01 18:03:33 +00:00
First basic implementation of cast functionality
This commit is contained in:
parent
2d82007abd
commit
75bad72d83
7 changed files with 63 additions and 12 deletions
|
|
@ -54,6 +54,9 @@ dependencies {
|
||||||
implementation "androidx.cardview:cardview:1.0.0"
|
implementation "androidx.cardview:cardview:1.0.0"
|
||||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||||
|
|
||||||
|
// Google GMS
|
||||||
|
implementation 'com.google.android.gms:play-services-cast-framework:21.0.0'
|
||||||
|
|
||||||
// Android Material
|
// Android Material
|
||||||
implementation 'com.google.android.material:material:1.4.0'
|
implementation 'com.google.android.material:material:1.4.0'
|
||||||
|
|
||||||
|
|
@ -70,6 +73,7 @@ dependencies {
|
||||||
implementation "androidx.media3:media3-common:1.0.0-alpha01"
|
implementation "androidx.media3:media3-common:1.0.0-alpha01"
|
||||||
implementation "androidx.media3:media3-exoplayer:1.0.0-alpha01"
|
implementation "androidx.media3:media3-exoplayer:1.0.0-alpha01"
|
||||||
implementation "androidx.media3:media3-ui:1.0.0-alpha01"
|
implementation "androidx.media3:media3-ui:1.0.0-alpha01"
|
||||||
|
implementation "androidx.media3:media3-cast:1.0.0-alpha01"
|
||||||
|
|
||||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
|
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
|
||||||
annotationProcessor 'androidx.room:room-compiler:2.4.0'
|
annotationProcessor 'androidx.room:room-compiler:2.4.0'
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,11 @@
|
||||||
android:theme="@style/AppTheme"
|
android:theme="@style/AppTheme"
|
||||||
android:usesCleartextTraffic="true"
|
android:usesCleartextTraffic="true"
|
||||||
android:allowBackup="false">
|
android:allowBackup="false">
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
|
||||||
|
android:value="androidx.media3.cast.DefaultCastOptionsProvider"/>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.activity.MainActivity"
|
android:name=".ui.activity.MainActivity"
|
||||||
android:windowSoftInputMode="adjustPan|adjustResize"
|
android:windowSoftInputMode="adjustPan|adjustResize"
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,15 @@ import android.app.PendingIntent;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.media3.cast.CastPlayer;
|
||||||
|
import androidx.media3.cast.SessionAvailabilityListener;
|
||||||
import androidx.media3.common.AudioAttributes;
|
import androidx.media3.common.AudioAttributes;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.MediaItem;
|
import androidx.media3.common.MediaItem;
|
||||||
|
import androidx.media3.common.MimeTypes;
|
||||||
import androidx.media3.common.Player;
|
import androidx.media3.common.Player;
|
||||||
import androidx.media3.datasource.DataSource;
|
import androidx.media3.datasource.DataSource;
|
||||||
import androidx.media3.exoplayer.ExoPlayer;
|
import androidx.media3.exoplayer.ExoPlayer;
|
||||||
import androidx.media3.exoplayer.analytics.AnalyticsListener;
|
|
||||||
import androidx.media3.exoplayer.analytics.PlaybackStatsListener;
|
|
||||||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
|
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
|
||||||
import androidx.media3.exoplayer.source.MediaSourceFactory;
|
import androidx.media3.exoplayer.source.MediaSourceFactory;
|
||||||
import androidx.media3.session.MediaLibraryService;
|
import androidx.media3.session.MediaLibraryService;
|
||||||
|
|
@ -20,14 +21,16 @@ import androidx.media3.session.MediaSession;
|
||||||
|
|
||||||
import com.cappielloantonio.play.ui.activity.MainActivity;
|
import com.cappielloantonio.play.ui.activity.MainActivity;
|
||||||
import com.cappielloantonio.play.util.DownloadUtil;
|
import com.cappielloantonio.play.util.DownloadUtil;
|
||||||
|
import com.google.android.gms.cast.framework.CastContext;
|
||||||
|
|
||||||
public class MediaService extends MediaLibraryService {
|
@SuppressLint("UnsafeOptInUsageError")
|
||||||
|
public class MediaService extends MediaLibraryService implements SessionAvailabilityListener {
|
||||||
private static final String TAG = "MediaService";
|
private static final String TAG = "MediaService";
|
||||||
|
|
||||||
public static final int REQUEST_CODE = 432;
|
public static final int REQUEST_CODE = 432;
|
||||||
|
|
||||||
private ExoPlayer player;
|
private ExoPlayer player;
|
||||||
private DataSource.Factory dataSourceFactory;
|
private CastPlayer castPlayer;
|
||||||
private MediaSourceFactory mediaSourceFactory;
|
private MediaSourceFactory mediaSourceFactory;
|
||||||
private MediaLibrarySession mediaLibrarySession;
|
private MediaLibrarySession mediaLibrarySession;
|
||||||
|
|
||||||
|
|
@ -36,7 +39,11 @@ public class MediaService extends MediaLibraryService {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
initializeMediaSource();
|
initializeMediaSource();
|
||||||
initializePlayer();
|
initializePlayer();
|
||||||
|
initializeCastPlayer();
|
||||||
|
initializeMediaLibrarySession();
|
||||||
initializePlayerListener();
|
initializePlayerListener();
|
||||||
|
|
||||||
|
setPlayer(null, castPlayer.isCastSessionAvailable() ? castPlayer : player);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -51,13 +58,11 @@ public class MediaService extends MediaLibraryService {
|
||||||
return mediaLibrarySession;
|
return mediaLibrarySession;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("UnsafeOptInUsageError")
|
|
||||||
private void initializeMediaSource() {
|
private void initializeMediaSource() {
|
||||||
dataSourceFactory = DownloadUtil.getDataSourceFactory(this);
|
DataSource.Factory dataSourceFactory = DownloadUtil.getDataSourceFactory(this);
|
||||||
mediaSourceFactory = new DefaultMediaSourceFactory(dataSourceFactory);
|
mediaSourceFactory = new DefaultMediaSourceFactory(dataSourceFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("UnsafeOptInUsageError")
|
|
||||||
private void initializePlayer() {
|
private void initializePlayer() {
|
||||||
player = new ExoPlayer.Builder(this)
|
player = new ExoPlayer.Builder(this)
|
||||||
.setMediaSourceFactory(mediaSourceFactory)
|
.setMediaSourceFactory(mediaSourceFactory)
|
||||||
|
|
@ -65,15 +70,40 @@ public class MediaService extends MediaLibraryService {
|
||||||
.setHandleAudioBecomingNoisy(true)
|
.setHandleAudioBecomingNoisy(true)
|
||||||
.setWakeMode(C.WAKE_MODE_NETWORK)
|
.setWakeMode(C.WAKE_MODE_NETWORK)
|
||||||
.build();
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeCastPlayer() {
|
||||||
|
castPlayer = new CastPlayer(CastContext.getSharedInstance(this));
|
||||||
|
castPlayer.setSessionAvailabilityListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeMediaLibrarySession() {
|
||||||
mediaLibrarySession = new MediaLibrarySession.Builder(this, player, new LibrarySessionCallback())
|
mediaLibrarySession = new MediaLibrarySession.Builder(this, player, new LibrarySessionCallback())
|
||||||
.setMediaItemFiller(new CustomMediaItemFiller())
|
.setMediaItemFiller(new CustomMediaItemFiller())
|
||||||
.setSessionActivity(PendingIntent.getActivity(getApplicationContext(), REQUEST_CODE, new Intent(getApplicationContext(), MainActivity.class), PendingIntent.FLAG_IMMUTABLE))
|
.setSessionActivity(PendingIntent.getActivity(getApplicationContext(), REQUEST_CODE, new Intent(getApplicationContext(), MainActivity.class), PendingIntent.FLAG_IMMUTABLE))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("UnsafeOptInUsageError")
|
private void setPlayer(Player oldPlayer, Player newPlayer) {
|
||||||
|
if (oldPlayer == newPlayer) return;
|
||||||
|
if (oldPlayer != null) oldPlayer.stop();
|
||||||
|
|
||||||
|
mediaLibrarySession.setPlayer(newPlayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCastSessionAvailable() {
|
||||||
|
setPlayer(player, castPlayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCastSessionUnavailable() {
|
||||||
|
setPlayer(castPlayer, player);
|
||||||
|
}
|
||||||
|
|
||||||
private void releasePlayer() {
|
private void releasePlayer() {
|
||||||
|
castPlayer.setSessionAvailabilityListener(null);
|
||||||
|
castPlayer.release();
|
||||||
player.release();
|
player.release();
|
||||||
mediaLibrarySession.release();
|
mediaLibrarySession.release();
|
||||||
}
|
}
|
||||||
|
|
@ -81,18 +111,17 @@ public class MediaService extends MediaLibraryService {
|
||||||
private class LibrarySessionCallback implements MediaLibrarySession.MediaLibrarySessionCallback {
|
private class LibrarySessionCallback implements MediaLibrarySession.MediaLibrarySessionCallback {
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("UnsafeOptInUsageError")
|
|
||||||
private class CustomMediaItemFiller implements MediaSession.MediaItemFiller {
|
private class CustomMediaItemFiller implements MediaSession.MediaItemFiller {
|
||||||
@Override
|
@Override
|
||||||
public MediaItem fillInLocalConfiguration(MediaSession session, MediaSession.ControllerInfo controller, MediaItem mediaItem) {
|
public MediaItem fillInLocalConfiguration(MediaSession session, MediaSession.ControllerInfo controller, MediaItem mediaItem) {
|
||||||
return mediaItem.buildUpon()
|
return mediaItem.buildUpon()
|
||||||
.setUri(mediaItem.mediaMetadata.mediaUri)
|
.setUri(mediaItem.mediaMetadata.mediaUri)
|
||||||
.setMediaMetadata(mediaItem.mediaMetadata)
|
.setMediaMetadata(mediaItem.mediaMetadata)
|
||||||
|
.setMimeType(MimeTypes.BASE_TYPE_AUDIO)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("UnsafeOptInUsageError")
|
|
||||||
private void initializePlayerListener() {
|
private void initializePlayerListener() {
|
||||||
player.addListener(new Player.Listener() {
|
player.addListener(new Player.Listener() {
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -103,7 +132,7 @@ public class MediaService extends MediaLibraryService {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onIsPlayingChanged(boolean isPlaying) {
|
public void onIsPlayingChanged(boolean isPlaying) {
|
||||||
if(isPlaying) {
|
if (isPlaying) {
|
||||||
MediaManager.setPlayingPausedTimestamp(player.getCurrentMediaItem(), player.getCurrentPosition());
|
MediaManager.setPlayingPausedTimestamp(player.getCurrentMediaItem(), player.getCurrentPosition());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ import com.cappielloantonio.play.ui.activity.MainActivity;
|
||||||
import com.cappielloantonio.play.util.MusicUtil;
|
import com.cappielloantonio.play.util.MusicUtil;
|
||||||
import com.cappielloantonio.play.util.UIUtil;
|
import com.cappielloantonio.play.util.UIUtil;
|
||||||
import com.cappielloantonio.play.viewmodel.HomeViewModel;
|
import com.cappielloantonio.play.viewmodel.HomeViewModel;
|
||||||
|
import com.google.android.gms.cast.framework.CastButtonFactory;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
@ -79,6 +80,7 @@ public class HomeFragment extends Fragment {
|
||||||
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
||||||
super.onCreateOptionsMenu(menu, inflater);
|
super.onCreateOptionsMenu(menu, inflater);
|
||||||
inflater.inflate(R.menu.main_page_menu, menu);
|
inflater.inflate(R.menu.main_page_menu, menu);
|
||||||
|
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.media_route_menu_item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
package com.cappielloantonio.play.util;
|
package com.cappielloantonio.play.util;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import androidx.media3.common.MediaItem;
|
import androidx.media3.common.MediaItem;
|
||||||
import androidx.media3.common.MediaMetadata;
|
import androidx.media3.common.MediaMetadata;
|
||||||
|
import androidx.media3.common.MimeTypes;
|
||||||
|
|
||||||
import com.cappielloantonio.play.model.Album;
|
import com.cappielloantonio.play.model.Album;
|
||||||
import com.cappielloantonio.play.model.Artist;
|
import com.cappielloantonio.play.model.Artist;
|
||||||
|
|
@ -197,6 +199,7 @@ public class MappingUtil {
|
||||||
return genres;
|
return genres;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("UnsafeOptInUsageError")
|
||||||
public static MediaItem mapMediaItem(Context context, Song song, boolean stream) {
|
public static MediaItem mapMediaItem(Context context, Song song, boolean stream) {
|
||||||
boolean isDownloaded = DownloadUtil.getDownloadTracker(context).isDownloaded(MusicUtil.getSongDownloadUri(song));
|
boolean isDownloaded = DownloadUtil.getDownloadTracker(context).isDownloaded(MusicUtil.getSongDownloadUri(song));
|
||||||
|
|
||||||
|
|
@ -219,6 +222,7 @@ public class MappingUtil {
|
||||||
.setExtras(bundle)
|
.setExtras(bundle)
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
|
.setMimeType(MimeTypes.BASE_TYPE_AUDIO)
|
||||||
.setUri(stream && !isDownloaded ? MusicUtil.getSongStreamUri(context, song) : MusicUtil.getSongDownloadUri(song))
|
.setUri(stream && !isDownloaded ? MusicUtil.getSongStreamUri(context, song) : MusicUtil.getSongDownloadUri(song))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,13 @@
|
||||||
android:id="@+id/action_search"
|
android:id="@+id/action_search"
|
||||||
android:icon="@drawable/ic_search"
|
android:icon="@drawable/ic_search"
|
||||||
android:title="@string/menu_search_button"
|
android:title="@string/menu_search_button"
|
||||||
app:showAsAction="ifRoom" />
|
app:showAsAction="always" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/media_route_menu_item"
|
||||||
|
android:title="@string/media_route_menu_title"
|
||||||
|
app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
|
||||||
|
app:showAsAction="always" />
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_settings"
|
android:id="@+id/action_settings"
|
||||||
|
|
|
||||||
|
|
@ -212,4 +212,5 @@
|
||||||
<string name="settings_summary_transcoding">Priority given to the transcoding mode. If set to \"Direct play\" the bitrate of the file will not be changed.</string>
|
<string name="settings_summary_transcoding">Priority given to the transcoding mode. If set to \"Direct play\" the bitrate of the file will not be changed.</string>
|
||||||
<string name="notification_channel_name">playNotificationChannelName</string>
|
<string name="notification_channel_name">playNotificationChannelName</string>
|
||||||
<string name="notification_channel_description">playNotificationChannelNameDescription</string>
|
<string name="notification_channel_description">playNotificationChannelNameDescription</string>
|
||||||
|
<string name="media_route_menu_title">Cast</string>
|
||||||
</resources>
|
</resources>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue