Selaa lähdekoodia

audio preview fixes

Milan Jurkulak 3 vuotta sitten
vanhempi
sitoutus
fb58b16384

+ 2 - 25
app/src/main/kotlin/org/mjdev/tvapp/ui/screens/IPTVScreen.kt

@@ -8,7 +8,6 @@
 
 package org.mjdev.tvapp.ui.screens
 
-import android.net.Uri
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
@@ -27,16 +26,12 @@ import org.mjdev.tvapp.data.local.Movie
 import org.mjdev.tvapp.viewmodel.DetailViewModel
 import org.mjdev.tvlib.annotations.TvPreview
 import org.mjdev.tvlib.extensions.HiltExt.appViewModel
-import org.mjdev.tvlib.extensions.StringExt.parseUri
 import org.mjdev.tvlib.interfaces.ItemAudio
 import org.mjdev.tvlib.interfaces.ItemPhoto
 import org.mjdev.tvlib.interfaces.ItemVideo
-import org.mjdev.tvlib.interfaces.ItemWithTitle
-import org.mjdev.tvlib.interfaces.ItemWithUri
 import org.mjdev.tvlib.navigation.AnyType
 import org.mjdev.tvlib.screen.Screen
 import org.mjdev.tvlib.ui.components.media.MediaPlayerContainer
-import java.net.URL
 
 class IPTVScreen : Screen() {
 
@@ -80,22 +75,6 @@ class IPTVScreen : Screen() {
 
         val currentData = dataList[currentIndex.intValue]
 
-        val mediaUri: String? = when (currentData) {
-            null -> null
-            is ItemWithUri<*> -> currentData.uri.toString()
-            is String -> currentData.toString()
-            is URL -> currentData.toString()
-            is Uri -> currentData.toString()
-            else -> currentData.toString()
-        }
-
-        val mediaTitle: String? = when (currentData) {
-            is ItemWithTitle<*> -> currentData.title.toString()
-            else -> null
-        }
-
-        val isAudio = currentData is ItemAudio
-
         Box(
             modifier = Modifier
                 .fillMaxSize()
@@ -103,10 +82,8 @@ class IPTVScreen : Screen() {
         ) {
             MediaPlayerContainer(
                 modifier = Modifier.fillMaxSize(),
-                isAudio = isAudio,
-                uri = mediaUri?.parseUri() ?: Uri.EMPTY,
-                title = mediaTitle,
-                subtitle = mediaUri
+                src = currentData,
+                // items = dataList // todo
             )
         }
 

+ 1 - 0
tvlib/src/main/kotlin/org/mjdev/tvlib/extensions/ModifierExt.kt

@@ -162,6 +162,7 @@ object ModifierExt {
         }
     }
 
+    // todo anr
     @OptIn(ExperimentalFoundationApi::class)
     fun Modifier.bringIntoViewIfChildrenAreFocused(): Modifier = composed(
         inspectorInfo = debugInspectorInfo { name = "bringIntoViewIfChildrenAreFocused" },

+ 3 - 0
tvlib/src/main/kotlin/org/mjdev/tvlib/extensions/ViewExt.kt

@@ -27,6 +27,9 @@ import androidx.media3.ui.PlayerView
 @Suppress("unused")
 object ViewExt {
 
+    inline fun <reified T : View> View.findView(): View? =
+        allViews.filter { v -> v is T }.firstOrNull()
+
     fun captureView(view: View, window: Window, bitmapCallback: (Bitmap) -> Unit) {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
             val bitmap = Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888)

+ 3 - 1
tvlib/src/main/kotlin/org/mjdev/tvlib/helpers/media/MetadataRetriever.kt

@@ -19,7 +19,9 @@ import org.mjdev.tvlib.interfaces.ItemVideo
 
 class MetadataRetriever(
     val context: Context,
-    val translateMeta: (name: String, value: String) -> String
+    val translateMeta: (name: String, value: String) -> String = { n, v ->
+        String.format("%s %s", n, v)
+    }
 ) {
 
     fun getInfo(

+ 13 - 27
tvlib/src/main/kotlin/org/mjdev/tvlib/ui/components/audiopreview/AudioPreview.kt

@@ -10,7 +10,6 @@ package org.mjdev.tvlib.ui.components.audiopreview
 
 import android.annotation.SuppressLint
 import android.content.Context
-import android.graphics.drawable.ColorDrawable
 import android.net.Uri
 import android.view.Gravity
 import android.view.KeyEvent
@@ -18,12 +17,10 @@ import android.view.MotionEvent
 import android.view.View
 import android.view.ViewGroup
 import android.widget.FrameLayout
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.viewinterop.AndroidView
 import org.mjdev.tvlib.ui.components.audiopreview.dailymotion.DailyMotionVideoView
-import org.mjdev.tvlib.ui.components.media.MediaPlayerState
 import timber.log.Timber
+import androidx.media3.common.MediaItem
+import androidx.media3.common.MediaMetadata
 
 @SuppressLint("ViewConstructor")
 class AudioPreview constructor(
@@ -31,7 +28,7 @@ class AudioPreview constructor(
     val uri: Uri = Uri.EMPTY
 ) : FrameLayout(context) {
 
-    //todo multiple engines
+    // todo : multiple engines
     private val previewEngine: IPreviewEngine by lazy {
         DailyMotionVideoView(context)
     }
@@ -39,7 +36,6 @@ class AudioPreview constructor(
     private var networkListener: NetworkReceiver? = null
 
     init {
-        background = ColorDrawable(0)
         addView(
             previewEngine as View,
             LayoutParams(
@@ -80,6 +76,14 @@ class AudioPreview constructor(
         }
     }
 
+    fun setData(mediaItem: MediaItem?) {
+        if (mediaItem?.mediaMetadata?.mediaType == MediaMetadata.MEDIA_TYPE_MUSIC) {
+            previewEngine.searchAndPlayIfFound(mediaItem.localConfiguration?.uri?.toString())
+        } else {
+            previewEngine.stop()
+        }
+    }
+
     override fun onDetachedFromWindow() {
         super.onDetachedFromWindow()
         networkListener?.unregister(context)
@@ -93,26 +97,8 @@ class AudioPreview constructor(
         return false
     }
 
-    companion object {
-
-        // todo video stop pause resume & etc
-        // todo lifecycle
-        @Suppress("UNUSED_PARAMETER")
-        @Composable
-        fun AudioPreview(
-            modifier: Modifier = Modifier,
-            uri: Uri = Uri.EMPTY,
-            state: MediaPlayerState,
-            onError: @Composable () -> Unit = {},
-        ) {
-            AndroidView(
-                modifier = modifier,
-                factory = { context ->
-                    AudioPreview(context, uri)
-                }
-            )
-        }
-
+    fun stop() {
+        previewEngine.stop()
     }
 
 }

+ 2 - 0
tvlib/src/main/kotlin/org/mjdev/tvlib/ui/components/audiopreview/IPrevieEngine.kt

@@ -11,6 +11,8 @@ interface IPreviewEngine {
 
     fun pause()
 
+    fun stop()
+
     fun resume()
 
     fun seekTo(seek:Long)

+ 53 - 6
tvlib/src/main/kotlin/org/mjdev/tvlib/ui/components/audiopreview/dailymotion/DailyMotionVideoView.kt

@@ -9,10 +9,19 @@
 package org.mjdev.tvlib.ui.components.audiopreview.dailymotion
 
 import android.content.Context
+import android.graphics.drawable.ColorDrawable
 import android.media.MediaMetadataRetriever
 import android.util.AttributeSet
+import android.view.View
 import android.widget.FrameLayout
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.toArgb
 import com.dailymotion.android.player.sdk.PlayerWebView
+import com.dailymotion.android.player.sdk.events.AdEndEvent
+import com.dailymotion.android.player.sdk.events.AdStartEvent
+import com.dailymotion.android.player.sdk.events.EndEvent
+import com.dailymotion.android.player.sdk.events.PlayEvent
+import com.dailymotion.android.player.sdk.events.PlayerEvent
 import com.squareup.moshi.Moshi
 import okhttp3.Cache
 import okhttp3.OkHttpClient
@@ -26,15 +35,26 @@ import java.io.File
 import java.io.IOException
 import java.net.URLEncoder
 
+// todo remove retriever
 @Suppress("DEPRECATION", "PrivatePropertyName")
 class DailyMotionVideoView @JvmOverloads constructor(
     context: Context, attrs: AttributeSet? = null
-) : FrameLayout(context, attrs), IPreviewEngine {
+) : FrameLayout(context, attrs), IPreviewEngine, PlayerWebView.EventListener {
 
     private val playerView by lazy {
         PlayerWebView(context).apply {
-            setBackgroundColor(0)
             mute()
+            setEventListener(this@DailyMotionVideoView)
+            setEventErrorListener(object : PlayerWebView.EventErrorListener {
+                override fun onError(
+                    playerEvent: String,
+                    description: String,
+                    map: Map<String, String?>
+                ) {
+                    Timber.e("dailymotion player error event $playerEvent")
+                    Timber.e(description)
+                }
+            })
         }
     }
 
@@ -82,6 +102,8 @@ class DailyMotionVideoView @JvmOverloads constructor(
     ).execute()
 
     init {
+        visibility = View.INVISIBLE
+        background = ColorDrawable(Color.Black.toArgb())
         addView(playerView)
     }
 
@@ -139,7 +161,6 @@ class DailyMotionVideoView @JvmOverloads constructor(
                     else null
                     if (videoId != null) {
                         post {
-                            visibility = VISIBLE
                             playerView.load(mapOf("video" to videoId))
                             success()
                         }
@@ -147,13 +168,10 @@ class DailyMotionVideoView @JvmOverloads constructor(
                 } catch (e: Exception) {
                     Timber.e(e)
                     post {
-                        visibility = GONE
                         error(e)
                     }
                 }
             }
-        } else {
-            visibility = GONE
         }
     }
 
@@ -161,6 +179,11 @@ class DailyMotionVideoView @JvmOverloads constructor(
         playerView.pause()
     }
 
+    override fun stop() {
+        visibility = INVISIBLE
+        playerView.pause()
+    }
+
     override fun resume() {
         playerView.play()
     }
@@ -177,4 +200,28 @@ class DailyMotionVideoView @JvmOverloads constructor(
         return Moshi.Builder().build().adapter(T::class.java).fromJson(json)
     }
 
+    override fun onEventReceived(event: PlayerEvent) {
+        when (event) {
+            is PlayEvent -> {
+                visibility = VISIBLE
+            }
+
+            is EndEvent -> {
+                visibility = INVISIBLE
+            }
+
+            is AdStartEvent -> {
+                visibility = INVISIBLE
+            }
+
+            is AdEndEvent -> {
+                visibility = VISIBLE
+            }
+
+            else -> {
+                Timber.e("Unhandled event: $event")
+            }
+        }
+    }
+
 }

+ 63 - 36
tvlib/src/main/kotlin/org/mjdev/tvlib/ui/components/media/ExoPlayerImpl.kt

@@ -14,7 +14,6 @@ import android.app.PendingIntent
 import android.content.Context
 import android.graphics.Bitmap
 import android.graphics.BitmapFactory
-import android.graphics.drawable.ColorDrawable
 import android.net.Uri
 import android.os.Build
 import android.os.Looper
@@ -41,6 +40,9 @@ import androidx.media3.common.MediaMetadata
 import androidx.media3.common.PlaybackException
 import androidx.media3.common.PlaybackParameters
 import androidx.media3.common.Player
+import androidx.media3.common.Player.STATE_ENDED
+import androidx.media3.common.Player.STATE_IDLE
+import androidx.media3.common.Player.STATE_READY
 import androidx.media3.common.Timeline
 import androidx.media3.common.TrackSelectionParameters
 import androidx.media3.common.Tracks
@@ -49,16 +51,19 @@ import androidx.media3.common.text.CueGroup
 import androidx.media3.common.util.Size
 import androidx.media3.common.util.UnstableApi
 import androidx.media3.exoplayer.ExoPlayer
+import androidx.media3.ui.PlayerControlView
 import androidx.media3.ui.PlayerNotificationManager
 import androidx.media3.ui.PlayerView
 import org.mjdev.tvlib.extensions.ComposeExt.isEditMode
 import org.mjdev.tvlib.extensions.ModifierExt.recomposeHighlighter
 import org.mjdev.tvlib.extensions.ViewExt.controller
+import org.mjdev.tvlib.extensions.ViewExt.findView
+import org.mjdev.tvlib.ui.components.audiopreview.AudioPreview
 
 @Suppress("DEPRECATION")
 @UnstableApi
 class ExoPlayerImpl(
-    private val context: Context,
+    override val context: Context,
     private val exoPlayer: ExoPlayer = ExoPlayer.Builder(context).build()
 ) : IMediaPlayer, PlayerNotificationManager.MediaDescriptionAdapter {
 
@@ -67,11 +72,13 @@ class ExoPlayerImpl(
         val notifyChannelName = this::class.java.`package`?.name ?: "MediaPlayer"
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
             val notificationManager = context.getSystemService<NotificationManager>()
-            notificationManager?.createNotificationChannel(NotificationChannel(
-                notifyChannelName,
-                notifyChannelName,
-                NotificationManager.IMPORTANCE_DEFAULT
-            ))
+            notificationManager?.createNotificationChannel(
+                NotificationChannel(
+                    notifyChannelName,
+                    notifyChannelName,
+                    NotificationManager.IMPORTANCE_DEFAULT
+                )
+            )
         }
         val notificationId = 1
         PlayerNotificationManager.Builder(
@@ -110,37 +117,35 @@ class ExoPlayerImpl(
         val width = LocalConfiguration.current.screenWidthDp
         val height = LocalConfiguration.current.screenHeightDp
         val isEdit = isEditMode()
+        val audioPlayerView = remember(exoPlayer) {
+            AudioPreview(context)
+        }
         val playerView = remember(exoPlayer) {
-            PlayerView(context).apply {
-                background = ColorDrawable(0)
-                controllerAutoShow = true
-                controllerHideOnTouch = true
-                useController = true
-                useArtwork = false
-                artworkDisplayMode = PlayerView.ARTWORK_DISPLAY_MODE_OFF
-                player = exoPlayer
-                setShutterBackgroundColor(0)
-                setBackgroundColor(0)
-                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
-                    outlineAmbientShadowColor = 0
-                }
-                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
-                    outlineSpotShadowColor = 0
+            object : PlayerView(context) {
+                init {
+                    setBackgroundColor(0)
+                    setShutterBackgroundColor(0)
+                    drawingCacheBackgroundColor = 0
+                    controllerAutoShow = true
+                    controllerHideOnTouch = true
+                    useController = true
+                    useArtwork = false
+                    artworkDisplayMode = ARTWORK_DISPLAY_MODE_OFF
+                    player = exoPlayer
+                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+                        outlineAmbientShadowColor = 0
+                    }
+                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+                        outlineSpotShadowColor = 0
+                    }
+                    setShowBuffering(SHOW_BUFFERING_ALWAYS)
+                    setShowMultiWindowTimeBar(true)
+                    addView(audioPlayerView)
+                    findView<PlayerControlView>()?.apply {
+                        removeView(this)
+                        addView(this)
+                    }
                 }
-                setShowBuffering(PlayerView.SHOW_BUFFERING_ALWAYS)
-                setShowMultiWindowTimeBar(true)
-                setShowNextButton(false)
-                setShowPreviousButton(false)
-                setShowRewindButton(true)
-                setShowFastForwardButton(true)
-                setShowShuffleButton(true)
-                setShowVrButton(false)
-                // todo : buttons tint
-//                doOnAttach {
-//                    buttons.forEach { b ->
-//                        tintButtonBackground(b, Color.Green.toArgb())
-//                    }
-//                }
             }
         }
         Box(
@@ -167,6 +172,28 @@ class ExoPlayerImpl(
                         playerView
                     }
                 )
+                exoPlayer.addListener(object : Player.Listener {
+                    override fun onPlaybackStateChanged(playbackState: Int) {
+                        when (playbackState) {
+                            STATE_READY -> {
+                                audioPlayerView.setData(currentMediaItem)
+                            }
+
+                            STATE_ENDED -> {
+                                audioPlayerView.stop()
+                            }
+
+                            STATE_IDLE -> {
+                                audioPlayerView.stop()
+                            }
+
+                            Player.STATE_BUFFERING -> {
+                                // todo
+                                // audioPlayerView.pause()
+                            }
+                        }
+                    }
+                })
             }
         }
         playerNotificationManager.setPlayer(exoPlayer)

+ 12 - 4
tvlib/src/main/kotlin/org/mjdev/tvlib/ui/components/media/IMediaPlayer.kt

@@ -8,6 +8,7 @@
 
 package org.mjdev.tvlib.ui.components.media
 
+import android.content.Context
 import android.net.Uri
 import android.os.Looper
 import android.view.Surface
@@ -34,6 +35,8 @@ import androidx.media3.common.util.Size
 @androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
 interface IMediaPlayer : Player {
 
+    val context: Context
+
     @Composable
     fun GetPlayerView()
 
@@ -56,6 +59,7 @@ interface IMediaPlayer : Player {
     companion object {
 
         val EMPTY = object : IMediaPlayer {
+            override val context: Context get() = throw (Exception("Unimplemented"))
 
             @Composable
             override fun GetPlayerView() {
@@ -506,22 +510,26 @@ interface IMediaPlayer : Player {
             }
 
             @Deprecated("Deprecated in Java")
-            override fun setDeviceVolume(volume: Int) {}
+            override fun setDeviceVolume(volume: Int) {
+            }
 
             override fun setDeviceVolume(volume: Int, flags: Int) {}
 
             @Deprecated("Deprecated in Java")
-            override fun increaseDeviceVolume() {}
+            override fun increaseDeviceVolume() {
+            }
 
             override fun increaseDeviceVolume(flags: Int) {}
 
             @Deprecated("Deprecated in Java")
-            override fun decreaseDeviceVolume() {}
+            override fun decreaseDeviceVolume() {
+            }
 
             override fun decreaseDeviceVolume(flags: Int) {}
 
             @Deprecated("Deprecated in Java")
-            override fun setDeviceMuted(muted: Boolean) {}
+            override fun setDeviceMuted(muted: Boolean) {
+            }
 
             override fun setDeviceMuted(muted: Boolean, flags: Int) {}
 

+ 3 - 19
tvlib/src/main/kotlin/org/mjdev/tvlib/ui/components/media/MediaPlayerContainer.kt

@@ -11,7 +11,6 @@
 package org.mjdev.tvlib.ui.components.media
 
 import android.annotation.SuppressLint
-import android.net.Uri
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.runtime.Composable
@@ -20,7 +19,6 @@ import androidx.compose.ui.Modifier
 import androidx.compose.ui.viewinterop.AndroidView
 import androidx.media3.ui.PlayerControlView
 import org.mjdev.tvlib.annotations.TvPreview
-import org.mjdev.tvlib.ui.components.audiopreview.AudioPreview.Companion.AudioPreview
 import org.mjdev.tvlib.extensions.ComposeExt.isEditMode
 import org.mjdev.tvlib.extensions.ModifierExt.recomposeHighlighter
 import org.mjdev.tvlib.ui.components.media.MediaPlayerState.Companion.rememberMediaPlayerState
@@ -30,10 +28,7 @@ import org.mjdev.tvlib.ui.components.media.MediaPlayerState.Companion.rememberMe
 @Composable
 fun MediaPlayerContainer(
     modifier: Modifier = Modifier,
-    uri: Uri = Uri.EMPTY,
-    isAudio: Boolean = false,
-    title: String? = null,
-    subtitle: String? = null,
+    src: Any? = null,
     autoPlay: Boolean = true,
     startSeek: Long = 0L,
     mediaPlayer: IMediaPlayer = MediaPlayerContainerDefaults.exoPlayer,
@@ -41,32 +36,21 @@ fun MediaPlayerContainer(
     val isEdit = isEditMode()
     val state = rememberMediaPlayerState(
         player = mediaPlayer,
-        uri = uri,
+        src = src,
         autoPlay = autoPlay,
         startSeek = startSeek,
-        title = title,
-        subtitle = subtitle
     )
     Box(
         modifier = modifier
             .recomposeHighlighter()
             .fillMaxSize()
     ) {
-        if (isAudio) {
-            AudioPreview(
-                modifier = Modifier.fillMaxSize(),
-                state = state,
-                uri = uri
-            )
-        }
         mediaPlayer.GetPlayerView()
         if (isEdit) {
             AndroidView(
                 modifier = Modifier.fillMaxSize(),
                 factory = { context ->
-                    PlayerControlView(context).apply {
-                        player = mediaPlayer
-                    }
+                    PlayerControlView(context)
                 }
             )
         }

+ 58 - 34
tvlib/src/main/kotlin/org/mjdev/tvlib/ui/components/media/MediaPlayerState.kt

@@ -16,45 +16,74 @@ import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.media3.common.MediaItem
 import androidx.media3.common.MediaMetadata
+import androidx.media3.common.MediaMetadata.MEDIA_TYPE_ALBUM
+import androidx.media3.common.MediaMetadata.MEDIA_TYPE_MOVIE
+import androidx.media3.common.MediaMetadata.MEDIA_TYPE_MUSIC
+import androidx.media3.common.MediaMetadata.MEDIA_TYPE_MIXED
+import org.mjdev.tvlib.helpers.media.MetadataRetriever
+import org.mjdev.tvlib.interfaces.ItemAudio
+import org.mjdev.tvlib.interfaces.ItemPhoto
+import org.mjdev.tvlib.interfaces.ItemVideo
+import org.mjdev.tvlib.interfaces.ItemWithBackground
+import org.mjdev.tvlib.interfaces.ItemWithImage
+import org.mjdev.tvlib.interfaces.ItemWithTitle
+import org.mjdev.tvlib.interfaces.ItemWithUri
+import org.mjdev.tvlib.extensions.StringExt.parseUri
+import org.mjdev.tvlib.interfaces.ItemWithDate
 
 @Suppress("unused", "MemberVisibilityCanBePrivate")
 class MediaPlayerState(
-    val player: IMediaPlayer?,
-    uri: Uri = Uri.EMPTY,
+    val player: IMediaPlayer,
+    val src: Any? = null,
     autoPlay: Boolean = true,
     startSeek: Long = 0,
-    var title: String? = null,
-    var subtitle: String? = null
 ) {
 
-    constructor(
-        player: IMediaPlayer?,
-        uri: String? = null,
-        autoPlay: Boolean = true,
-        startSeek: Long = 0
-    ) : this(
-        player,
-        if (uri === null) Uri.EMPTY else Uri.parse(uri),
-        autoPlay,
-        startSeek
-    )
-
-    val mediaUri: MutableState<Uri> = mutableStateOf(uri)
+    val title: String get() = (src as? ItemWithTitle<*>)?.title?.toString() ?: "-"
+    val date: String get() = (src as? ItemWithDate)?.date ?: "-"
+    val details: String get() = metadataRetriever.getInfo(src)
+    val uri: String get() = ((src as? ItemWithUri<*>)?.uri ?: src).toString()
+
+    val mediaType: Int = when (src) {
+        is ItemAudio -> MEDIA_TYPE_MUSIC
+        is ItemVideo -> MEDIA_TYPE_MOVIE
+        is ItemPhoto -> MEDIA_TYPE_ALBUM
+        else -> MEDIA_TYPE_MIXED
+    }
+
+    val metadataRetriever by lazy {
+        MetadataRetriever(player.context)
+    }
+
+    val mediaUri: MutableState<Any?> = mutableStateOf(uri)
     val isPlaying: MutableState<Boolean> = mutableStateOf(autoPlay)
     val seek: MutableState<Long> = mutableLongStateOf(startSeek)
 
     val hasMediaToPlay = mediaUri.value != Uri.EMPTY
     val isAutoPlay = isPlaying.value
 
-    val metaData get() = MediaMetadata.Builder()
-        .setDisplayTitle(title)
-        .setSubtitle(subtitle)
-        .build()
+    val imageUrl: Any?
+        get() {
+            val photo = (src as? ItemPhoto)?.uri
+            val image = (src as? ItemWithImage<*>)?.image
+            val background = (src as? ItemWithBackground<*>)?.background
+            return photo ?: image ?: background
+        }
 
-    val mediaItem get() = MediaItem.Builder()
-        .setUri(mediaUri.value)
-        .setMediaMetadata(metaData)
-        .build()
+    val metaData
+        get() = MediaMetadata.Builder()
+            .setDisplayTitle(title)
+            .setDescription(details)
+            .setMediaType(mediaType)
+            // todo more info
+            .setArtworkUri(imageUrl.toString().parseUri())
+            .build()
+
+    val mediaItem
+        get() = MediaItem.Builder()
+            .setUri(mediaUri.value.toString())
+            .setMediaMetadata(metaData)
+            .build()
 
     fun play() {
         isPlaying.value = true
@@ -77,28 +106,23 @@ class MediaPlayerState(
     }
 
     fun dispose() {
-        player?.release()
-        player?.dispose()
+        player.release()
     }
 
     companion object {
 
         @Composable
         fun rememberMediaPlayerState(
-            player: IMediaPlayer?,
-            uri: Uri = Uri.EMPTY,
+            player: IMediaPlayer,
+            src: Any? = null,
             autoPlay: Boolean = true,
             startSeek: Long = 0,
-            title: String? = null,
-            subtitle: String? = null
         ) = remember {
             MediaPlayerState(
                 player,
-                uri,
+                src,
                 autoPlay,
                 startSeek,
-                title,
-                subtitle
             )
         }
 

+ 1 - 1
tvlib/src/main/kotlin/org/mjdev/tvlib/ui/components/media/NativePlayerImpl.kt

@@ -44,7 +44,7 @@ import org.mjdev.tvlib.extensions.ModifierExt.recomposeHighlighter
 @Suppress("unused", "DeprecatedCallableAddReplaceWith")
 @SuppressLint("UnsafeOptInUsageError")
 class NativePlayerImpl(
-    private val context: Context,
+    override val context: Context,
     private val mediaPlayer: MediaPlayer = MediaPlayer()
 ) : IMediaPlayer {