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.
- package/CHANGELOG.md +18 -0
- package/README.md +13 -12
- package/android/src/main/AndroidManifest.xml +0 -11
- package/android/src/main/java/com/theoplayer/ReactTHEOplayerContext.kt +7 -1
- package/android/src/main/java/com/theoplayer/media/MediaNotificationBuilder.kt +26 -2
- package/android/src/main/java/com/theoplayer/media/MediaPlaybackService.kt +12 -58
- package/android/src/main/java/com/theoplayer/media/MediaQueueNavigator.kt +33 -0
- package/android/src/main/java/com/theoplayer/media/MediaSessionConfig.kt +5 -0
- package/android/src/main/java/com/theoplayer/media/MediaSessionConfigAdapter.kt +4 -0
- package/android/src/main/java/com/theoplayer/presentation/PipUtils.kt +8 -8
- package/android/src/main/res/values/strings.xml +7 -9
- package/ios/THEOplayerRCTDebug.swift +3 -0
- package/ios/THEOplayerRCTPlayerAPI.swift +6 -9
- package/ios/THEOplayerRCTView.swift +4 -4
- package/ios/backgroundAudio/THEOplayerRCTRemoteCommandsManager.swift +53 -19
- package/ios/backgroundAudio/THEOplayerRCTView+BackgroundAudioConfig.swift +51 -0
- package/ios/backgroundAudio/THEOplayerRCTView+MediaControlConfig.swift +4 -0
- package/lib/typescript/api/backgroundAudio/BackgroundAudioConfiguration.d.ts +7 -0
- package/lib/typescript/api/backgroundAudio/BackgroundAudioConfiguration.d.ts.map +1 -1
- package/lib/typescript/api/media/MediaControlConfiguration.d.ts +10 -0
- package/lib/typescript/api/media/MediaControlConfiguration.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/api/backgroundAudio/BackgroundAudioConfiguration.ts +8 -0
- 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
|
|
135
|
-
|
|
136
|
-
| Adobe analytics
|
|
137
|
-
|
|
|
138
|
-
|
|
|
139
|
-
|
|
|
140
|
-
|
|
|
141
|
-
|
|
|
142
|
-
|
|
|
143
|
-
|
|
|
144
|
-
|
|
|
145
|
-
|
|
|
134
|
+
| Connector | npm package | Source |
|
|
135
|
+
|-----------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------|
|
|
136
|
+
| Adobe Heartbeat analytics using the Media Collections API | [](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 | [](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 | [](https://www.npmjs.com/package/%40theoplayer%2Freact-native-analytics-agama) | [`Agama`](https://github.com/THEOplayer/react-native-connectors/tree/main/agama) |
|
|
139
|
+
| Comscore analytics | [](https://www.npmjs.com/package/%40theoplayer%2Freact-native-analytics-comscore) | [`Comscore`](https://github.com/THEOplayer/react-native-connectors/tree/main/comscore) |
|
|
140
|
+
| Conviva analytics | [](https://www.npmjs.com/package/%40theoplayer%2Freact-native-analytics-conviva) | [`Conviva`](https://github.com/THEOplayer/react-native-connectors/tree/main/conviva) |
|
|
141
|
+
| Mux analytics | [](https://www.npmjs.com/package/%40theoplayer%2Freact-native-analytics-mux) | [`Mux`](https://github.com/THEOplayer/react-native-connectors/tree/main/mux) |
|
|
142
|
+
| Nielsen analytics | [](https://www.npmjs.com/package/%40theoplayer%2Freact-native-analytics-nielsen) | [`Nielsen`](https://github.com/THEOplayer/react-native-connectors/tree/main/nielsen) |
|
|
143
|
+
| Youbora analytics | [](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) | [](https://www.npmjs.com/package/%40theoplayer%2Freact-native-drm) | [`DRM`](https://github.com/THEOplayer/react-native-theoplayer-drm) |
|
|
145
|
+
| React Native Open 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. | [](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(
|
|
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
|
|
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
|
-
|
|
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 :
|
|
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
|
|
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(
|
|
62
|
-
|
|
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
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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.
|
|
134
|
-
R.string.
|
|
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.
|
|
148
|
-
R.string.
|
|
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.
|
|
156
|
-
R.string.
|
|
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.
|
|
169
|
-
R.string.
|
|
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="
|
|
6
|
-
<string name="
|
|
7
|
-
<string name="
|
|
8
|
-
<string name="
|
|
9
|
-
<string name="
|
|
10
|
-
<string name="
|
|
11
|
-
<string name="
|
|
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>
|
|
@@ -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
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
38
|
-
|
|
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
|
|
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 =
|
|
52
|
-
commandCenter.pauseCommand.isEnabled =
|
|
53
|
-
commandCenter.togglePlayPauseCommand.isEnabled =
|
|
54
|
-
commandCenter.stopCommand.isEnabled =
|
|
55
|
-
commandCenter.changePlaybackPositionCommand.isEnabled =
|
|
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.
|
|
82
|
-
commandCenter.
|
|
83
|
-
commandCenter.
|
|
84
|
-
commandCenter.
|
|
85
|
-
commandCenter.
|
|
86
|
-
commandCenter.
|
|
87
|
-
commandCenter.
|
|
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")
|
|
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;
|
|
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;
|
|
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
|
@@ -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
|
}
|