react-native-theoplayer 7.7.1 → 7.8.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.
Files changed (24) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +13 -12
  3. package/android/src/main/AndroidManifest.xml +0 -11
  4. package/android/src/main/java/com/theoplayer/ReactTHEOplayerContext.kt +7 -1
  5. package/android/src/main/java/com/theoplayer/media/MediaNotificationBuilder.kt +26 -2
  6. package/android/src/main/java/com/theoplayer/media/MediaPlaybackService.kt +12 -58
  7. package/android/src/main/java/com/theoplayer/media/MediaQueueNavigator.kt +33 -0
  8. package/android/src/main/java/com/theoplayer/media/MediaSessionConfig.kt +5 -0
  9. package/android/src/main/java/com/theoplayer/media/MediaSessionConfigAdapter.kt +4 -0
  10. package/android/src/main/java/com/theoplayer/presentation/PipUtils.kt +8 -8
  11. package/android/src/main/res/values/strings.xml +7 -9
  12. package/ios/THEOplayerRCTDebug.swift +3 -0
  13. package/ios/THEOplayerRCTPlayerAPI.swift +6 -9
  14. package/ios/THEOplayerRCTView.swift +4 -4
  15. package/ios/backgroundAudio/THEOplayerRCTRemoteCommandsManager.swift +53 -19
  16. package/ios/backgroundAudio/THEOplayerRCTView+BackgroundAudioConfig.swift +51 -0
  17. package/ios/backgroundAudio/THEOplayerRCTView+MediaControlConfig.swift +4 -0
  18. package/lib/typescript/api/backgroundAudio/BackgroundAudioConfiguration.d.ts +7 -0
  19. package/lib/typescript/api/backgroundAudio/BackgroundAudioConfiguration.d.ts.map +1 -1
  20. package/lib/typescript/api/media/MediaControlConfiguration.d.ts +10 -0
  21. package/lib/typescript/api/media/MediaControlConfiguration.d.ts.map +1 -1
  22. package/package.json +1 -1
  23. package/src/api/backgroundAudio/BackgroundAudioConfiguration.ts +8 -0
  24. package/src/api/media/MediaControlConfiguration.ts +11 -0
package/CHANGELOG.md CHANGED
@@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.1.0/)
6
6
  and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [7.8.0] - 24-08-09
9
+
10
+ ### Added
11
+
12
+ - Added shouldResumeAfterInterruptions to the BackgroundAudioConfiguration which toggles playback resume after an interruption (e.g. phone call) on the lockscreen.
13
+ - Added the option to set a nil source on the iOS Bridge, to 'unset' the previous source.
14
+ - Added the fast-forward & rewind buttons for the Android notification when `mediaControl.mediaSessionEnabled` is set to `true`.
15
+ - Added a `mediaControl.convertSkipToSeek` player config property to allow seeking with the `NEXT` and `PREVIOUS` media buttons. Its default value is `false`.
16
+
17
+ ### Fixed
18
+
19
+ - Fixed an issue where the Lockscreen showed enabled controls when the player has no valid source.
20
+ - Fixed an issue on Android where the notification would not disappear when setting an undefined source.
21
+
22
+ ### Changed
23
+
24
+ - Replaced the `MediaBrowserService` with a regular `Service` to facilitate background playback on Android.
25
+
8
26
  ## [7.7.1] - 24-07-29
9
27
 
10
28
  ### Fixed
package/README.md CHANGED
@@ -131,18 +131,19 @@ please reach out to us for support.
131
131
  The `react-native-theoplayer` package can be combined with any number of connectors to provide extra
132
132
  functionality. Currently, the following connectors are available:
133
133
 
134
- | Connector | npm package | Source |
135
- |----------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------|
136
- | Adobe analytics, implementing the Media Collections API | [![%40theoplayer/react-native-analytics-adobe](https://img.shields.io/npm/v/%40theoplayer%2Freact-native-analytics-adobe?label=%40theoplayer/react-native-analytics-adobe)](https://www.npmjs.com/package/%40theoplayer%2Freact-native-analytics-adobe) | [`Adobe`](https://github.com/THEOplayer/react-native-connectors/tree/main/adobe) |
137
- | Agama analytics | [![%40theoplayer/react-native-analytics-agama](https://img.shields.io/npm/v/%40theoplayer%2Freact-native-analytics-agama?label=%40theoplayer/react-native-analytics-agama)](https://www.npmjs.com/package/%40theoplayer%2Freact-native-analytics-agama) | [`Agama`](https://github.com/THEOplayer/react-native-connectors/tree/main/agama) |
138
- | Comscore analytics | [![%40theoplayer/react-native-analytics-comscore](https://img.shields.io/npm/v/%40theoplayer%2Freact-native-analytics-comscore?label=%40theoplayer/react-native-analytics-comscore)](https://www.npmjs.com/package/%40theoplayer%2Freact-native-analytics-comscore) | [`Comscore`](https://github.com/THEOplayer/react-native-connectors/tree/main/comscore) |
139
- | Conviva analytics | [![%40theoplayer/react-native-analytics-conviva](https://img.shields.io/npm/v/%40theoplayer%2Freact-native-analytics-conviva?label=%40theoplayer/react-native-analytics-conviva)](https://www.npmjs.com/package/%40theoplayer%2Freact-native-analytics-conviva) | [`Conviva`](https://github.com/THEOplayer/react-native-connectors/tree/main/conviva) |
140
- | Mux analytics | [![%40theoplayer/react-native-analytics-mux](https://img.shields.io/npm/v/%40theoplayer%2Freact-native-analytics-mux?label=%40theoplayer/react-native-analytics-mux)](https://www.npmjs.com/package/%40theoplayer%2Freact-native-analytics-mux) | [`Mux`](https://github.com/THEOplayer/react-native-connectors/tree/main/mux) |
141
- | Nielsen analytics | [![%40theoplayer/react-native-analytics-nielsen](https://img.shields.io/npm/v/%40theoplayer%2Freact-native-analytics-nielsen?label=%40theoplayer/react-native-analytics-nielsen)](https://www.npmjs.com/package/%40theoplayer%2Freact-native-analytics-nielsen) | [`Nielsen`](https://github.com/THEOplayer/react-native-connectors/tree/main/nielsen) |
142
- | Youbora analytics | [![%40theoplayer/react-native-analytics-youbora](https://img.shields.io/npm/v/%40theoplayer%2Freact-native-analytics-youbora?label=%40theoplayer/react-native-analytics-youbora)](https://www.npmjs.com/package/%40theoplayer%2Freact-native-analytics-youbora) | [`Youbora`](https://github.com/THEOplayer/react-native-connectors/tree/main/youbora) |
143
- | Content protection (DRM) | [![%40theoplayer/react-native-drm](https://img.shields.io/npm/v/%40theoplayer%2Freact-native-drm?label=%40theoplayer/react-native-drm)](https://www.npmjs.com/package/%40theoplayer%2Freact-native-drm) | [`DRM`](https://github.com/THEOplayer/react-native-theoplayer-drm) |
144
- | React Native Open UI | [![%40theoplayer/react-native-ui](https://img.shields.io/npm/v/%40theoplayer%2Freact-native-ui?label=%40theoplayer/react-native-ui)](https://www.npmjs.com/package/%40theoplayer%2Freact-native-ui) | [`Open UI`](https://github.com/THEOplayer/react-native-theoplayer-ui) |
145
- | A template for<br/>`react-native-theoplayer` connectors. | [![%40theoplayer/react-native-connector-template](https://img.shields.io/npm/v/%40theoplayer%2Freact-native-connector-template?label=%40theoplayer/react-native-connector-template)](https://www.npmjs.com/package/%40theoplayer%2Freact-native-connector-template) | [`Connector template`](https://github.com/THEOplayer/react-native-theoplayer-connector-template) |
134
+ | Connector | npm package | Source |
135
+ |-----------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------|
136
+ | Adobe Heartbeat analytics using the Media Collections API | [![%40theoplayer/react-native-analytics-adobe](https://img.shields.io/npm/v/%40theoplayer%2Freact-native-analytics-adobe?label=%40theoplayer/react-native-analytics-adobe)](https://www.npmjs.com/package/%40theoplayer%2Freact-native-analytics-adobe) | [`Adobe`](https://github.com/THEOplayer/react-native-connectors/tree/main/adobe) |
137
+ | Adobe Media Edge analytics | [![%40theoplayer/react-native-analytics-adobe-edge](https://img.shields.io/npm/v/%40theoplayer%2Freact-native-analytics-adobe-edge?label=%40theoplayer/react-native-analytics-adobe-edge)](https://www.npmjs.com/package/%40theoplayer%2Freact-native-analytics-adobe) | [`Adobe Edge`](https://github.com/THEOplayer/react-native-connectors/tree/main/adobe-edge) |
138
+ | Agama analytics | [![%40theoplayer/react-native-analytics-agama](https://img.shields.io/npm/v/%40theoplayer%2Freact-native-analytics-agama?label=%40theoplayer/react-native-analytics-agama)](https://www.npmjs.com/package/%40theoplayer%2Freact-native-analytics-agama) | [`Agama`](https://github.com/THEOplayer/react-native-connectors/tree/main/agama) |
139
+ | Comscore analytics | [![%40theoplayer/react-native-analytics-comscore](https://img.shields.io/npm/v/%40theoplayer%2Freact-native-analytics-comscore?label=%40theoplayer/react-native-analytics-comscore)](https://www.npmjs.com/package/%40theoplayer%2Freact-native-analytics-comscore) | [`Comscore`](https://github.com/THEOplayer/react-native-connectors/tree/main/comscore) |
140
+ | Conviva analytics | [![%40theoplayer/react-native-analytics-conviva](https://img.shields.io/npm/v/%40theoplayer%2Freact-native-analytics-conviva?label=%40theoplayer/react-native-analytics-conviva)](https://www.npmjs.com/package/%40theoplayer%2Freact-native-analytics-conviva) | [`Conviva`](https://github.com/THEOplayer/react-native-connectors/tree/main/conviva) |
141
+ | Mux analytics | [![%40theoplayer/react-native-analytics-mux](https://img.shields.io/npm/v/%40theoplayer%2Freact-native-analytics-mux?label=%40theoplayer/react-native-analytics-mux)](https://www.npmjs.com/package/%40theoplayer%2Freact-native-analytics-mux) | [`Mux`](https://github.com/THEOplayer/react-native-connectors/tree/main/mux) |
142
+ | Nielsen analytics | [![%40theoplayer/react-native-analytics-nielsen](https://img.shields.io/npm/v/%40theoplayer%2Freact-native-analytics-nielsen?label=%40theoplayer/react-native-analytics-nielsen)](https://www.npmjs.com/package/%40theoplayer%2Freact-native-analytics-nielsen) | [`Nielsen`](https://github.com/THEOplayer/react-native-connectors/tree/main/nielsen) |
143
+ | Youbora analytics | [![%40theoplayer/react-native-analytics-youbora](https://img.shields.io/npm/v/%40theoplayer%2Freact-native-analytics-youbora?label=%40theoplayer/react-native-analytics-youbora)](https://www.npmjs.com/package/%40theoplayer%2Freact-native-analytics-youbora) | [`Youbora`](https://github.com/THEOplayer/react-native-connectors/tree/main/youbora) |
144
+ | Content protection (DRM) | [![%40theoplayer/react-native-drm](https://img.shields.io/npm/v/%40theoplayer%2Freact-native-drm?label=%40theoplayer/react-native-drm)](https://www.npmjs.com/package/%40theoplayer%2Freact-native-drm) | [`DRM`](https://github.com/THEOplayer/react-native-theoplayer-drm) |
145
+ | React Native Open UI | [![%40theoplayer/react-native-ui](https://img.shields.io/npm/v/%40theoplayer%2Freact-native-ui?label=%40theoplayer/react-native-ui)](https://www.npmjs.com/package/%40theoplayer%2Freact-native-ui) | [`Open UI`](https://github.com/THEOplayer/react-native-theoplayer-ui) |
146
+ | A template for<br/>`react-native-theoplayer` connectors. | [![%40theoplayer/react-native-connector-template](https://img.shields.io/npm/v/%40theoplayer%2Freact-native-connector-template?label=%40theoplayer/react-native-connector-template)](https://www.npmjs.com/package/%40theoplayer%2Freact-native-connector-template) | [`Connector template`](https://github.com/THEOplayer/react-native-theoplayer-connector-template) |
146
147
 
147
148
  ## Creating your first app
148
149
 
@@ -37,19 +37,8 @@
37
37
  android:exported="false"
38
38
  android:enabled="true"
39
39
  android:foregroundServiceType="mediaPlayback">
40
- <intent-filter>
41
- <action android:name="android.media.browse.MediaBrowserService" />
42
- <action android:name="android.intent.action.MEDIA_BUTTON" />
43
- </intent-filter>
44
40
  </service>
45
41
 
46
- <receiver android:name="androidx.media.session.MediaButtonReceiver"
47
- android:exported="false">
48
- <intent-filter>
49
- <action android:name="android.intent.action.MEDIA_BUTTON" />
50
- </intent-filter>
51
- </receiver>
52
-
53
42
  </application>
54
43
 
55
44
  </manifest>
@@ -28,6 +28,7 @@ import com.theoplayer.audio.AudioBecomingNoisyManager
28
28
  import com.theoplayer.audio.AudioFocusManager
29
29
  import com.theoplayer.audio.BackgroundAudioConfig
30
30
  import com.theoplayer.media.MediaPlaybackService
31
+ import com.theoplayer.media.MediaQueueNavigator
31
32
  import com.theoplayer.media.MediaSessionConfig
32
33
  import java.util.concurrent.atomic.AtomicBoolean
33
34
 
@@ -112,7 +113,7 @@ class ReactTHEOplayerContext private constructor(
112
113
  binder?.setPlayerContext(this@ReactTHEOplayerContext)
113
114
 
114
115
  // Apply background audio config
115
- binder?.setEnablePlaybackControls(backgroundAudioConfig.enabled)
116
+ binder?.setEnablePlaybackControls(mediaSessionConfig)
116
117
  }
117
118
 
118
119
  override fun onServiceDisconnected(className: ComponentName?) {
@@ -259,6 +260,11 @@ class ReactTHEOplayerContext private constructor(
259
260
 
260
261
  // Pass metadata from source description
261
262
  setMediaSessionMetadata(player?.source)
263
+
264
+ // Install a queue navigator, but only if we want to handle skip buttons.
265
+ if (mediaSessionConfig.convertSkipToSeek) {
266
+ queueNavigator = MediaQueueNavigator(mediaSessionConfig)
267
+ }
262
268
  }
263
269
  }
264
270
 
@@ -67,6 +67,22 @@ class MediaNotificationBuilder(
67
67
  )
68
68
  )
69
69
 
70
+ private val forwardAction = NotificationCompat.Action(
71
+ R.drawable.ic_fast_forward, context.getString(R.string.fast_forward),
72
+ MediaButtonReceiver.buildMediaButtonPendingIntent(
73
+ context,
74
+ PlaybackStateCompat.ACTION_FAST_FORWARD
75
+ )
76
+ )
77
+
78
+ private val rewindAction = NotificationCompat.Action(
79
+ R.drawable.ic_rewind, context.getString(R.string.rewind),
80
+ MediaButtonReceiver.buildMediaButtonPendingIntent(
81
+ context,
82
+ PlaybackStateCompat.ACTION_REWIND
83
+ )
84
+ )
85
+
70
86
  fun build(
71
87
  @PlaybackStateCompat.State playbackState: Int,
72
88
  largeIcon: Bitmap?,
@@ -130,13 +146,15 @@ class MediaNotificationBuilder(
130
146
  // on the eyes and avoid extremely bright or fluorescent colors.
131
147
  color = ContextCompat.getColor(context, R.color.app_primary_color)
132
148
 
133
- // Add a play/pause button
149
+ // Add play/pause, rewind and fast-forward buttons.
134
150
  if (enableMediaControls) {
151
+ addAction(rewindAction)
135
152
  if (playbackState == PlaybackStateCompat.STATE_PAUSED) {
136
153
  addAction(playAction)
137
154
  } else if (playbackState == PlaybackStateCompat.STATE_PLAYING) {
138
155
  addAction(pauseAction)
139
156
  }
157
+ addAction(forwardAction)
140
158
  } else {
141
159
  // Add empty placeholder action as clearActions() does not work.
142
160
  addAction(0, null, null)
@@ -150,7 +168,13 @@ class MediaNotificationBuilder(
150
168
  style.setMediaSession(mediaSession.sessionToken)
151
169
 
152
170
  // Add up to 3 actions to be shown in the notification's standard-sized contentView.
153
- style.setShowActionsInCompactView(0)
171
+ if (enableMediaControls) {
172
+ // The Rewind, Play/Pause and FastForward actions.
173
+ style.setShowActionsInCompactView(0,1,2)
174
+ } else {
175
+ // The placeholder action, which was added above.
176
+ style.setShowActionsInCompactView(0)
177
+ }
154
178
 
155
179
  if (enableCancelButton) {
156
180
  // In Android 5.0 (API level 21) and later you can swipe away a notification to
@@ -6,16 +6,12 @@ import android.content.pm.ServiceInfo
6
6
  import android.graphics.Bitmap
7
7
  import android.os.Binder
8
8
  import android.os.Build
9
- import android.os.Bundle
10
9
  import android.os.IBinder
11
- import android.support.v4.media.MediaBrowserCompat
12
10
  import android.support.v4.media.session.MediaSessionCompat
13
11
  import android.support.v4.media.session.PlaybackStateCompat
14
- import android.text.TextUtils
15
12
  import android.util.Log
16
13
  import androidx.core.app.ServiceCompat
17
14
  import androidx.core.content.ContextCompat
18
- import androidx.media.MediaBrowserServiceCompat
19
15
  import androidx.media.session.MediaButtonReceiver
20
16
  import com.theoplayer.BuildConfig
21
17
  import com.theoplayer.ReactTHEOplayerContext
@@ -23,22 +19,20 @@ import com.theoplayer.android.api.player.Player
23
19
  import com.theoplayer.android.connector.mediasession.MediaSessionConnector
24
20
  import com.theoplayer.android.connector.mediasession.MediaSessionListener
25
21
 
26
- private const val BROWSABLE_ROOT = "/"
27
- private const val EMPTY_ROOT = "@empty@"
28
22
  private const val STOP_SERVICE_IF_APP_REMOVED = true
29
23
 
30
24
  private const val NOTIFICATION_ID = 1
31
25
 
32
26
  private const val TAG = "MediaPlaybackService"
33
27
 
34
- class MediaPlaybackService : MediaBrowserServiceCompat() {
28
+ class MediaPlaybackService : Service() {
35
29
 
36
30
  private lateinit var notificationManager: NotificationManager
37
31
  private lateinit var notificationBuilder: MediaNotificationBuilder
38
32
 
39
33
  private var playerContext: ReactTHEOplayerContext? = null
40
34
 
41
- private var enableMediaControls: Boolean = true
35
+ private var mediaSessionConfig: MediaSessionConfig = MediaSessionConfig()
42
36
 
43
37
  private val player: Player?
44
38
  get() = playerContext?.player
@@ -58,8 +52,8 @@ class MediaPlaybackService : MediaBrowserServiceCompat() {
58
52
  service.connectPlayerContext(playerContext)
59
53
  }
60
54
 
61
- fun setEnablePlaybackControls(enable: Boolean) {
62
- enableMediaControls = enable
55
+ fun setEnablePlaybackControls(newConfig: MediaSessionConfig) {
56
+ mediaSessionConfig = newConfig
63
57
  updateNotification()
64
58
  }
65
59
 
@@ -175,9 +169,6 @@ class MediaPlaybackService : MediaBrowserServiceCompat() {
175
169
  // Set mediaSession active
176
170
  setActive(BuildConfig.EXTENSION_MEDIASESSION)
177
171
  }
178
-
179
- // Set the MediaBrowserServiceCompat's media session.
180
- sessionToken = mediaSession.sessionToken
181
172
  }
182
173
 
183
174
  private fun stopForegroundService() {
@@ -208,53 +199,16 @@ class MediaPlaybackService : MediaBrowserServiceCompat() {
208
199
  mediaSessionConnector.removeListener(mediaSessionListener)
209
200
  }
210
201
 
211
- override fun onGetRoot(
212
- clientPackageName: String,
213
- clientUid: Int,
214
- rootHints: Bundle?
215
- ): BrowserRoot {
216
- // (Optional) Control the level of access for the specified package name.
217
- // You'll need to write your own logic to do this.
218
- return if (allowBrowsing(clientPackageName, clientUid)) {
219
- // Returns a root ID that clients can use with onLoadChildren() to retrieve
220
- // the content hierarchy.
221
- BrowserRoot(BROWSABLE_ROOT, null)
222
- } else {
223
- // Clients can connect, but this BrowserRoot is an empty hierachy
224
- // so onLoadChildren returns nothing. This disables the ability to browse for content.
225
- BrowserRoot(EMPTY_ROOT, null)
226
- }
227
- }
228
-
229
- @Suppress("UNUSED_PARAMETER")
230
- private fun allowBrowsing(clientPackageName: String, clientUid: Int): Boolean {
231
- // Only allow browsing from the same package
232
- return TextUtils.equals(clientPackageName, packageName)
233
- }
234
-
235
- override fun onLoadChildren(
236
- parentId: String,
237
- result: Result<List<MediaBrowserCompat.MediaItem>>
238
- ) {
239
- if (parentId == EMPTY_ROOT) {
240
- result.sendResult(null)
241
- return
242
- }
243
- result.sendResult(emptyList())
244
- }
245
-
246
202
  private fun updateNotification() {
247
- player?.let {
248
- if (it.isPaused) {
249
- updateNotification(PlaybackStateCompat.STATE_PAUSED)
250
- } else {
251
- updateNotification(PlaybackStateCompat.STATE_PLAYING)
252
- }
203
+ val player = player
204
+ when {
205
+ player?.source == null -> updateNotification(PlaybackStateCompat.STATE_STOPPED)
206
+ !player.isPaused -> updateNotification(PlaybackStateCompat.STATE_PLAYING)
207
+ else -> updateNotification(PlaybackStateCompat.STATE_PAUSED)
253
208
  }
254
209
  }
255
210
 
256
211
  private fun updateNotification(@PlaybackStateCompat.State playbackState: Int) {
257
-
258
212
  // When a service is playing, it should be running in the foreground.
259
213
  // This lets the system know that the service is performing a useful function and should
260
214
  // not be killed if the system is low on memory.
@@ -262,7 +216,7 @@ class MediaPlaybackService : MediaBrowserServiceCompat() {
262
216
  PlaybackStateCompat.STATE_PAUSED -> {
263
217
  // Fetch large icon asynchronously
264
218
  fetchImageFromUri(mediaSession.controller.metadata?.description?.iconUri) { largeIcon ->
265
- notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build(playbackState, largeIcon, enableMediaControls))
219
+ notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build(playbackState, largeIcon, mediaSessionConfig.mediaSessionEnabled))
266
220
  }
267
221
  }
268
222
  PlaybackStateCompat.STATE_PLAYING -> {
@@ -296,13 +250,13 @@ class MediaPlaybackService : MediaBrowserServiceCompat() {
296
250
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
297
251
  startForeground(
298
252
  NOTIFICATION_ID,
299
- notificationBuilder.build(playbackState, largeIcon, enableMediaControls),
253
+ notificationBuilder.build(playbackState, largeIcon, mediaSessionConfig.mediaSessionEnabled),
300
254
  ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK
301
255
  )
302
256
  } else {
303
257
  startForeground(
304
258
  NOTIFICATION_ID,
305
- notificationBuilder.build(playbackState, largeIcon, enableMediaControls)
259
+ notificationBuilder.build(playbackState, largeIcon, mediaSessionConfig.mediaSessionEnabled)
306
260
  )
307
261
  }
308
262
  } catch (e: IllegalStateException) {
@@ -0,0 +1,33 @@
1
+ package com.theoplayer.media
2
+
3
+ import com.theoplayer.android.api.player.Player
4
+ import com.theoplayer.android.connector.mediasession.QueueNavigator
5
+
6
+ private const val DEFAULT_ACTIVE_ITEM_ID = 0L
7
+
8
+ class MediaQueueNavigator(private var mediaSessionConfig: MediaSessionConfig): QueueNavigator {
9
+ override fun getActiveQueueItemId(player: Player): Long {
10
+ return DEFAULT_ACTIVE_ITEM_ID
11
+ }
12
+
13
+ override fun getSupportedQueueNavigatorActions(player: Player): Long {
14
+ return QueueNavigator.AVAILABLE_ACTIONS
15
+ }
16
+
17
+ override fun onSkipToNext(player: Player) {
18
+ // Check if we need to treat a MEDIA_NEXT keycode as a MEDIA_FAST_FORWARD
19
+ if (mediaSessionConfig.convertSkipToSeek) {
20
+ player.currentTime += mediaSessionConfig.skipForwardInterval
21
+ }
22
+ }
23
+
24
+ override fun onSkipToPrevious(player: Player) {
25
+ // Check if we need to treat a MEDIA_PREVIOUS keycode as a MEDIA_REWIND
26
+ if (mediaSessionConfig.convertSkipToSeek) {
27
+ player.currentTime -= mediaSessionConfig.skipBackwardInterval
28
+ }
29
+ }
30
+
31
+ override fun onSkipToQueueItem(player: Player, id: Long) {
32
+ }
33
+ }
@@ -15,4 +15,9 @@ data class MediaSessionConfig (
15
15
  * The amount of seconds the player will skip backward.
16
16
  */
17
17
  var skipBackwardInterval: Double = 5.0,
18
+
19
+ /**
20
+ * Whether "skip track" events should be handled the same as "fast-forward/rewind".
21
+ */
22
+ var convertSkipToSeek: Boolean = false,
18
23
  )
@@ -6,6 +6,7 @@ object MediaSessionConfigAdapter {
6
6
  private const val PROP_ENABLED = "mediaSessionEnabled"
7
7
  private const val PROP_SKIP_FORWARD_INTERVAL = "skipForwardInterval"
8
8
  private const val PROP_SKIP_BACKWARD_INTERVAL = "skipBackwardInterval"
9
+ private const val PROP_CONVERT_SKIP = "convertSkipToSeek"
9
10
 
10
11
  fun fromProps(props: ReadableMap?): MediaSessionConfig {
11
12
  return MediaSessionConfig().apply {
@@ -18,6 +19,9 @@ object MediaSessionConfigAdapter {
18
19
  if (props?.hasKey(PROP_SKIP_BACKWARD_INTERVAL) == true) {
19
20
  skipBackwardInterval = props.getDouble(PROP_SKIP_BACKWARD_INTERVAL)
20
21
  }
22
+ if (props?.hasKey(PROP_CONVERT_SKIP) == true) {
23
+ convertSkipToSeek = props.getBoolean(PROP_CONVERT_SKIP)
24
+ }
21
25
  }
22
26
  }
23
27
  }
@@ -130,8 +130,8 @@ class PipUtils(
130
130
  buildRemoteAction(
131
131
  ACTION_RWD,
132
132
  R.drawable.ic_rewind,
133
- R.string.rwd_pip,
134
- R.string.rwd_desc_pip
133
+ R.string.rewind,
134
+ R.string.rewind_description
135
135
  )
136
136
  )
137
137
  }
@@ -144,16 +144,16 @@ class PipUtils(
144
144
  buildRemoteAction(
145
145
  ACTION_PLAY,
146
146
  R.drawable.ic_play,
147
- R.string.play_pip,
148
- R.string.play_desc_pip,
147
+ R.string.play,
148
+ R.string.play_description,
149
149
  enablePlayPause
150
150
  )
151
151
  } else {
152
152
  buildRemoteAction(
153
153
  ACTION_PAUSE,
154
154
  R.drawable.ic_pause,
155
- R.string.pause_pip,
156
- R.string.pause_desc_pip,
155
+ R.string.play,
156
+ R.string.pause_description,
157
157
  enablePlayPause
158
158
  )
159
159
  }
@@ -165,8 +165,8 @@ class PipUtils(
165
165
  buildRemoteAction(
166
166
  ACTION_FFD,
167
167
  R.drawable.ic_fast_forward,
168
- R.string.ffd_pip,
169
- R.string.ffd_desc_pip
168
+ R.string.fast_forward,
169
+ R.string.fast_forward_description
170
170
  )
171
171
  )
172
172
  }
@@ -1,15 +1,13 @@
1
1
  <?xml version="1.0" encoding="utf-8"?>
2
2
  <resources>
3
- <string name="pause">Pause</string>
4
3
  <string name="play">Play</string>
5
- <string name="pause_pip">Pause</string>
6
- <string name="pause_desc_pip">Pause video</string>
7
- <string name="play_pip">Play</string>
8
- <string name="play_desc_pip">Play video</string>
9
- <string name="ffd_pip">Fast Forward</string>
10
- <string name="ffd_desc_pip">Fast Forward video</string>
11
- <string name="rwd_pip">Rewind</string>
12
- <string name="rwd_desc_pip">Rewind video</string>
4
+ <string name="play_description">Play video</string>
5
+ <string name="pause">Pause</string>
6
+ <string name="pause_description">Pause video</string>
7
+ <string name="fast_forward">Fast Forward</string>
8
+ <string name="fast_forward_description">Fast Forward video</string>
9
+ <string name="rewind">Rewind</string>
10
+ <string name="rewind_description">Rewind video</string>
13
11
 
14
12
  <string name="background_playback_service_description">THEOplayer service providing background playback.</string>
15
13
  <string name="notification_channel_id">theoplayer_default_channel</string>
@@ -44,3 +44,6 @@ let DEBUG_CACHE_EVENTS = DEBUG && false
44
44
 
45
45
  // Debug flag to monitor cache API usage
46
46
  let DEBUG_CACHE_API = DEBUG && false
47
+
48
+ // Debug flag to monitor AudioSession interruptions (e.g. phone cll)
49
+ let DEBUG_INTERRUPTIONS = DEBUG && false
@@ -57,14 +57,10 @@ class THEOplayerRCTPlayerAPI: NSObject, RCTBridgeModule {
57
57
  DispatchQueue.main.async {
58
58
  if let theView = self.bridge.uiManager.view(forReactTag: node) as? THEOplayerRCTView {
59
59
  let (sourceDescription, metadataTrackDescriptions) = THEOplayerRCTSourceDescriptionBuilder.buildSourceDescription(src)
60
- if let srcDescription = sourceDescription {
61
- if let player = theView.player {
62
- self.triggerViewHierarchyValidation(player)
63
- self.setNewSourceDescription(player: player, srcDescription: srcDescription)
64
- theView.processMetadataTracks(metadataTrackDescriptions: metadataTrackDescriptions)
65
- }
66
- } else {
67
- if DEBUG_PLAYER_API { PrintUtils.printLog(logText: "[NATIVE] Failed to update THEOplayer source.") }
60
+ if let player = theView.player {
61
+ self.triggerViewHierarchyValidation(player)
62
+ self.setNewSourceDescription(player: player, srcDescription: sourceDescription)
63
+ theView.processMetadataTracks(metadataTrackDescriptions: metadataTrackDescriptions)
68
64
  }
69
65
  } else {
70
66
  if DEBUG_PLAYER_API { PrintUtils.printLog(logText: "[NATIVE] Failed to update THEOplayer source.") }
@@ -81,7 +77,7 @@ class THEOplayerRCTPlayerAPI: NSObject, RCTBridgeModule {
81
77
  #endif
82
78
  }
83
79
 
84
- private func setNewSourceDescription(player: THEOplayer, srcDescription: SourceDescription) {
80
+ private func setNewSourceDescription(player: THEOplayer, srcDescription: SourceDescription?) {
85
81
  if DEBUG_PLAYER_API { PrintUtils.printLog(logText: "[NATIVE] Setting new source on TheoPlayer") }
86
82
  #if canImport(THEOplayerConnectorSideloadedSubtitle)
87
83
  player.setSourceWithSubtitles(source: srcDescription)
@@ -227,6 +223,7 @@ class THEOplayerRCTPlayerAPI: NSObject, RCTBridgeModule {
227
223
  private func parseBackgroundAudioConfig(configDict: NSDictionary) -> BackgroundAudioConfig {
228
224
  var backgroundAudio = BackgroundAudioConfig()
229
225
  backgroundAudio.enabled = configDict["enabled"] as? Bool ?? false
226
+ backgroundAudio.shouldResumeAfterInterruption = configDict["shouldResumeAfterInterruption"] as? Bool ?? false
230
227
  return backgroundAudio
231
228
  }
232
229
 
@@ -34,10 +34,10 @@ public class THEOplayerRCTView: UIView {
34
34
  }
35
35
  }
36
36
  var backgroundAudioConfig = BackgroundAudioConfig() {
37
- didSet {
38
- self.remoteCommandsManager.setBackGroundAudioConfig(backgroundAudioConfig)
39
- }
40
- }
37
+ didSet {
38
+ self.updateInterruptionNotifications()
39
+ }
40
+ }
41
41
 
42
42
  // MARK: Events
43
43
  var onNativePlayerReady: RCTDirectEventBlock?
@@ -9,7 +9,7 @@ class THEOplayerRCTRemoteCommandsManager: NSObject {
9
9
  private weak var player: THEOplayer?
10
10
  private var isLive: Bool = false
11
11
  private var inAd: Bool = false
12
- private var backgroundaudioConfig = BackgroundAudioConfig()
12
+ private var hasSource: Bool = false
13
13
  private var mediaControlConfig = MediaControlConfig()
14
14
 
15
15
  // MARK: player Listeners
@@ -33,11 +33,6 @@ class THEOplayerRCTRemoteCommandsManager: NSObject {
33
33
  self.attachListeners()
34
34
  }
35
35
 
36
- func setBackGroundAudioConfig(_ newBackgroundAudioConfig: BackgroundAudioConfig) {
37
- self.backgroundaudioConfig = newBackgroundAudioConfig
38
- self.updateRemoteCommands()
39
- }
40
-
41
36
  func setMediaControlConfig(_ newMediaControlConfig: MediaControlConfig) {
42
37
  self.mediaControlConfig = newMediaControlConfig
43
38
  self.updateRemoteCommands()
@@ -46,13 +41,18 @@ class THEOplayerRCTRemoteCommandsManager: NSObject {
46
41
  private func initRemoteCommands() {
47
42
  self.isLive = false
48
43
  self.inAd = false
44
+ self.hasSource = false
49
45
  let commandCenter = MPRemoteCommandCenter.shared()
50
46
 
51
- commandCenter.playCommand.isEnabled = true
52
- commandCenter.pauseCommand.isEnabled = true
53
- commandCenter.togglePlayPauseCommand.isEnabled = true
54
- commandCenter.stopCommand.isEnabled = true
55
- commandCenter.changePlaybackPositionCommand.isEnabled = true
47
+ commandCenter.playCommand.isEnabled = false
48
+ commandCenter.pauseCommand.isEnabled = false
49
+ commandCenter.togglePlayPauseCommand.isEnabled = false
50
+ commandCenter.stopCommand.isEnabled = false
51
+ commandCenter.changePlaybackPositionCommand.isEnabled = false
52
+ commandCenter.skipForwardCommand.isEnabled = false
53
+ commandCenter.skipBackwardCommand.isEnabled = false
54
+ commandCenter.previousTrackCommand.isEnabled = false
55
+ commandCenter.nextTrackCommand.isEnabled = false
56
56
 
57
57
  // PLAY
58
58
  commandCenter.playCommand.addTarget(self, action: #selector(onPlayCommand(_:)))
@@ -70,6 +70,10 @@ class THEOplayerRCTRemoteCommandsManager: NSObject {
70
70
  // ADD SEEK BACKWARD
71
71
  commandCenter.skipBackwardCommand.preferredIntervals = [NSNumber(value: self.mediaControlConfig.skipBackwardInterval)]
72
72
  commandCenter.skipBackwardCommand.addTarget(self, action: #selector(onSkipBackwardCommand(_:)))
73
+ // ADD NEXT TRACK
74
+ commandCenter.nextTrackCommand.addTarget(self, action: #selector(onNextTrackCommand(_:)))
75
+ // ADD PREVIOUS TRACK
76
+ commandCenter.previousTrackCommand.addTarget(self, action: #selector(onPreviousTrackCommand(_:)))
73
77
 
74
78
  if DEBUG_REMOTECOMMANDS { PrintUtils.printLog(logText: "[NATIVE] Remote commands initialised.") }
75
79
  }
@@ -78,19 +82,22 @@ class THEOplayerRCTRemoteCommandsManager: NSObject {
78
82
  let commandCenter = MPRemoteCommandCenter.shared()
79
83
 
80
84
  // update the enabled state to have correct visual representation in the lockscreen
81
- commandCenter.playCommand.isEnabled = !self.inAd && self.backgroundaudioConfig.enabled
82
- commandCenter.pauseCommand.isEnabled = !self.inAd && self.backgroundaudioConfig.enabled
83
- commandCenter.togglePlayPauseCommand.isEnabled = !self.inAd && self.backgroundaudioConfig.enabled
84
- commandCenter.stopCommand.isEnabled = !self.inAd && self.backgroundaudioConfig.enabled
85
- commandCenter.changePlaybackPositionCommand.isEnabled = !self.isLive && !self.inAd && self.backgroundaudioConfig.enabled
86
- commandCenter.skipForwardCommand.isEnabled = !self.isLive && !self.inAd && self.backgroundaudioConfig.enabled
87
- commandCenter.skipBackwardCommand.isEnabled = !self.isLive && !self.inAd && self.backgroundaudioConfig.enabled
85
+ commandCenter.pauseCommand.isEnabled = self.hasSource && !self.inAd
86
+ commandCenter.playCommand.isEnabled = self.hasSource && !self.inAd
87
+ commandCenter.pauseCommand.isEnabled = self.hasSource && !self.inAd
88
+ commandCenter.togglePlayPauseCommand.isEnabled = self.hasSource && !self.inAd
89
+ commandCenter.stopCommand.isEnabled = self.hasSource && !self.inAd
90
+ commandCenter.changePlaybackPositionCommand.isEnabled = self.hasSource && !self.isLive && !self.inAd
91
+ commandCenter.skipForwardCommand.isEnabled = self.hasSource && !self.isLive && !self.inAd
92
+ commandCenter.skipBackwardCommand.isEnabled = self.hasSource && !self.isLive && !self.inAd
93
+ commandCenter.nextTrackCommand.isEnabled = !self.isLive && !self.inAd
94
+ commandCenter.previousTrackCommand.isEnabled = !self.isLive && !self.inAd
88
95
 
89
96
  // set configured skip forward/backward intervals
90
97
  commandCenter.skipForwardCommand.preferredIntervals = [NSNumber(value: self.mediaControlConfig.skipForwardInterval)]
91
98
  commandCenter.skipBackwardCommand.preferredIntervals = [NSNumber(value: self.mediaControlConfig.skipBackwardInterval)]
92
99
 
93
- if DEBUG_REMOTECOMMANDS { PrintUtils.printLog(logText: "[NATIVE] Remote commands updated for \(self.isLive ? "LIVE" : "VOD") (\(self.inAd ? "AD IS PLAYING" : "NO AD PLAYING"), \(self.backgroundaudioConfig.enabled ? "BGAUDIO ENABLED" : "BGAUDIO DISABLED") ).") }
100
+ if DEBUG_REMOTECOMMANDS { PrintUtils.printLog(logText: "[NATIVE] Remote commands updated for \(self.isLive ? "LIVE" : "VOD") (\(self.inAd ? "AD IS PLAYING" : "NO AD PLAYING")).") }
94
101
  }
95
102
 
96
103
  @objc private func onPlayCommand(_ event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
@@ -181,6 +188,32 @@ class THEOplayerRCTRemoteCommandsManager: NSObject {
181
188
  return .success
182
189
  }
183
190
 
191
+ @objc private func onPreviousTrackCommand(_ event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
192
+ if let player = self.player,
193
+ self.mediaControlConfig.convertSkipToSeek,
194
+ !self.isLive,
195
+ !self.inAd {
196
+ player.currentTime = player.currentTime - Double(self.mediaControlConfig.skipBackwardInterval)
197
+ if DEBUG_REMOTECOMMANDS { PrintUtils.printLog(logText: "[NATIVE] previous track command handled as skip backward command.") }
198
+ } else {
199
+ if DEBUG_REMOTECOMMANDS { PrintUtils.printLog(logText: "[NATIVE] previous track command not handled.") }
200
+ }
201
+ return .success
202
+ }
203
+
204
+ @objc private func onNextTrackCommand(_ event: MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus {
205
+ if let player = self.player,
206
+ self.mediaControlConfig.convertSkipToSeek,
207
+ !self.isLive,
208
+ !self.inAd {
209
+ player.currentTime = player.currentTime + Double(self.mediaControlConfig.skipForwardInterval)
210
+ if DEBUG_REMOTECOMMANDS { PrintUtils.printLog(logText: "[NATIVE] next track command handled as skip forward command.") }
211
+ } else {
212
+ if DEBUG_REMOTECOMMANDS { PrintUtils.printLog(logText: "[NATIVE] next track command not handled.") }
213
+ }
214
+ return .success
215
+ }
216
+
184
217
  private func attachListeners() {
185
218
  guard let player = self.player else {
186
219
  return
@@ -198,6 +231,7 @@ class THEOplayerRCTRemoteCommandsManager: NSObject {
198
231
  self.sourceChangeListener = player.addEventListener(type: PlayerEventTypes.SOURCE_CHANGE) { [weak self] event in
199
232
  self?.isLive = false
200
233
  self?.inAd = false
234
+ self?.hasSource = (event.source != nil)
201
235
  self?.updateRemoteCommands()
202
236
  }
203
237
 
@@ -2,9 +2,11 @@
2
2
 
3
3
  import Foundation
4
4
  import THEOplayerSDK
5
+ import AVFAudio
5
6
 
6
7
  struct BackgroundAudioConfig {
7
8
  var enabled: Bool = false
9
+ var shouldResumeAfterInterruption: Bool = false
8
10
  }
9
11
 
10
12
  extension THEOplayerRCTView: BackgroundPlaybackDelegate {
@@ -18,6 +20,9 @@ extension THEOplayerRCTView: BackgroundPlaybackDelegate {
18
20
  return
19
21
  }
20
22
  player.backgroundPlaybackDelegate = DefaultBackgroundPlaybackDelegate()
23
+ NotificationCenter.default.removeObserver(self,
24
+ name: AVAudioSession.interruptionNotification,
25
+ object: AVAudioSession.sharedInstance())
21
26
  }
22
27
 
23
28
  public func shouldContinueAudioPlaybackInBackground() -> Bool {
@@ -26,6 +31,52 @@ extension THEOplayerRCTView: BackgroundPlaybackDelegate {
26
31
 
27
32
  return self.backgroundAudioConfig.enabled
28
33
  }
34
+
35
+ func updateInterruptionNotifications() {
36
+ // Get the default notification center instance.
37
+ if self.backgroundAudioConfig.shouldResumeAfterInterruption {
38
+ NotificationCenter.default.addObserver(self,
39
+ selector: #selector(handleInterruption),
40
+ name: AVAudioSession.interruptionNotification,
41
+ object: AVAudioSession.sharedInstance())
42
+ } else {
43
+ NotificationCenter.default.removeObserver(self,
44
+ name: AVAudioSession.interruptionNotification,
45
+ object: AVAudioSession.sharedInstance())
46
+ }
47
+ }
48
+
49
+
50
+ @objc func handleInterruption(notification: Notification) {
51
+ guard let userInfo = notification.userInfo,
52
+ let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
53
+ let type = AVAudioSession.InterruptionType(rawValue: typeValue) else {
54
+ return
55
+ }
56
+
57
+ // Switch over the interruption type.
58
+ switch type {
59
+ case .began:
60
+ // An interruption began. Update the UI as necessary.
61
+ if DEBUG_INTERRUPTIONS { PrintUtils.printLog(logText: "[INTERRUPTION] An interruption began")}
62
+ case .ended:
63
+ // An interruption ended. Resume playback, if appropriate.
64
+ if DEBUG_INTERRUPTIONS { PrintUtils.printLog(logText: "[INTERRUPTION] An interruption ended")}
65
+ guard let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt else { return }
66
+ let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
67
+ if options.contains(.shouldResume) {
68
+ // An interruption ended. Resume playback.
69
+ if let player = self.player {
70
+ if DEBUG_INTERRUPTIONS { PrintUtils.printLog(logText: "[INTERRUPTION] Ended interruption should resume playback => play()")}
71
+ player.play()
72
+ }
73
+ } else {
74
+ // An interruption ended. Don't resume playback.
75
+ if DEBUG_INTERRUPTIONS { PrintUtils.printLog(logText: "[INTERRUPTION] Ended interruption should not resume playback.")}
76
+ }
77
+ default: ()
78
+ }
79
+ }
29
80
  }
30
81
 
31
82
  struct DefaultBackgroundPlaybackDelegate: BackgroundPlaybackDelegate {
@@ -6,6 +6,7 @@ import THEOplayerSDK
6
6
  struct MediaControlConfig {
7
7
  var skipForwardInterval: Int = 15
8
8
  var skipBackwardInterval: Int = 15
9
+ var convertSkipToSeek: Bool = false
9
10
  }
10
11
 
11
12
  extension THEOplayerRCTView {
@@ -18,6 +19,9 @@ extension THEOplayerRCTView {
18
19
  if let skipBackwardInterval = mediaControlConfig["skipBackwardInterval"] as? Int {
19
20
  self.mediaControlConfig.skipBackwardInterval = skipBackwardInterval
20
21
  }
22
+ if let convertSkipToSeek = mediaControlConfig["convertSkipToSeek"] as? Bool {
23
+ self.mediaControlConfig.convertSkipToSeek = convertSkipToSeek
24
+ }
21
25
  }
22
26
  }
23
27
  }
@@ -10,5 +10,12 @@ export interface BackgroundAudioConfiguration {
10
10
  * @defaultValue `false`
11
11
  */
12
12
  readonly enabled?: boolean;
13
+ /**
14
+ * Whether background audio should be resumed after an interruption (e.g. incoming call ended).
15
+ *
16
+ * @defaultValue `false`
17
+ * @remark Applies to iOS only, impacting behaviour for handling interruptions while on the lockscreen.
18
+ */
19
+ readonly shouldResumeAfterInterruption?: boolean;
13
20
  }
14
21
  //# sourceMappingURL=BackgroundAudioConfiguration.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"BackgroundAudioConfiguration.d.ts","sourceRoot":"","sources":["../../../../src/api/backgroundAudio/BackgroundAudioConfiguration.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,WAAW,4BAA4B;IAC3C;;;;OAIG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC5B"}
1
+ {"version":3,"file":"BackgroundAudioConfiguration.d.ts","sourceRoot":"","sources":["../../../../src/api/backgroundAudio/BackgroundAudioConfiguration.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,WAAW,4BAA4B;IAC3C;;;;OAIG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAE3B;;;;;OAKG;IACH,QAAQ,CAAC,6BAA6B,CAAC,EAAE,OAAO,CAAC;CAClD"}
@@ -35,5 +35,15 @@ export interface MediaControlConfiguration {
35
35
  * @defaultValue 5 on Web and android, 15 on iOS.
36
36
  */
37
37
  readonly skipBackwardInterval?: number;
38
+ /**
39
+ * A flag that allows next/previous track commands to be interpreted as skip
40
+ * forward/backward commands, according to the configured skip intervals.
41
+ *
42
+ * @defaultValue `false`
43
+ *
44
+ * @remarks
45
+ * <br/> - This property only applies to iOS and Android.
46
+ */
47
+ readonly convertSkipToSeek?: boolean;
38
48
  }
39
49
  //# sourceMappingURL=MediaControlConfiguration.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"MediaControlConfiguration.d.ts","sourceRoot":"","sources":["../../../../src/api/media/MediaControlConfiguration.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,yBAAyB;IACxC;;;;;;;OAOG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAEvC;;;;OAIG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAEtC;;;;OAIG;IACH,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;CACxC"}
1
+ {"version":3,"file":"MediaControlConfiguration.d.ts","sourceRoot":"","sources":["../../../../src/api/media/MediaControlConfiguration.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,yBAAyB;IACxC;;;;;;;OAOG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAEvC;;;;OAIG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAEtC;;;;OAIG;IACH,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAEvC;;;;;;;;OAQG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,OAAO,CAAC;CACtC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-theoplayer",
3
- "version": "7.7.1",
3
+ "version": "7.8.0",
4
4
  "description": "A THEOplayer video component for react-native.",
5
5
  "main": "lib/commonjs/index",
6
6
  "module": "lib/module/index",
@@ -10,4 +10,12 @@ export interface BackgroundAudioConfiguration {
10
10
  * @defaultValue `false`
11
11
  */
12
12
  readonly enabled?: boolean;
13
+
14
+ /**
15
+ * Whether background audio should be resumed after an interruption (e.g. incoming call ended).
16
+ *
17
+ * @defaultValue `false`
18
+ * @remark Applies to iOS only, impacting behaviour for handling interruptions while on the lockscreen.
19
+ */
20
+ readonly shouldResumeAfterInterruption?: boolean;
13
21
  }
@@ -37,4 +37,15 @@ export interface MediaControlConfiguration {
37
37
  * @defaultValue 5 on Web and android, 15 on iOS.
38
38
  */
39
39
  readonly skipBackwardInterval?: number;
40
+
41
+ /**
42
+ * A flag that allows next/previous track commands to be interpreted as skip
43
+ * forward/backward commands, according to the configured skip intervals.
44
+ *
45
+ * @defaultValue `false`
46
+ *
47
+ * @remarks
48
+ * <br/> - This property only applies to iOS and Android.
49
+ */
50
+ readonly convertSkipToSeek?: boolean;
40
51
  }