mirror of
https://github.com/zoriya/react-native-video.git
synced 2025-12-06 07:16:12 +00:00
fix(android): call start foreground service if needed (#4733)
This commit is contained in:
6
bun.lock
6
bun.lock
@@ -51,7 +51,7 @@
|
||||
},
|
||||
"example": {
|
||||
"name": "react-native-video-example",
|
||||
"version": "7.0.0-alpha.5",
|
||||
"version": "7.0.0-alpha.6",
|
||||
"dependencies": {
|
||||
"@react-native-community/slider": "^4.5.6",
|
||||
"@react-native-video/drm": "*",
|
||||
@@ -79,7 +79,7 @@
|
||||
},
|
||||
"packages/drm-plugin": {
|
||||
"name": "@react-native-video/drm",
|
||||
"version": "7.0.0-alpha.5",
|
||||
"version": "7.0.0-alpha.6",
|
||||
"devDependencies": {
|
||||
"@react-native/babel-preset": "0.79.2",
|
||||
"@release-it/conventional-changelog": "^9.0.2",
|
||||
@@ -107,7 +107,7 @@
|
||||
},
|
||||
"packages/react-native-video": {
|
||||
"name": "react-native-video",
|
||||
"version": "7.0.0-alpha.5",
|
||||
"version": "7.0.0-alpha.6",
|
||||
"devDependencies": {
|
||||
"@expo/config-plugins": "^10.0.2",
|
||||
"@react-native/eslint-config": "^0.77.0",
|
||||
|
||||
@@ -1565,7 +1565,7 @@ PODS:
|
||||
- React-logger (= 0.77.2)
|
||||
- React-perflogger (= 0.77.2)
|
||||
- React-utils (= 0.77.2)
|
||||
- ReactNativeVideo (7.0.0-alpha.5):
|
||||
- ReactNativeVideo (7.0.0-alpha.6):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- hermes-engine
|
||||
@@ -1587,7 +1587,7 @@ PODS:
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- Yoga
|
||||
- ReactNativeVideoDrm (7.0.0-alpha.5):
|
||||
- ReactNativeVideoDrm (7.0.0-alpha.6):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- hermes-engine
|
||||
@@ -1904,8 +1904,8 @@ SPEC CHECKSUMS:
|
||||
ReactAppDependencyProvider: f334cebc0beed0a72490492e978007082c03d533
|
||||
ReactCodegen: 474fbb3e4bb0f1ee6c255d1955db76e13d509269
|
||||
ReactCommon: 7763e59534d58e15f8f22121cdfe319040e08888
|
||||
ReactNativeVideo: 705a2a90d9f04afff9afd90d4ef194e1bc1135d5
|
||||
ReactNativeVideoDrm: 2e0844e18cd8024078da2762a749420c5268cf18
|
||||
ReactNativeVideo: 6290dbf881cdeb58c09b5aef1af1245aebf5a207
|
||||
ReactNativeVideoDrm: 0664dcc3ccac781f6fd00329cb890b6d1f15c392
|
||||
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
|
||||
Yoga: 31a098f74c16780569aebd614a0f37a907de0189
|
||||
|
||||
|
||||
@@ -21,10 +21,17 @@ fun VideoPlaybackService.Companion.startService(
|
||||
val intent = Intent(context, VideoPlaybackService::class.java)
|
||||
intent.action = VIDEO_PLAYBACK_SERVICE_INTERFACE
|
||||
|
||||
// Use startForegroundService on O+ so the service has the opportunity to call
|
||||
// startForeground(...) quickly and avoid ForegroundServiceDidNotStartInTimeException.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
reactContext.startForegroundService(intent);
|
||||
try {
|
||||
reactContext.startForegroundService(intent)
|
||||
} catch (_: Exception) {
|
||||
// Fall back to startService if anything goes wrong
|
||||
try { reactContext.startService(intent) } catch (_: Exception) {}
|
||||
}
|
||||
} else {
|
||||
reactContext.startService(intent);
|
||||
reactContext.startService(intent)
|
||||
}
|
||||
|
||||
val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
|
||||
@@ -13,6 +13,11 @@ import androidx.media3.session.DefaultMediaNotificationProvider
|
||||
import androidx.media3.session.SimpleBitmapLoader
|
||||
import androidx.media3.session.MediaSession
|
||||
import androidx.media3.session.MediaSessionService
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.os.Build
|
||||
import androidx.core.app.NotificationCompat
|
||||
import com.margelo.nitro.NitroModules
|
||||
import com.margelo.nitro.video.HybridVideoPlayer
|
||||
import java.util.concurrent.ExecutorService
|
||||
@@ -25,12 +30,28 @@ class VideoPlaybackService : MediaSessionService() {
|
||||
private var mediaSessionsList = mutableMapOf<HybridVideoPlayer, MediaSession>()
|
||||
private var binder = VideoPlaybackServiceBinder(this)
|
||||
private var sourceActivity: Class<Activity>? = null // retained for future deep-links; currently unused
|
||||
private var isForeground = false
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
setMediaNotificationProvider(CustomMediaNotificationProvider(this))
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
// Ensure we call startForeground quickly on newer Android versions to avoid
|
||||
// ForegroundServiceDidNotStartInTimeException when startForegroundService(...) was used.
|
||||
try {
|
||||
if (!isForeground && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
startForeground(PLACEHOLDER_NOTIFICATION_ID, createPlaceholderNotification())
|
||||
isForeground = true
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
Log.e(TAG, "Failed to start foreground service!")
|
||||
}
|
||||
|
||||
return super.onStartCommand(intent, flags, startId)
|
||||
}
|
||||
|
||||
// Player Registry
|
||||
fun registerPlayer(player: HybridVideoPlayer, from: Class<Activity>) {
|
||||
if (mediaSessionsList.containsKey(player)) {
|
||||
@@ -113,7 +134,9 @@ class VideoPlaybackService : MediaSessionService() {
|
||||
private fun stopForegroundSafely() {
|
||||
try {
|
||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||
} catch (_: Exception) {}
|
||||
} catch (_: Exception) {
|
||||
Log.e(TAG, "Failed to stop foreground service!")
|
||||
}
|
||||
}
|
||||
|
||||
private fun cleanup() {
|
||||
@@ -128,11 +151,50 @@ class VideoPlaybackService : MediaSessionService() {
|
||||
// Stop the service if there are no active media sessions (no players need it)
|
||||
fun stopIfNoPlayers() {
|
||||
if (mediaSessionsList.isEmpty()) {
|
||||
// Remove placeholder notification and stop the service when no active players exist
|
||||
try {
|
||||
if (isForeground) {
|
||||
stopForegroundSafely()
|
||||
isForeground = false
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
Log.e(TAG, "Failed to stop foreground service!")
|
||||
}
|
||||
cleanup()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "VideoPlaybackService"
|
||||
const val VIDEO_PLAYBACK_SERVICE_INTERFACE = SERVICE_INTERFACE
|
||||
private const val PLACEHOLDER_NOTIFICATION_ID = 1729
|
||||
private const val NOTIFICATION_CHANNEL_ID = "twg_video_playback"
|
||||
}
|
||||
|
||||
private fun createPlaceholderNotification(): Notification {
|
||||
val nm = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
try {
|
||||
val channel = NotificationChannel(
|
||||
NOTIFICATION_CHANNEL_ID,
|
||||
"Media playback",
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
)
|
||||
channel.setShowBadge(false)
|
||||
nm.createNotificationChannel(channel)
|
||||
} catch (_: Exception) {
|
||||
Log.e(TAG, "Failed to create notification channel!")
|
||||
}
|
||||
}
|
||||
|
||||
val appName = try { applicationInfo.loadLabel(packageManager).toString() } catch (_: Exception) { "Media Playback" }
|
||||
|
||||
return NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
|
||||
.setSmallIcon(android.R.drawable.ic_media_play)
|
||||
.setContentTitle(appName)
|
||||
.setContentText("")
|
||||
.setOngoing(true)
|
||||
.setCategory(Notification.CATEGORY_SERVICE)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
<androidx.media3.ui.PlayerView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/player_view_surface"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
app:surface_type="surface_view" />
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
app:surface_type="surface_view"
|
||||
app:scrubber_enabled_size="0dp"
|
||||
app:scrubber_disabled_size="0dp"
|
||||
app:scrubber_dragged_size="0dp"
|
||||
app:touch_target_height="12dp"
|
||||
app:bar_height="5dp" />
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
android:id="@+id/player_view_texture"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:surface_type="texture_view"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
app:surface_type="texture_view" />
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
app:scrubber_enabled_size="0dp"
|
||||
app:scrubber_disabled_size="0dp"
|
||||
app:scrubber_dragged_size="0dp"
|
||||
app:touch_target_height="12dp"
|
||||
app:bar_height="5dp"
|
||||
/>
|
||||
Reference in New Issue
Block a user