media3 playback related code, not working though

This commit is contained in:
Gary Wang 2021-11-23 00:08:30 +08:00
parent 2e4bca966a
commit b614106dd9
5 changed files with 200 additions and 42 deletions

View File

@ -23,6 +23,15 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<service
android:name=".service.PlaybackService"
android:exported="true">
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService"/>
</intent-filter>
</service>
</application> </application>
</manifest> </manifest>

View File

@ -1,5 +1,6 @@
package net.blumia.pcmdroid package net.blumia.pcmdroid
import android.content.ComponentName
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
@ -9,12 +10,25 @@ import androidx.compose.material.Surface
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.media3.common.MediaItem
import androidx.media3.common.util.UnstableApi
import androidx.media3.session.LibraryResult
import androidx.media3.session.MediaBrowser
import androidx.media3.session.SessionToken
import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.MoreExecutors
import net.blumia.pcmdroid.service.PlaybackService
import net.blumia.pcmdroid.ui.NavGraph import net.blumia.pcmdroid.ui.NavGraph
import net.blumia.pcmdroid.ui.screen.main.MainPlayer import net.blumia.pcmdroid.ui.screen.main.MainPlayer
import net.blumia.pcmdroid.ui.theme.PrivateCloudMusicTheme import net.blumia.pcmdroid.ui.theme.PrivateCloudMusicTheme
import java.lang.Exception
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
private lateinit var browserFuture: ListenableFuture<MediaBrowser>
private val browser: MediaBrowser?
get() = if (browserFuture.isDone) browserFuture.get() else null
private val model: MainViewModel by viewModels { private val model: MainViewModel by viewModels {
MainViewModelFactory((application as MainApplication).repository) MainViewModelFactory((application as MainApplication).repository)
} }
@ -25,9 +39,49 @@ class MainActivity : ComponentActivity() {
PrivateCloudMusicTheme { PrivateCloudMusicTheme {
// A surface container using the 'background' color from the theme // A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) { Surface(color = MaterialTheme.colors.background) {
NavGraph(model) NavGraph(model, browser)
} }
} }
} }
} }
override fun onStart() {
super.onStart()
initializeBrowser()
}
override fun onStop() {
releaseBrowser()
super.onStop()
}
@androidx.annotation.OptIn(UnstableApi::class)
private fun initializeBrowser() {
browserFuture =
MediaBrowser.Builder(
this,
SessionToken(this, ComponentName(this, PlaybackService::class.java))
)
.buildAsync()
browserFuture.addListener({ pushRoot() }, MoreExecutors.directExecutor())
}
private fun releaseBrowser() {
MediaBrowser.releaseFuture(browserFuture)
}
private fun pushRoot() {
// browser can be initialized many times
// only push root at the first initialization
val browser = this.browser ?: return
val rootFuture = browser.getLibraryRoot(/* params= */ null)
rootFuture.addListener(
{
val result: LibraryResult<MediaItem> = rootFuture.get()!!
val root: MediaItem = result.value!!
// pushPathStack(root)
},
MoreExecutors.directExecutor()
)
}
} }

View File

@ -0,0 +1,61 @@
package net.blumia.pcmdroid.service
import android.app.PendingIntent.FLAG_IMMUTABLE
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
import android.content.Intent
import android.os.Build
import androidx.core.app.TaskStackBuilder
import androidx.media3.common.AudioAttributes
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.session.MediaLibraryService
import androidx.media3.session.MediaSession
import net.blumia.pcmdroid.MainActivity
class PlaybackService : MediaLibraryService() {
private lateinit var player: ExoPlayer
private lateinit var mediaLibrarySession: MediaLibrarySession
private val librarySessionCallback = CustomMediaLibrarySessionCallback()
private inner class CustomMediaLibrarySessionCallback
: MediaLibrarySession.MediaLibrarySessionCallback
{
}
override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaLibrarySession {
return mediaLibrarySession
}
override fun onCreate() {
super.onCreate()
initializeSessionAndPlayer()
}
override fun onDestroy() {
player.release()
mediaLibrarySession.release()
super.onDestroy()
}
private fun initializeSessionAndPlayer() {
player =
ExoPlayer.Builder(this)
.setAudioAttributes(AudioAttributes.DEFAULT, true)
.build()
val mainActivityIntent = Intent(this, MainActivity::class.java)
val pendingIntent =
TaskStackBuilder.create(this).run {
addNextIntent(mainActivityIntent)
val immutableFlag = if (Build.VERSION.SDK_INT >= 23) FLAG_IMMUTABLE else 0
getPendingIntent(0, immutableFlag or FLAG_UPDATE_CURRENT)
}
mediaLibrarySession =
MediaLibrarySession.Builder(this, player, librarySessionCallback)
// .setMediaItemFiller(CustomMediaItemFiller())
.setSessionActivity(pendingIntent!!)
.build()
}
}

View File

@ -5,6 +5,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.livedata.observeAsState
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.media3.common.MediaItem
import androidx.media3.session.MediaBrowser
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.NavType import androidx.navigation.NavType
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
@ -30,6 +32,7 @@ object MainDestinations {
@Composable @Composable
fun NavGraph( fun NavGraph(
viewModel: MainViewModel, viewModel: MainViewModel,
browser: MediaBrowser?,
navController: NavHostController = rememberNavController(), navController: NavHostController = rememberNavController(),
startDestination: String = MainDestinations.MAIN_ROUTE, startDestination: String = MainDestinations.MAIN_ROUTE,
) { ) {
@ -62,6 +65,16 @@ fun NavGraph(
onFolderItemClicked = { folder -> onFolderItemClicked = { folder ->
viewModel.fetchFolder(folder) viewModel.fetchFolder(folder)
}, },
onSongItemClicked = { song, songs ->
Log.d("vvv", song.url + browser.toString())
val playlist = browser ?: return@MainPlayer
val item = MediaItem.Builder().setUri(song.url).build()
playlist.setMediaItem(item)
playlist.prepare()
playlist.play()
},
onEditServerActionTriggered = { server -> onEditServerActionTriggered = { server ->
navController.navigate( "${MainDestinations.EDIT_SERVER_ROUTE}?id=${server.serverId}") navController.navigate( "${MainDestinations.EDIT_SERVER_ROUTE}?id=${server.serverId}")
}, },

View File

@ -29,6 +29,62 @@ import net.blumia.pcmdroid.model.Folder
import net.blumia.pcmdroid.model.Server import net.blumia.pcmdroid.model.Server
import net.blumia.pcmdroid.model.Song import net.blumia.pcmdroid.model.Song
@Preview(showBackground = true)
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun FileList(
modifier: Modifier = Modifier,
currentFolder: Folder? = null,
folders: List<Folder> = listOf(),
songs: List<Song> = listOf(),
onFolderItemClicked: (Folder) -> Unit = {},
onSongItemClicked: (Song, List<Song>) -> Unit = { _, _ -> },
) {
Column(
modifier = modifier
) {
Breadcrumb(
folder = currentFolder,
onFolderClicked = onFolderItemClicked,
)
folders.forEach { folder ->
ListItem(
modifier = Modifier.clickable {
onFolderItemClicked(folder)
},
icon = {
Icon(
imageVector = Icons.Filled.FolderOpen,
contentDescription = null,
)
},
text = {
Text(folder.displayName())
}
)
}
songs.forEach { song ->
ListItem(
modifier = Modifier.clickable {
onSongItemClicked(song, songs)
},
icon = {
Icon(
imageVector = Icons.Filled.MusicNote,
contentDescription = null
)
},
text = {
Text(song.displayName())
}
)
}
}
}
@Preview(showBackground = true) @Preview(showBackground = true)
@OptIn(ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterialApi::class)
@Composable @Composable
@ -82,6 +138,7 @@ fun MainPlayer(
addServerActionTriggered: () -> Unit = {}, addServerActionTriggered: () -> Unit = {},
onServerItemIconClicked: (Server) -> Unit = {}, onServerItemIconClicked: (Server) -> Unit = {},
onFolderItemClicked: (Folder) -> Unit = {}, onFolderItemClicked: (Folder) -> Unit = {},
onSongItemClicked: (Song, List<Song>) -> Unit = { _, _ -> },
onEditServerActionTriggered: (Server) -> Unit = {}, onEditServerActionTriggered: (Server) -> Unit = {},
onDeleteServerActionTriggered: (Server) -> Unit = {}, onDeleteServerActionTriggered: (Server) -> Unit = {},
settingsActionTriggered: () -> Unit = {}, settingsActionTriggered: () -> Unit = {},
@ -160,49 +217,13 @@ fun MainPlayer(
Column( Column(
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
) { ) {
Column( FileList(
modifier = Modifier modifier = Modifier
.weight(1f, fill = true) .weight(1f, fill = true)
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState()),
) { currentFolder, folders, songs,
onFolderItemClicked, onSongItemClicked
Breadcrumb(
folder = currentFolder,
onFolderClicked = onFolderItemClicked,
) )
folders.forEach { folder ->
ListItem(
modifier = Modifier.clickable {
onFolderItemClicked(folder)
},
icon = {
Icon(
imageVector = Icons.Filled.FolderOpen,
contentDescription = null,
)
},
text = {
Text(folder.displayName())
}
)
}
songs.forEach { song ->
ListItem(
modifier = Modifier.clickable { },
icon = {
Icon(
imageVector = Icons.Filled.MusicNote,
contentDescription = null
)
},
text = {
Text(song.displayName())
}
)
}
}
NowPlaying() NowPlaying()
} }
} }