react-native-nitro-player 1.2.2 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -140,11 +140,14 @@ dependencies {
140
140
  implementation project(":react-native-nitro-modules")
141
141
 
142
142
  implementation "androidx.media3:media3-exoplayer:$media3_version"
143
+ implementation "androidx.media3:media3-exoplayer-hls:$media3_version" //for .m3u8
144
+ implementation "androidx.media3:media3-exoplayer-dash:$media3_version"
145
+ implementation "androidx.media3:media3-exoplayer-smoothstreaming:$media3_version"
143
146
  implementation "androidx.media3:media3-session:$media3_version"
144
147
  implementation "androidx.media3:media3-common:$media3_version"
145
148
  implementation "androidx.media:media:1.7.0"
146
-
147
- // WorkManager for background downloads
148
- implementation "androidx.work:work-runtime-ktx:2.9.0"
149
+
150
+ // WorkManager for background downloads.
151
+ implementation "androidx.work:work-runtime-ktx:2.10.5" // Latest version of 2.10
149
152
  }
150
153
 
@@ -125,6 +125,7 @@ class TrackPlayerCore private constructor(
125
125
 
126
126
  // ── Service binding ────────────────────────────────────────────────────
127
127
  private var serviceBound = false
128
+ private var rebindAttempts = 0
128
129
 
129
130
  private val serviceConnection =
130
131
  object : ServiceConnection {
@@ -132,7 +133,35 @@ class TrackPlayerCore private constructor(
132
133
  name: ComponentName?,
133
134
  service: IBinder?,
134
135
  ) {
135
- val binder = service as NitroPlayerPlaybackService.LocalBinder
136
+ // Android can redeliver the MediaSessionService binder instead of
137
+ // our LocalBinder (e.g. after the service is restarted). Guard the
138
+ // cast and rebind explicitly with ACTION_LOCAL_BIND instead of crashing.
139
+ val binder = service as? NitroPlayerPlaybackService.LocalBinder
140
+ if (binder == null) {
141
+ NitroPlayerLogger.log("TrackPlayerCore") {
142
+ "onServiceConnected received unexpected binder: $service"
143
+ }
144
+ try {
145
+ context.unbindService(this)
146
+ } catch (_: Exception) {}
147
+ serviceBound = false
148
+ if (rebindAttempts < 3) {
149
+ rebindAttempts++
150
+ handler.post {
151
+ val bindIntent =
152
+ Intent(context, NitroPlayerPlaybackService::class.java).apply {
153
+ action = NitroPlayerPlaybackService.ACTION_LOCAL_BIND
154
+ }
155
+ context.bindService(
156
+ bindIntent,
157
+ this,
158
+ Context.BIND_AUTO_CREATE,
159
+ )
160
+ }
161
+ }
162
+ return
163
+ }
164
+ rebindAttempts = 0
136
165
  playerHandler = binder.handler
137
166
  binder.service.trackPlayerCore = this@TrackPlayerCore
138
167
  serviceBound = true
@@ -11,7 +11,9 @@ import androidx.work.ForegroundInfo
11
11
  import androidx.work.WorkerParameters
12
12
  import com.margelo.nitro.nitroplayer.*
13
13
  import kotlinx.coroutines.Dispatchers
14
+ import kotlinx.coroutines.TimeoutCancellationException
14
15
  import kotlinx.coroutines.withContext
16
+ import kotlinx.coroutines.withTimeout
15
17
  import java.io.BufferedInputStream
16
18
  import java.io.FileOutputStream
17
19
  import java.net.HttpURLConnection
@@ -35,6 +37,14 @@ class DownloadWorker(
35
37
  private const val NOTIFICATION_CHANNEL_ID = "nitro_player_downloads"
36
38
  private const val BASE_NOTIFICATION_ID = 2001
37
39
  private const val BUFFER_SIZE = 8192
40
+
41
+ /**
42
+ * Hard upper bound on a single download. Bounds runaway/trickling
43
+ * downloads so the dataSync foreground service is always released
44
+ * well within the Android 14+ FGS timeout window — otherwise the
45
+ * system kills the app with ForegroundServiceDidNotStopInTimeException.
46
+ */
47
+ private const val MAX_DOWNLOAD_DURATION_MS = 30L * 60L * 1000L
38
48
  private val CONTENT_DISPOSITION_REGEX = Regex("filename=\"?([^\";]+)\"?")
39
49
  }
40
50
 
@@ -72,8 +82,12 @@ class DownloadWorker(
72
82
  // Download continues in background.
73
83
  }
74
84
 
75
- // Perform download
76
- val localPath = downloadFile(downloadId, trackId, trackTitle, urlString, storageLocation)
85
+ // Perform download, bounded so the foreground service is
86
+ // always released within the Android 14+ FGS timeout window.
87
+ val localPath =
88
+ withTimeout(MAX_DOWNLOAD_DURATION_MS) {
89
+ downloadFile(downloadId, trackId, trackTitle, urlString, storageLocation)
90
+ }
77
91
 
78
92
  if (localPath != null) {
79
93
  downloadManager.onComplete(downloadId, trackId, localPath)
@@ -91,6 +105,17 @@ class DownloadWorker(
91
105
  showErrorNotification(trackTitle)
92
106
  Result.retry()
93
107
  }
108
+ } catch (e: TimeoutCancellationException) {
109
+ val error =
110
+ DownloadError(
111
+ code = "DOWNLOAD_TIMEOUT",
112
+ message = "Download exceeded maximum allowed duration",
113
+ reason = DownloadErrorReason.TIMEOUT,
114
+ isRetryable = true,
115
+ )
116
+ downloadManager.onError(downloadId, trackId, error)
117
+ showErrorNotification(trackTitle)
118
+ Result.retry()
94
119
  } catch (e: Exception) {
95
120
  val errorReason =
96
121
  when {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-nitro-player",
3
- "version": "1.2.2",
3
+ "version": "1.4.0",
4
4
  "description": "A powerful audio player library for React Native with playlist management, playback controls, and support for Android Auto and CarPlay",
5
5
  "main": "lib/index",
6
6
  "module": "lib/index",
@@ -114,7 +114,8 @@
114
114
  },
115
115
  "release-it": {
116
116
  "npm": {
117
- "publish": true
117
+ "publish": true,
118
+ "skipChecks": true
118
119
  },
119
120
  "git": {
120
121
  "commitMessage": "chore: release ${version}",