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" />
</intent-filter>
</activity>
<service
android:name=".service.PlaybackService"
android:exported="true">
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService"/>
</intent-filter>
</service>
</application>
</manifest>

View File

@ -1,5 +1,6 @@
package net.blumia.pcmdroid
import android.content.ComponentName
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
@ -9,12 +10,25 @@ import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
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.screen.main.MainPlayer
import net.blumia.pcmdroid.ui.theme.PrivateCloudMusicTheme
import java.lang.Exception
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 {
MainViewModelFactory((application as MainApplication).repository)
}
@ -25,9 +39,49 @@ class MainActivity : ComponentActivity() {
PrivateCloudMusicTheme {
// A surface container using the 'background' color from the theme
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.livedata.observeAsState
import androidx.lifecycle.MutableLiveData
import androidx.media3.common.MediaItem
import androidx.media3.session.MediaBrowser
import androidx.navigation.NavHostController
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
@ -30,6 +32,7 @@ object MainDestinations {
@Composable
fun NavGraph(
viewModel: MainViewModel,
browser: MediaBrowser?,
navController: NavHostController = rememberNavController(),
startDestination: String = MainDestinations.MAIN_ROUTE,
) {
@ -62,6 +65,16 @@ fun NavGraph(
onFolderItemClicked = { 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 ->
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.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)
@OptIn(ExperimentalMaterialApi::class)
@Composable
@ -82,6 +138,7 @@ fun MainPlayer(
addServerActionTriggered: () -> Unit = {},
onServerItemIconClicked: (Server) -> Unit = {},
onFolderItemClicked: (Folder) -> Unit = {},
onSongItemClicked: (Song, List<Song>) -> Unit = { _, _ -> },
onEditServerActionTriggered: (Server) -> Unit = {},
onDeleteServerActionTriggered: (Server) -> Unit = {},
settingsActionTriggered: () -> Unit = {},
@ -160,49 +217,13 @@ fun MainPlayer(
Column(
modifier = Modifier.fillMaxSize()
) {
Column(
FileList(
modifier = Modifier
.weight(1f, fill = true)
.verticalScroll(rememberScrollState())
) {
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())
}
)
}
}
.verticalScroll(rememberScrollState()),
currentFolder, folders, songs,
onFolderItemClicked, onSongItemClicked
)
NowPlaying()
}
}