media3 playback related code, not working though
This commit is contained in:
parent
2e4bca966a
commit
b614106dd9
|
@ -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>
|
|
@ -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()
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -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}")
|
||||||
},
|
},
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user