mirror of
https://github.com/zoriya/react-native-omni.git
synced 2026-08-05 05:36:17 +00:00
Make the media notification actually work
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+21
-9
@@ -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 ? (
|
||||
<Text style={styles.emptyTrackText}>No video tracks</Text>
|
||||
) : (
|
||||
tracks.videos.map((video) => (
|
||||
tracks.videos.map((video, index) => (
|
||||
<Pressable
|
||||
key={`video-${video.id}`}
|
||||
key={`video-${video.id}-${index}`}
|
||||
style={[
|
||||
styles.trackButton,
|
||||
video.selected && styles.selectedTrackButton,
|
||||
@@ -288,9 +297,9 @@ function PlayerExample({
|
||||
{tracks.audios.length === 0 ? (
|
||||
<Text style={styles.emptyTrackText}>No audio tracks</Text>
|
||||
) : (
|
||||
tracks.audios.map((audio) => (
|
||||
tracks.audios.map((audio, index) => (
|
||||
<Pressable
|
||||
key={`audio-${audio.id}`}
|
||||
key={`audio-${audio.id}-${index}`}
|
||||
style={[
|
||||
styles.trackButton,
|
||||
audio.selected && styles.selectedTrackButton,
|
||||
@@ -333,9 +342,9 @@ function PlayerExample({
|
||||
{tracks.subtitles.length === 0 ? (
|
||||
<Text style={styles.emptyTrackText}>No subtitles</Text>
|
||||
) : (
|
||||
tracks.subtitles.map((subtitle) => (
|
||||
tracks.subtitles.map((subtitle, index) => (
|
||||
<Pressable
|
||||
key={`subtitle-${subtitle.id}`}
|
||||
key={`subtitle-${subtitle.id}-${index}`}
|
||||
style={[
|
||||
styles.trackButton,
|
||||
subtitle.selected && styles.selectedTrackButton,
|
||||
@@ -378,9 +387,9 @@ function PlayerExample({
|
||||
{tracks.renditions.length === 0 ? (
|
||||
<Text style={styles.emptyTrackText}>No renditions</Text>
|
||||
) : (
|
||||
tracks.renditions.map((rendition) => (
|
||||
tracks.renditions.map((rendition, index) => (
|
||||
<Pressable
|
||||
key={`rendition-${rendition.id}`}
|
||||
key={`rendition-${rendition.id}-${index}`}
|
||||
style={[
|
||||
styles.trackButton,
|
||||
rendition.selected && styles.selectedTrackButton,
|
||||
@@ -443,6 +452,9 @@ function App(): React.JSX.Element {
|
||||
subtitles: [],
|
||||
metadata: {
|
||||
title: PLAYLIST[currentIndex].title,
|
||||
artist: PLAYLIST[currentIndex].artist,
|
||||
album: PLAYLIST[currentIndex].album,
|
||||
imageLink: PLAYLIST[currentIndex].artwork,
|
||||
hasPrev: true,
|
||||
hasNext: true,
|
||||
},
|
||||
|
||||
+2
-2
@@ -58,8 +58,8 @@ export function usePlayerState<Key extends keyof OmniPlayerState>(
|
||||
useEffect(() => {
|
||||
if (!refresh || refresh <= 0) return;
|
||||
const int = setInterval(() => {
|
||||
setState(player[key])
|
||||
}, refresh);
|
||||
setState(player[key]);
|
||||
}, refresh * 1000);
|
||||
return () => clearInterval(int);
|
||||
}, [refresh, key, player]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user