diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index fc6adb3..5914718 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -23,6 +23,15 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/net/blumia/pcmdroid/MainActivity.kt b/app/src/main/java/net/blumia/pcmdroid/MainActivity.kt
index c56cec8..2d913cd 100644
--- a/app/src/main/java/net/blumia/pcmdroid/MainActivity.kt
+++ b/app/src/main/java/net/blumia/pcmdroid/MainActivity.kt
@@ -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
+ 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 = rootFuture.get()!!
+ val root: MediaItem = result.value!!
+// pushPathStack(root)
+ },
+ MoreExecutors.directExecutor()
+ )
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/net/blumia/pcmdroid/service/PlaybackService.kt b/app/src/main/java/net/blumia/pcmdroid/service/PlaybackService.kt
new file mode 100644
index 0000000..6face65
--- /dev/null
+++ b/app/src/main/java/net/blumia/pcmdroid/service/PlaybackService.kt
@@ -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()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/blumia/pcmdroid/ui/NavGraph.kt b/app/src/main/java/net/blumia/pcmdroid/ui/NavGraph.kt
index de69556..a4034d8 100644
--- a/app/src/main/java/net/blumia/pcmdroid/ui/NavGraph.kt
+++ b/app/src/main/java/net/blumia/pcmdroid/ui/NavGraph.kt
@@ -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}")
},
diff --git a/app/src/main/java/net/blumia/pcmdroid/ui/screen/main/MainPlayer.kt b/app/src/main/java/net/blumia/pcmdroid/ui/screen/main/MainPlayer.kt
index 764eaa9..94e7cff 100644
--- a/app/src/main/java/net/blumia/pcmdroid/ui/screen/main/MainPlayer.kt
+++ b/app/src/main/java/net/blumia/pcmdroid/ui/screen/main/MainPlayer.kt
@@ -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 = listOf(),
+ songs: List = listOf(),
+ onFolderItemClicked: (Folder) -> Unit = {},
+ onSongItemClicked: (Song, List) -> 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) -> 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()
}
}