player screen

This commit is contained in:
grakovne
2024-10-03 22:58:06 +02:00
parent 4e57e44b77
commit 3d23a445fd
5 changed files with 179 additions and 1 deletions

View File

@@ -80,6 +80,7 @@ dependencies {
implementation(libs.androidx.hilt.navigation.compose)
implementation(libs.hilt.android)
implementation(libs.androidx.media3.session)
kapt(libs.hilt.android.compiler)
implementation(libs.androidx.core.ktx)
@@ -91,6 +92,11 @@ dependencies {
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
implementation(libs.androidx.runtime.livedata)
implementation("androidx.media3:media3-exoplayer:1.4.1")
implementation("androidx.media3:media3-exoplayer-dash:1.4.1")
implementation("androidx.media3:media3-ui:1.4.1")
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)

View File

@@ -3,11 +3,12 @@
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<application
android:name=".LissenApplication"
android:allowBackup="true"
android:screenOrientation="portrait"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
@@ -16,6 +17,7 @@
android:supportsRtl="true"
android:theme="@style/Theme.Lissen"
tools:targetApi="31">
<activity
android:name=".ui.activity.AppActivity"
android:exported="true"
@@ -25,6 +27,16 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".player.service.AudioPlayerService"
android:exported="false"
android:foregroundServiceType="mediaPlayback">
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService" />
</intent-filter>
</service>
</application>
</manifest>

View File

@@ -0,0 +1,43 @@
package org.grakovne.lissen.player
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.session.MediaSession
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ServiceComponent
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.android.scopes.ServiceScoped
import org.grakovne.lissen.ui.activity.AppActivity
@Module
@InstallIn(ServiceComponent::class)
object MediaModule {
@Provides
@ServiceScoped
fun provideExoPlayer(@ApplicationContext context: Context): ExoPlayer {
return ExoPlayer.Builder(context).build()
}
@Provides
@ServiceScoped
fun provideMediaSession(
@ApplicationContext context: Context,
exoPlayer: ExoPlayer
): MediaSession {
val sessionActivityPendingIntent = PendingIntent.getActivity(
context,
0,
Intent(context, AppActivity::class.java),
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
return MediaSession.Builder(context, exoPlayer)
.setSessionActivity(sessionActivityPendingIntent)
.build()
}
}

View File

@@ -0,0 +1,115 @@
package org.grakovne.lissen.player.service
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi
import androidx.media3.session.MediaSession
import androidx.media3.session.MediaSessionService
import androidx.media3.ui.PlayerNotificationManager
import dagger.hilt.android.AndroidEntryPoint
import org.grakovne.lissen.ui.activity.AppActivity
import javax.inject.Inject
@UnstableApi
@AndroidEntryPoint
class AudioPlayerService : MediaSessionService() {
@Inject
lateinit var mediaSession: MediaSession
private lateinit var playerNotificationManager: PlayerNotificationManager
companion object {
private const val CHANNEL_ID = "audio_player_channel"
private const val NOTIFICATION_ID = 1
fun createNotificationChannel(context: Context) {
val channel = NotificationChannel(
CHANNEL_ID,
"Audio Player",
NotificationManager.IMPORTANCE_LOW
).apply {
description = "Channel for audio player notifications"
}
val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
}
override fun onCreate() {
super.onCreate()
createNotificationChannel(this)
playerNotificationManager = PlayerNotificationManager.Builder(
this,
NOTIFICATION_ID,
CHANNEL_ID
)
.setMediaDescriptionAdapter(MediaDescriptionAdapterImpl())
.setNotificationListener(NotificationListenerImpl())
.build()
playerNotificationManager.setPlayer(mediaSession.player)
}
override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession {
return mediaSession
}
@UnstableApi
private inner class MediaDescriptionAdapterImpl :
PlayerNotificationManager.MediaDescriptionAdapter {
override fun getCurrentContentTitle(player: Player): String {
return player.mediaMetadata.title.toString()
}
override fun getCurrentContentText(player: Player): String {
return player.mediaMetadata.artist.toString()
}
override fun getCurrentLargeIcon(
player: Player,
callback: PlayerNotificationManager.BitmapCallback
): Bitmap? {
return null
}
override fun createCurrentContentIntent(player: Player): PendingIntent? {
val intent = Intent(this@AudioPlayerService, AppActivity::class.java)
return PendingIntent.getActivity(
this@AudioPlayerService,
0,
intent,
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
}
}
/**
* Внутренний класс для прослушивания событий уведомления.
*/
private inner class NotificationListenerImpl : PlayerNotificationManager.NotificationListener {
override fun onNotificationPosted(
notificationId: Int,
notification: android.app.Notification,
ongoing: Boolean
) {
if (ongoing) {
startForeground(notificationId, notification)
} else {
stopForeground(STOP_FOREGROUND_REMOVE)
}
}
override fun onNotificationCancelled(notificationId: Int, dismissedByUser: Boolean) {
stopForeground(STOP_FOREGROUND_REMOVE)
stopSelf()
}
}
}

View File

@@ -23,6 +23,7 @@ navigationCompose = "2.8.2"
paletteKtx = "1.0.0"
retrofit = "2.9.0"
runtimeLivedata = "1.7.3"
media3Session = "1.4.1"
[libraries]
accompanist-systemuicontroller = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "accompanistSystemuicontroller" }
@@ -56,6 +57,7 @@ androidx-runtime-livedata = { group = "androidx.compose.runtime", name = "runtim
logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "loggingInterceptor" }
material3 = { module = "androidx.compose.material3:material3", version.ref = "material3" }
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
androidx-media3-session = { group = "androidx.media3", name = "media3-session", version.ref = "media3Session" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }