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