Better Cast
Now there won't be any need of ugly cast mini player as cast will be able to respond to UI events of app like seek, changing song, pause
This commit is contained in:
parent
70e323eb0a
commit
4b4aadcc5b
7 changed files with 127 additions and 205 deletions
|
@ -1,7 +1,6 @@
|
|||
package code.name.monkey.retromusic.activities.base
|
||||
|
||||
import android.os.Bundle
|
||||
import code.name.monkey.retromusic.cast.CastHelper
|
||||
import code.name.monkey.retromusic.cast.RetroSessionManagerListener
|
||||
import code.name.monkey.retromusic.cast.RetroWebServer
|
||||
import code.name.monkey.retromusic.helper.MusicPlayerRemote
|
||||
|
@ -30,20 +29,7 @@ abstract class AbsCastActivity : AbsSlidingMusicPanelActivity() {
|
|||
override fun onSessionStarted(castSession: CastSession, p1: String) {
|
||||
invalidateOptionsMenu()
|
||||
mCastSession = castSession
|
||||
loadCastQueue()
|
||||
MusicPlayerRemote.isCasting = true
|
||||
setAllowDragging(false)
|
||||
collapsePanel()
|
||||
}
|
||||
|
||||
override fun onSessionEnding(castSession: CastSession) {
|
||||
MusicPlayerRemote.isCasting = false
|
||||
castSession.remoteMediaClient?.let {
|
||||
val position = it.mediaQueue.indexOfItemWithId(it.currentItem?.itemId ?: 0)
|
||||
val progress = it.approximateStreamPosition
|
||||
MusicPlayerRemote.position = position
|
||||
MusicPlayerRemote.seekTo(progress.toInt())
|
||||
}
|
||||
MusicPlayerRemote.switchToRemotePlayback(castSession)
|
||||
}
|
||||
|
||||
override fun onSessionEnded(castSession: CastSession, p1: Int) {
|
||||
|
@ -51,7 +37,7 @@ abstract class AbsCastActivity : AbsSlidingMusicPanelActivity() {
|
|||
if (mCastSession == castSession) {
|
||||
mCastSession = null
|
||||
}
|
||||
setAllowDragging(true)
|
||||
MusicPlayerRemote.switchToLocalPlayback()
|
||||
webServer.stop()
|
||||
}
|
||||
|
||||
|
@ -59,14 +45,7 @@ abstract class AbsCastActivity : AbsSlidingMusicPanelActivity() {
|
|||
invalidateOptionsMenu()
|
||||
mCastSession = castSession
|
||||
webServer.start()
|
||||
mCastSession?.remoteMediaClient?.let {
|
||||
loadCastQueue(it.mediaQueue.indexOfItemWithId(it.currentItem?.itemId ?: 0),
|
||||
it.approximateStreamPosition)
|
||||
}
|
||||
|
||||
MusicPlayerRemote.isCasting = true
|
||||
setAllowDragging(false)
|
||||
collapsePanel()
|
||||
MusicPlayerRemote.switchToRemotePlayback(castSession)
|
||||
}
|
||||
|
||||
override fun onSessionSuspended(castSession: CastSession, p1: Int) {
|
||||
|
@ -74,8 +53,7 @@ abstract class AbsCastActivity : AbsSlidingMusicPanelActivity() {
|
|||
if (mCastSession == castSession) {
|
||||
mCastSession = null
|
||||
}
|
||||
MusicPlayerRemote.isCasting = false
|
||||
setAllowDragging(true)
|
||||
MusicPlayerRemote.switchToLocalPlayback()
|
||||
webServer.stop()
|
||||
}
|
||||
}
|
||||
|
@ -121,27 +99,4 @@ abstract class AbsCastActivity : AbsSlidingMusicPanelActivity() {
|
|||
mCastSession = null
|
||||
}
|
||||
}
|
||||
|
||||
fun loadCastQueue(
|
||||
position: Int = MusicPlayerRemote.position,
|
||||
progress: Long = MusicPlayerRemote.songProgressMillis.toLong(),
|
||||
) {
|
||||
mCastSession?.let {
|
||||
if (MusicPlayerRemote.playingQueue.isNotEmpty()) {
|
||||
CastHelper.castQueue(
|
||||
it,
|
||||
MusicPlayerRemote.playingQueue,
|
||||
position,
|
||||
progress
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onQueueChanged() {
|
||||
super.onQueueChanged()
|
||||
if (playServicesAvailable) {
|
||||
loadCastQueue()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -393,9 +393,7 @@ abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
|
|||
animate: Boolean = false,
|
||||
isBottomNavVisible: Boolean = bottomNavigationView.isVisible,
|
||||
) {
|
||||
val heightOfBar =
|
||||
windowInsets.safeGetBottomInsets() +
|
||||
if (MusicPlayerRemote.isCasting) dip(R.dimen.cast_mini_player_height) else dip(R.dimen.mini_player_height)
|
||||
val heightOfBar = windowInsets.safeGetBottomInsets() + dip(R.dimen.mini_player_height)
|
||||
val heightOfBarWithTabs = heightOfBar + dip(R.dimen.bottom_nav_height)
|
||||
if (hide) {
|
||||
bottomSheetBehavior.peekHeight = -windowInsets.safeGetBottomInsets()
|
||||
|
|
|
@ -6,12 +6,11 @@ import code.name.monkey.retromusic.cast.RetroWebServer.Companion.PART_COVER_ART
|
|||
import code.name.monkey.retromusic.cast.RetroWebServer.Companion.PART_SONG
|
||||
import code.name.monkey.retromusic.model.Song
|
||||
import code.name.monkey.retromusic.util.RetroUtil
|
||||
import com.google.android.gms.cast.*
|
||||
import com.google.android.gms.cast.MediaInfo
|
||||
import com.google.android.gms.cast.MediaInfo.STREAM_TYPE_BUFFERED
|
||||
import com.google.android.gms.cast.MediaMetadata
|
||||
import com.google.android.gms.cast.MediaMetadata.*
|
||||
import com.google.android.gms.cast.framework.CastSession
|
||||
import com.google.android.gms.common.images.WebImage
|
||||
import org.json.JSONObject
|
||||
import java.net.MalformedURLException
|
||||
import java.net.URL
|
||||
|
||||
|
@ -21,39 +20,7 @@ object CastHelper {
|
|||
private const val CAST_MUSIC_METADATA_ALBUM_ID = "metadata_album_id"
|
||||
private const val CAST_URL_PROTOCOL = "http"
|
||||
|
||||
fun castSong(castSession: CastSession, song: Song) {
|
||||
try {
|
||||
val remoteMediaClient = castSession.remoteMediaClient
|
||||
val mediaLoadOptions = MediaLoadOptions.Builder().apply {
|
||||
setPlayPosition(0)
|
||||
setAutoplay(true)
|
||||
}.build()
|
||||
remoteMediaClient?.load(song.toMediaInfo()!!, mediaLoadOptions)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
fun castQueue(castSession: CastSession, songs: List<Song>, position: Int, progress: Long) {
|
||||
try {
|
||||
val remoteMediaClient = castSession.remoteMediaClient
|
||||
remoteMediaClient?.queueLoad(
|
||||
songs.toMediaInfoList(),
|
||||
if (position != -1) position else 0,
|
||||
MediaStatus.REPEAT_MODE_REPEAT_OFF,
|
||||
progress,
|
||||
JSONObject()
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<Song>.toMediaInfoList(): Array<MediaQueueItem> {
|
||||
return map { MediaQueueItem.Builder(it.toMediaInfo()!!).build() }.toTypedArray()
|
||||
}
|
||||
|
||||
private fun Song.toMediaInfo(): MediaInfo? {
|
||||
fun Song.toMediaInfo(): MediaInfo? {
|
||||
val song = this
|
||||
val baseUrl: URL
|
||||
try {
|
||||
|
|
|
@ -30,6 +30,7 @@ import code.name.monkey.retromusic.repository.SongRepository
|
|||
import code.name.monkey.retromusic.service.MusicService
|
||||
import code.name.monkey.retromusic.util.PreferenceUtil
|
||||
import code.name.monkey.retromusic.util.getExternalStorageDirectory
|
||||
import com.google.android.gms.cast.framework.CastSession
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import java.io.File
|
||||
|
@ -43,14 +44,6 @@ object MusicPlayerRemote : KoinComponent {
|
|||
|
||||
private val songRepository by inject<SongRepository>()
|
||||
|
||||
var isCasting: Boolean = false
|
||||
set(value) {
|
||||
field = value
|
||||
if (value) {
|
||||
musicService?.quit()
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
val isPlaying: Boolean
|
||||
get() = musicService != null && musicService!!.isPlaying
|
||||
|
@ -472,6 +465,14 @@ object MusicPlayerRemote : KoinComponent {
|
|||
.dropLastWhile { it.isEmpty() }.toTypedArray()[1]
|
||||
}
|
||||
|
||||
fun switchToRemotePlayback(castSession: CastSession) {
|
||||
musicService?.switchToRemotePlayback(castSession)
|
||||
}
|
||||
|
||||
fun switchToLocalPlayback() {
|
||||
musicService?.switchToLocalPlayback()
|
||||
}
|
||||
|
||||
class ServiceBinder internal constructor(private val mCallback: ServiceConnection?) :
|
||||
ServiceConnection {
|
||||
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
package code.name.monkey.retromusic.service
|
||||
|
||||
import code.name.monkey.retromusic.cast.CastHelper.toMediaInfo
|
||||
import code.name.monkey.retromusic.model.Song
|
||||
import code.name.monkey.retromusic.service.playback.Playback
|
||||
import com.google.android.gms.cast.MediaLoadOptions
|
||||
import com.google.android.gms.cast.MediaSeekOptions
|
||||
import com.google.android.gms.cast.MediaStatus
|
||||
import com.google.android.gms.cast.framework.CastSession
|
||||
import com.google.android.gms.cast.framework.media.RemoteMediaClient
|
||||
|
||||
class CastPlayer(castSession: CastSession) : Playback,
|
||||
RemoteMediaClient.Callback() {
|
||||
|
||||
override val isInitialized: Boolean = true
|
||||
|
||||
private val remoteMediaClient: RemoteMediaClient? = castSession.remoteMediaClient
|
||||
|
||||
init {
|
||||
remoteMediaClient?.registerCallback(this)
|
||||
}
|
||||
|
||||
private var isActuallyPlaying = false
|
||||
|
||||
override val isPlaying: Boolean
|
||||
get() {
|
||||
return remoteMediaClient?.isPlaying == true || isActuallyPlaying
|
||||
}
|
||||
|
||||
override val audioSessionId: Int = 0
|
||||
|
||||
private var callbacks: Playback.PlaybackCallbacks? = null
|
||||
|
||||
override fun setDataSource(
|
||||
song: Song,
|
||||
force: Boolean,
|
||||
completion: (success: Boolean) -> Unit,
|
||||
) {
|
||||
try {
|
||||
val mediaLoadOptions =
|
||||
MediaLoadOptions.Builder().setPlayPosition(0).setAutoplay(true).build()
|
||||
remoteMediaClient?.load(song.toMediaInfo()!!, mediaLoadOptions)
|
||||
completion(true)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
completion(false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun setNextDataSource(path: String?) {}
|
||||
|
||||
override fun setCallbacks(callbacks: Playback.PlaybackCallbacks) {
|
||||
this.callbacks = callbacks
|
||||
}
|
||||
|
||||
override fun start(): Boolean {
|
||||
isActuallyPlaying = true
|
||||
remoteMediaClient?.play()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
isActuallyPlaying = false
|
||||
remoteMediaClient?.stop()
|
||||
}
|
||||
|
||||
override fun release() {
|
||||
stop()
|
||||
}
|
||||
|
||||
override fun pause(): Boolean {
|
||||
isActuallyPlaying = false
|
||||
remoteMediaClient?.pause()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun duration(): Int {
|
||||
return remoteMediaClient?.streamDuration?.toInt() ?: 0
|
||||
}
|
||||
|
||||
override fun position(): Int {
|
||||
return remoteMediaClient?.approximateStreamPosition?.toInt() ?: 0
|
||||
}
|
||||
|
||||
override fun seek(whereto: Int): Int {
|
||||
remoteMediaClient?.seek(MediaSeekOptions.Builder().setPosition(whereto.toLong()).build())
|
||||
return whereto
|
||||
}
|
||||
|
||||
override fun setVolume(vol: Float) = true
|
||||
|
||||
override fun setAudioSessionId(sessionId: Int) = true
|
||||
|
||||
override fun setCrossFadeDuration(duration: Int) {}
|
||||
|
||||
override fun onStatusUpdated() {
|
||||
when (remoteMediaClient?.playerState) {
|
||||
MediaStatus.PLAYER_STATE_IDLE -> {
|
||||
val idleReason = remoteMediaClient.idleReason
|
||||
if (idleReason == MediaStatus.IDLE_REASON_FINISHED) {
|
||||
callbacks?.onTrackEnded()
|
||||
}
|
||||
}
|
||||
MediaStatus.PLAYER_STATE_PLAYING, MediaStatus.PLAYER_STATE_PAUSED -> {
|
||||
callbacks?.onPlayStateChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,102 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/container_all"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="0dp">
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/container_current"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/cast_mini_controller_height">
|
||||
|
||||
<View
|
||||
android:id="@+id/center"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:visibility="invisible" />
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/icon_container"
|
||||
android:layout_width="@dimen/cast_mini_controller_image_size"
|
||||
android:layout_height="@dimen/cast_mini_controller_image_size"
|
||||
android:layout_margin="8dp"
|
||||
app:cardCornerRadius="10dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignParentStart="true"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/default_audio_art" />
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/button_0"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginEnd="@dimen/cast_mini_controller_control_button_margin"
|
||||
android:layout_toStartOf="@+id/button_1" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/button_1"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginEnd="@dimen/cast_mini_controller_control_button_margin"
|
||||
android:layout_toStartOf="@+id/button_2" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/button_2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginEnd="8dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_above="@+id/center"
|
||||
android:layout_marginStart="15dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="-3dp"
|
||||
android:layout_toStartOf="@+id/button_0"
|
||||
android:layout_toEndOf="@+id/icon_container"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textAppearance="?attr/castTitleTextAppearance" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/subtitle_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/center"
|
||||
android:layout_alignStart="@+id/title_view"
|
||||
android:layout_alignEnd="@+id/title_view"
|
||||
android:layout_marginTop="3dp"
|
||||
android:layout_toStartOf="@+id/button_0"
|
||||
android:layout_toEndOf="@+id/icon_container"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textAppearance="?attr/castSubtitleTextAppearance" />
|
||||
|
||||
<!-- Note: The ProgressBar is deliberately laid out in absolute LTR -->
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
style="@android:style/Widget.ProgressBar.Horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="5dp"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layoutDirection="ltr"
|
||||
android:progressDrawable="@drawable/cast_mini_controller_progress_drawable" />
|
||||
</RelativeLayout>
|
||||
|
||||
</LinearLayout>
|
|
@ -11,8 +11,8 @@
|
|||
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:defaultNavHost="true"
|
||||
android:background="?attr/colorSurface"
|
||||
app:defaultNavHost="true"
|
||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
|
||||
tools:layout="@layout/fragment_home" />
|
||||
|
||||
|
@ -39,12 +39,6 @@
|
|||
android:layout_height="@dimen/mini_player_height"
|
||||
tools:layout="@layout/fragment_mini_player" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/castMiniController"
|
||||
class="com.google.android.gms.cast.framework.media.widget.MiniControllerFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<code.name.monkey.retromusic.views.BottomNavigationBarTinted
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue