tracks virtual queue implemented

master
Isaac Iwasaki 3 months ago
parent eb574f3115
commit 1956170e33

@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">

@ -1,8 +1,13 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:tools="http://schemas.android.com/tools" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"> xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
<uses-permission
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" /> tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<application <application
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
@ -22,6 +27,9 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<service android:name=".PlayerService" /> <service
android:name=".PlayerService"
android:foregroundServiceType="mediaPlayback"
android:launchMode="singleTop" />
</application> </application>
</manifest> </manifest>

@ -6,6 +6,7 @@ import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.os.Environment import android.os.Environment
import android.provider.Settings import android.provider.Settings
import android.util.Log
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
@ -19,7 +20,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@ -34,6 +35,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.media3.common.util.UnstableApi
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import usr.empty.player.ui.theme.PlayerTheme import usr.empty.player.ui.theme.PlayerTheme
@ -45,6 +47,7 @@ inline fun <T> nullifyException(block: () -> T) = try {
null null
} }
@UnstableApi
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -56,7 +59,10 @@ class MainActivity : ComponentActivity() {
startActivity(intent) startActivity(intent)
} }
startService(Intent(this, PlayerService::class.java)) startForegroundService(Intent(this, PlayerService::class.java).apply {
putExtra("check", "empty")
putExtra("type", "start")
})
enableEdgeToEdge() enableEdgeToEdge()
setContent { setContent {
@ -74,6 +80,7 @@ fun uriToPath(uri: Uri): String {
return "/storage/" + result.replace("primary:", "/storage/emulated/0/") return "/storage/" + result.replace("primary:", "/storage/emulated/0/")
} }
@UnstableApi
@Composable @Composable
fun MainLayout(modifier: Modifier = Modifier) { fun MainLayout(modifier: Modifier = Modifier) {
val context = LocalContext.current val context = LocalContext.current
@ -134,20 +141,24 @@ fun MainLayout(modifier: Modifier = Modifier) {
} }
} }
@UnstableApi
@Composable @Composable
fun NotaList(notas: List<NotaDescriptor>, modifier: Modifier = Modifier) { fun NotaList(notas: List<NotaDescriptor>, modifier: Modifier = Modifier, queueId: Int = notas.hashCode()) {
val context = LocalContext.current val context = LocalContext.current
LazyColumn( LazyColumn(
modifier = modifier, modifier = modifier,
) { ) {
items(notas) { nota -> itemsIndexed(notas) { index, nota ->
Column(modifier = Modifier Column(modifier = Modifier
.fillParentMaxWidth() .fillParentMaxWidth()
.clickable { .clickable {
Log.d("meow", "queueId: $queueId")
context.startService(Intent(context, PlayerService::class.java).apply { context.startService(Intent(context, PlayerService::class.java).apply {
putExtra("check", "empty") putExtra("check", "empty")
putExtra("type", "nota") putExtra("type", "queue")
putExtra("nota", Json.encodeToString(nota)) putExtra("queueId", queueId)
putExtra("queue", Json.encodeToString(notas))
putExtra("start", index)
}) })
} }
.padding(4.dp) .padding(4.dp)
@ -163,6 +174,7 @@ fun NotaList(notas: List<NotaDescriptor>, modifier: Modifier = Modifier) {
} }
} }
@UnstableApi
@Preview @Preview
@Composable @Composable
fun Preview() { fun Preview() {

@ -0,0 +1,9 @@
package usr.empty.player
import java.util.LinkedList
class NotaQueue : LinkedList<Nota>() {
var currentId = 0
val current
get() = if (currentId < size) this[currentId] else null
}

@ -1,28 +1,71 @@
package usr.empty.player package usr.empty.player
import android.annotation.SuppressLint
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK
import android.util.Log import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.media3.common.MediaItem import androidx.media3.common.MediaItem
import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.session.MediaSession import androidx.media3.session.MediaSession
import androidx.media3.session.MediaSessionService import androidx.media3.session.MediaSessionService
import androidx.media3.session.MediaStyleNotificationHelper
import androidx.media3.ui.PlayerNotificationManager
import com.google.common.util.concurrent.Futures import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.ListenableFuture
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import java.util.concurrent.ConcurrentLinkedDeque
@UnstableApi
class PlayerService : MediaSessionService() { class PlayerService : MediaSessionService() {
private lateinit var mediaSession: MediaSession private lateinit var mediaSession: MediaSession
lateinit var player: ExoPlayer lateinit var player: ExoPlayer
private var virtualQueue = ConcurrentLinkedDeque<Nota>() private lateinit var playerNotificationManager: PlayerNotificationManager
private val notificationId = 1004
private var virtualQueue = NotaQueue()
private var queueId = -1
@SuppressLint("ForegroundServiceType")
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
with(ExoPlayer.Builder(this).build()) { player = ExoPlayer.Builder(this).build()
player = this player.addListener(object : Player.Listener {
mediaSession = MediaSession.Builder(this@PlayerService, this).setCallback(MediaSessionCallback()).build() override fun onIsPlayingChanged(isPlaying: Boolean) {
if (!isPlaying) {
if (player.playbackState == Player.STATE_ENDED) next()
} }
super.onIsPlayingChanged(isPlaying)
}
})
mediaSession = MediaSession.Builder(this, player).setCallback(MediaSessionCallback()).build()
playerNotificationManager = PlayerNotificationManager.Builder(this, notificationId, "emptyynes")
.setMediaDescriptionAdapter(object : PlayerNotificationManager.MediaDescriptionAdapter {
override fun getCurrentContentTitle(player: Player) = "title?"
override fun createCurrentContentIntent(player: Player) = null
override fun getCurrentContentText(player: Player) = "context?"
override fun getCurrentLargeIcon(player: Player, callback: PlayerNotificationManager.BitmapCallback) = null
})
.build()
.apply {
setPriority(NotificationCompat.PRIORITY_DEFAULT)
setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
setPlayer(player)
setMediaSessionToken(mediaSession.platformToken)
}
val notificationManager: NotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(NotificationChannel("emptyynes", "Channel", NotificationManager.IMPORTANCE_LOW))
val nBuilder = NotificationCompat.Builder(this, "emptyynes").setStyle(MediaStyleNotificationHelper.MediaStyle(mediaSession))
startForeground(notificationId, nBuilder.build(), FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK)
} }
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
@ -31,24 +74,62 @@ class PlayerService : MediaSessionService() {
if (getStringExtra("check") != "empty") return@run if (getStringExtra("check") != "empty") return@run
handleCommand(getStringExtra("type")!!, this) handleCommand(getStringExtra("type")!!, this)
} }
return super.onStartCommand(intent, flags, startId) super.onStartCommand(intent, flags, startId)
return START_STICKY
} }
private fun handleCommand(type: String, intent: Intent) { private fun handleCommand(type: String, intent: Intent) {
when (type) { when (type) {
"start" -> {
}
"queue" -> {
Log.d("meow", "queue!")
intent.getIntExtra("queueId", -1).let { newQueueId ->
if (newQueueId == queueId) return@let
virtualQueue.clear()
Log.d("meow", "queue reset!")
virtualQueue.addAll(Json.decodeFromString<List<NotaDescriptor>>(intent.getStringExtra("queue")!!).map { Nota(it) })
queueId = newQueueId
}
virtualQueue.currentId = intent.getIntExtra("start", -1)
Log.d("meow", "currentId: ${virtualQueue.currentId}")
virtualQueue.current?.let {
it.prepare(player)
player.prepare()
player.play()
}
}
"next" -> {
}
"nota" -> { "nota" -> {
virtualQueue.clear()
virtualQueue.add(Nota(Json.decodeFromString<NotaDescriptor>(intent.getStringExtra("nota")!!))) virtualQueue.add(Nota(Json.decodeFromString<NotaDescriptor>(intent.getStringExtra("nota")!!)))
// virtualQueue.first.prepare(player)
virtualQueue.last.prepare(player) virtualQueue.last.prepare(player)
player.prepare() player.prepare()
player.play() player.play()
Log.d("nya", virtualQueue.last.mediaSource.toString()) Log.d("nya", virtualQueue.last.mediaSource.toString())
} }
"pause" -> player.pause() "pause" -> player.pause()
"play" -> player.play() "play" -> player.play()
} }
} }
fun next() {
virtualQueue.currentId++
virtualQueue.current?.let {
it.prepare(player)
player.prepare()
player.play()
}
Log.d("meow", "next!")
}
override fun onDestroy() { override fun onDestroy() {
player.release() player.release()
mediaSession.release() mediaSession.release()

@ -1,11 +0,0 @@
package usr.empty.player.ui.theme
import androidx.compose.ui.graphics.Color
val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8C8)
val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)
Loading…
Cancel
Save