diff --git a/android/src/main/java/dev/zoriya/omni/EventMap.kt b/android/src/main/java/dev/zoriya/omni/EventMap.kt index daed677..b6921d4 100644 --- a/android/src/main/java/dev/zoriya/omni/EventMap.kt +++ b/android/src/main/java/dev/zoriya/omni/EventMap.kt @@ -72,7 +72,7 @@ class EventMap(private val player: Player) : HybridOmniEventMapSpec(), Player.Li } override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) { - onIsPlayingChanged(playWhenReady) + onIsPlayingChanged(player.isPlaying) } override fun onIsPlayingChanged(isPlaying: Boolean) { diff --git a/android/src/main/java/dev/zoriya/omni/OmniPlayer.kt b/android/src/main/java/dev/zoriya/omni/OmniPlayer.kt index c2f0c51..5392675 100644 --- a/android/src/main/java/dev/zoriya/omni/OmniPlayer.kt +++ b/android/src/main/java/dev/zoriya/omni/OmniPlayer.kt @@ -1,10 +1,11 @@ package dev.zoriya.omni +import android.R import android.annotation.SuppressLint +import android.app.PendingIntent import android.content.Intent import android.util.Log import android.view.SurfaceHolder -import androidx.core.content.ContextCompat import androidx.media3.common.C import androidx.media3.common.MediaItem import androidx.media3.common.MediaMetadata @@ -36,15 +37,16 @@ class OmniPlayer : HybridOmniPlayerSpec() { override var showNotification: Boolean? = false set(value) { - Log.e("omni", "Toggle show notif, old: ${field}, new: ${value}") + Log.e("omni", "Toggle show notif, old: ${field}, new: $value") if (value == true) { if (notificationPlayer != null) { throw Error("Two players can't display notifications at the same time.") } notificationPlayer = player ctx.startForegroundService(Intent(ctx, OmniPlayerService::class.java)) - } else if (field == true) { + } else if (field == true && notificationPlayer == player) { ctx.stopService(Intent(ctx, OmniPlayerService::class.java)) + notificationPlayer = null } field = value } @@ -62,6 +64,7 @@ class OmniPlayer : HybridOmniPlayerSpec() { get() = currentSource ?: throw IllegalStateException("source should be initialized before get") set(value) { + Log.e("omni", "update source") currentSource = value val src = source.src.firstOrNull() ?: return player.setMediaItem(MediaItem.EMPTY) // val headers = Bundle().apply { @@ -106,7 +109,10 @@ class OmniPlayer : HybridOmniPlayerSpec() { .build() ) .build() - runOnMainThreadSync { player.setMediaItem(item) } + runOnMainThreadSync { + player.setMediaItem(item) + player.prepare() + } } fun setSurface(holder: SurfaceHolder?) { @@ -157,7 +163,7 @@ class OmniPlayer : HybridOmniPlayerSpec() { ) override var volume by mainThreadProperty( - get = {player.volume.toDouble()}, + get = { player.volume.toDouble() }, set = { value -> player.volume = value.toFloat().coerceIn(0f, 1f) } ) @@ -168,9 +174,6 @@ class OmniPlayer : HybridOmniPlayerSpec() { override fun play() { runOnMainThreadSync { player.play() } - if (showNotification == true) { - ContextCompat.startForegroundService(ctx, Intent(ctx, OmniPlayerService::class.java)) - } } override fun pause() { @@ -262,8 +265,8 @@ class OmniPlayer : HybridOmniPlayerSpec() { @SuppressLint("UnsafeOptInUsageError") class OmniPlayerService : MediaSessionService() { - private val player = OmniPlayer.notificationPlayer ?: throw Error("No player available") - var mediaSession: MediaSession = MediaSession.Builder(this, player).build() + lateinit var player: Player + lateinit var mediaSession: MediaSession init { Log.e("omni", "service inited") @@ -272,17 +275,36 @@ class OmniPlayerService : MediaSessionService() { override fun onCreate() { Log.e("omni", "service created") super.onCreate() - setMediaNotificationProvider( - DefaultMediaNotificationProvider.Builder(this).build() - ) + player = OmniPlayer.notificationPlayer ?: throw Error("No player available") + val launchIntent = packageManager.getLaunchIntentForPackage(packageName)?.apply { + addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) + } + val sessionActivity = launchIntent?.let { + PendingIntent.getActivity( + this, + 0, + it, + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE, + ) + } + mediaSession = MediaSession.Builder(this, player) + .apply { + sessionActivity?.let { setSessionActivity(it) } + } + .build() + + setMediaNotificationProvider(DefaultMediaNotificationProvider.Builder(this).build().apply { + setSmallIcon(applicationInfo.icon.takeIf { it != 0 } ?: R.drawable.ic_media_play) + }) + addSession(mediaSession) + setShowNotificationForIdlePlayer(SHOW_NOTIFICATION_FOR_IDLE_PLAYER_ALWAYS) + triggerNotificationUpdate() } override fun onGetSession(controllerInfo: MediaSession.ControllerInfo) = mediaSession override fun onTaskRemoved(rootIntent: Intent?) { - if (!isPlaybackOngoing) { - pauseAllPlayersAndStopSelf() - } + pauseAllPlayersAndStopSelf() } override fun onDestroy() { diff --git a/android/src/main/java/dev/zoriya/omni/OmniView.kt b/android/src/main/java/dev/zoriya/omni/OmniView.kt index ca49b8d..533c38d 100755 --- a/android/src/main/java/dev/zoriya/omni/OmniView.kt +++ b/android/src/main/java/dev/zoriya/omni/OmniView.kt @@ -29,24 +29,15 @@ class OmniView(val context: ThemedReactContext) : HybridOmniViewSpec(), SurfaceH override fun afterUpdate() { Log.e("omniView", "After update called") if (!::player.isInitialized) { - Log.w("omniView", "Skipping update because player is not set yet") return } - val omniPlayer = player as? OmniPlayer - ?: run { - Log.w( - "omniView", - "Skipping update because player has unexpected type: ${player::class.java.name}" - ) - return - } + val omniPlayer = player as? OmniPlayer ?: return if (boundPlayer === omniPlayer) { - return; + return } - Log.e("omniView", "Resetting old player") boundPlayer?.setSurface(null) boundPlayer = omniPlayer diff --git a/example/App.tsx b/example/App.tsx index 1b1cdec..908843f 100644 --- a/example/App.tsx +++ b/example/App.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo, useState } from "react"; +import { memo, useCallback, useEffect, useMemo, useState } from "react"; import type React from "react"; import { Pressable, ScrollView, StyleSheet, Text, View } from "react-native"; import { @@ -12,14 +12,23 @@ import { const PLAYLIST = [ { title: "Big Buck Bunny (HLS)", + artist: "Blender Foundation", + album: "Open Movie Project", + artwork: + "https://peach.blender.org/wp-content/uploads/title_anouncement.jpg", uri: "https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8", }, { title: "Sintel Trailer (MP4)", + artist: "Blender Foundation", + album: "Sintel", + artwork: + "https://download.blender.org/durian/trailer/sintel_trailer-480p.jpg", uri: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/Sintel.mp4", }, ] as const; + function formatTime(seconds: number): string { if (!Number.isFinite(seconds) || seconds < 0) { return "00:00"; @@ -261,9 +270,9 @@ function PlayerExample({ {tracks.videos.length === 0 ? ( No video tracks ) : ( - tracks.videos.map((video) => ( + tracks.videos.map((video, index) => ( No audio tracks ) : ( - tracks.audios.map((audio) => ( + tracks.audios.map((audio, index) => ( No subtitles ) : ( - tracks.subtitles.map((subtitle) => ( + tracks.subtitles.map((subtitle, index) => ( No renditions ) : ( - tracks.renditions.map((rendition) => ( + tracks.renditions.map((rendition, index) => ( ( useEffect(() => { if (!refresh || refresh <= 0) return; const int = setInterval(() => { - setState(player[key]) - }, refresh); + setState(player[key]); + }, refresh * 1000); return () => clearInterval(int); }, [refresh, key, player]);