react-native-media-notification 0.3.4 → 0.3.6
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/README.md +2 -0
- package/android/src/main/java/com/mediacontrols/MediaControlsModule.kt +4 -4
- package/android/src/main/java/com/mediacontrols/MediaControlsPlayer.kt +62 -20
- package/ios/MediaControls.mm +12 -9
- package/lib/typescript/src/NativeMediaControls.d.ts +2 -2
- package/lib/typescript/src/NativeMediaControls.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/NativeMediaControls.ts +56 -56
package/README.md
CHANGED
|
@@ -149,6 +149,8 @@ MediaControls.addEventListener('duck', () => {}); // reduce volume for interrupt
|
|
|
149
149
|
MediaControls.addEventListener('unduck', () => {}); // restore volume after interruption
|
|
150
150
|
```
|
|
151
151
|
|
|
152
|
+
**Note**: On iOS, seekForward and seekBackward are not fired, even when technically triggered. Instead, a seek event with the corresponding timestamp is fired.
|
|
153
|
+
|
|
152
154
|
### Stop Media Notification
|
|
153
155
|
|
|
154
156
|
```typescript
|
|
@@ -58,11 +58,11 @@ class MediaControlsModule(reactContext: ReactApplicationContext) :
|
|
|
58
58
|
ensureServiceStarted()
|
|
59
59
|
|
|
60
60
|
val trackMetadata = MediaTrackMetadata(
|
|
61
|
-
title = metadata.getString("title")
|
|
62
|
-
artist = metadata.getString("artist")
|
|
63
|
-
album = metadata.getString("album"),
|
|
61
|
+
title = if (metadata.hasKey("title")) metadata.getString("title") else null,
|
|
62
|
+
artist = if (metadata.hasKey("artist")) metadata.getString("artist") else null,
|
|
63
|
+
album = if (metadata.hasKey("album")) metadata.getString("album") else null,
|
|
64
64
|
duration = if (metadata.hasKey("duration")) metadata.getDouble("duration") else null,
|
|
65
|
-
artwork = metadata.getString("artwork"),
|
|
65
|
+
artwork = if (metadata.hasKey("artwork")) metadata.getString("artwork") else null,
|
|
66
66
|
position = if (metadata.hasKey("position")) metadata.getDouble("position") else null,
|
|
67
67
|
isPlaying = if (metadata.hasKey("isPlaying")) metadata.getBoolean("isPlaying") else null,
|
|
68
68
|
shuffleMode = if (metadata.hasKey("shuffle")) metadata.getBoolean("shuffle") else null,
|
|
@@ -1,25 +1,32 @@
|
|
|
1
1
|
package com.mediacontrols
|
|
2
2
|
|
|
3
|
+
import android.graphics.Bitmap
|
|
4
|
+
import android.graphics.BitmapFactory
|
|
5
|
+
import android.graphics.drawable.BitmapDrawable
|
|
3
6
|
import android.os.Handler
|
|
4
7
|
import android.os.Looper
|
|
8
|
+
import android.webkit.URLUtil
|
|
9
|
+
import androidx.core.net.toUri
|
|
5
10
|
import androidx.media3.common.MediaItem
|
|
6
11
|
import androidx.media3.common.MediaMetadata
|
|
7
12
|
import androidx.media3.common.Player
|
|
8
13
|
import androidx.media3.common.SimpleBasePlayer
|
|
9
14
|
import androidx.media3.common.util.UnstableApi
|
|
15
|
+
import androidx.media3.session.CommandButton
|
|
10
16
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
17
|
+
import com.facebook.react.views.imagehelper.ResourceDrawableIdHelper
|
|
11
18
|
import com.google.common.util.concurrent.Futures
|
|
12
19
|
import com.google.common.util.concurrent.ListenableFuture
|
|
13
20
|
import kotlinx.coroutines.CoroutineScope
|
|
14
21
|
import kotlinx.coroutines.Dispatchers
|
|
15
22
|
import kotlinx.coroutines.SupervisorJob
|
|
16
23
|
import kotlinx.coroutines.cancel
|
|
17
|
-
import
|
|
18
|
-
|
|
24
|
+
import java.io.ByteArrayOutputStream
|
|
25
|
+
|
|
19
26
|
|
|
20
27
|
@UnstableApi
|
|
21
28
|
class MediaControlsPlayer(
|
|
22
|
-
reactContext: ReactApplicationContext,
|
|
29
|
+
private val reactContext: ReactApplicationContext,
|
|
23
30
|
private val module: MediaControlsModule,
|
|
24
31
|
) : SimpleBasePlayer(Looper.getMainLooper()) {
|
|
25
32
|
|
|
@@ -143,19 +150,19 @@ class MediaControlsPlayer(
|
|
|
143
150
|
audioFocusListener.requestAudioFocus()
|
|
144
151
|
}
|
|
145
152
|
|
|
146
|
-
this.currentMetadata = metadata
|
|
153
|
+
this.currentMetadata = this.currentMetadata?.merge(metadata) ?: metadata
|
|
147
154
|
|
|
148
155
|
val mediaMetadata = MediaMetadata.Builder()
|
|
149
|
-
.setTitle(
|
|
150
|
-
.setArtist(
|
|
151
|
-
.setAlbumTitle(
|
|
152
|
-
.setDurationMs(
|
|
156
|
+
.setTitle(this.currentMetadata!!.title)
|
|
157
|
+
.setArtist(this.currentMetadata!!.artist)
|
|
158
|
+
.setAlbumTitle(this.currentMetadata!!.album)
|
|
159
|
+
.setDurationMs(this.currentMetadata!!.duration?.times(1000)?.toLong())
|
|
153
160
|
.setIsPlayable(true)
|
|
154
161
|
.setIsBrowsable(false)
|
|
155
162
|
.setMediaType(MediaMetadata.MEDIA_TYPE_MUSIC)
|
|
156
|
-
.
|
|
157
|
-
|
|
158
|
-
|
|
163
|
+
.also {
|
|
164
|
+
this.currentMetadata!!.artwork?.let { artworkUrl ->
|
|
165
|
+
it.setArtwork(artworkUrl)
|
|
159
166
|
}
|
|
160
167
|
}
|
|
161
168
|
.build()
|
|
@@ -171,23 +178,23 @@ class MediaControlsPlayer(
|
|
|
171
178
|
|
|
172
179
|
val mediaItemData = MediaItemData.Builder(mediaId)
|
|
173
180
|
.setMediaItem(mediaItem)
|
|
174
|
-
.setDefaultPositionUs(
|
|
175
|
-
.setDurationUs(
|
|
181
|
+
.setDefaultPositionUs(this.currentMetadata!!.position?.times(1_000_000)?.toLong() ?: 0)
|
|
182
|
+
.setDurationUs(this.currentMetadata!!.duration?.times(1_000_000)?.toLong() ?: androidx.media3.common.C.TIME_UNSET)
|
|
176
183
|
.setIsSeekable(true)
|
|
177
184
|
.build()
|
|
178
185
|
|
|
179
186
|
updateState { builder ->
|
|
180
187
|
builder.setPlaylist(listOf(mediaItemData))
|
|
181
188
|
.setCurrentMediaItemIndex(0)
|
|
182
|
-
.setContentPositionMs(
|
|
189
|
+
.setContentPositionMs(this.currentMetadata!!.position?.times(1000)?.toLong() ?: 0)
|
|
183
190
|
.setPlayWhenReady(
|
|
184
|
-
|
|
191
|
+
this.currentMetadata!!.isPlaying ?: false,
|
|
185
192
|
Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST
|
|
186
193
|
)
|
|
187
194
|
.setPlaybackState(Player.STATE_READY)
|
|
188
195
|
.setAvailableCommands(state.availableCommands)
|
|
189
|
-
.setRepeatMode(
|
|
190
|
-
.setShuffleModeEnabled(
|
|
196
|
+
.setRepeatMode(this.currentMetadata!!.repeatMode)
|
|
197
|
+
.setShuffleModeEnabled(this.currentMetadata!!.shuffleMode)
|
|
191
198
|
}
|
|
192
199
|
}
|
|
193
200
|
|
|
@@ -211,6 +218,28 @@ class MediaControlsPlayer(
|
|
|
211
218
|
return this.setShuffleModeEnabled(enabled)
|
|
212
219
|
}
|
|
213
220
|
|
|
221
|
+
private fun MediaMetadata.Builder.setArtwork(artwork: String?): MediaMetadata.Builder {
|
|
222
|
+
if (artwork == null) {
|
|
223
|
+
return this
|
|
224
|
+
}
|
|
225
|
+
if (URLUtil.isValidUrl(artwork)) {
|
|
226
|
+
this.setArtworkUri(artwork.toUri())
|
|
227
|
+
} else {
|
|
228
|
+
val helper = ResourceDrawableIdHelper.getInstance()
|
|
229
|
+
val image = helper.getResourceDrawable(reactContext, artwork)
|
|
230
|
+
|
|
231
|
+
val bitmap = if (image is BitmapDrawable) {
|
|
232
|
+
image.bitmap
|
|
233
|
+
} else {
|
|
234
|
+
BitmapFactory.decodeFile(artwork)
|
|
235
|
+
}
|
|
236
|
+
val stream = ByteArrayOutputStream()
|
|
237
|
+
bitmap.compress(Bitmap.CompressFormat.PNG, 90, stream)
|
|
238
|
+
this.setArtworkData(stream.toByteArray(), MediaMetadata.PICTURE_TYPE_OTHER)
|
|
239
|
+
}
|
|
240
|
+
return this
|
|
241
|
+
}
|
|
242
|
+
|
|
214
243
|
fun setControlEnabled(controlName: Controls, enabled: Boolean) {
|
|
215
244
|
enabledControls[controlName] = enabled
|
|
216
245
|
|
|
@@ -313,8 +342,8 @@ class MediaControlsPlayer(
|
|
|
313
342
|
|
|
314
343
|
// Data class for metadata
|
|
315
344
|
data class MediaTrackMetadata(
|
|
316
|
-
val title: String,
|
|
317
|
-
val artist: String,
|
|
345
|
+
val title: String? = null,
|
|
346
|
+
val artist: String? = null,
|
|
318
347
|
val album: String? = null,
|
|
319
348
|
val duration: Double? = null,
|
|
320
349
|
val artwork: String? = null,
|
|
@@ -322,4 +351,17 @@ data class MediaTrackMetadata(
|
|
|
322
351
|
val isPlaying: Boolean? = null,
|
|
323
352
|
val repeatMode: String? = null,
|
|
324
353
|
val shuffleMode: Boolean? = null
|
|
325
|
-
)
|
|
354
|
+
) {
|
|
355
|
+
|
|
356
|
+
fun merge(other: MediaTrackMetadata): MediaTrackMetadata = MediaTrackMetadata(
|
|
357
|
+
title = other.title ?: this.title,
|
|
358
|
+
artist = other.artist ?: this.artist,
|
|
359
|
+
album = other.album ?: this.album,
|
|
360
|
+
duration = other.duration ?: this.duration,
|
|
361
|
+
artwork = other.artwork ?: this.artwork,
|
|
362
|
+
position = other.position ?: this.position,
|
|
363
|
+
isPlaying = other.isPlaying ?: this.isPlaying,
|
|
364
|
+
repeatMode = other.repeatMode ?: this.repeatMode,
|
|
365
|
+
shuffleMode = other.shuffleMode ?: this.shuffleMode
|
|
366
|
+
)
|
|
367
|
+
}
|
package/ios/MediaControls.mm
CHANGED
|
@@ -87,17 +87,17 @@ RCT_EXPORT_METHOD(updateMetadata:(JS::NativeMediaControls::NativeMediaTrackMetad
|
|
|
87
87
|
MPNowPlayingInfoCenter *_nowPlayingCenter = [MPNowPlayingInfoCenter defaultCenter];
|
|
88
88
|
|
|
89
89
|
@try {
|
|
90
|
-
NSMutableDictionary *nowPlayingInfo = [NSMutableDictionary
|
|
90
|
+
NSMutableDictionary *nowPlayingInfo = [[NSMutableDictionary alloc] initWithDictionary: _nowPlayingCenter.nowPlayingInfo];
|
|
91
91
|
|
|
92
|
-
if (metadata.title().length > 0) {
|
|
92
|
+
if (metadata.title() != nil && metadata.title().length > 0) {
|
|
93
93
|
nowPlayingInfo[MPMediaItemPropertyTitle] = metadata.title();
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
if (metadata.artist().length > 0) {
|
|
96
|
+
if (metadata.artist() != nil && metadata.artist().length > 0) {
|
|
97
97
|
nowPlayingInfo[MPMediaItemPropertyArtist] = metadata.artist();
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
if (metadata.album().length > 0) {
|
|
100
|
+
if (metadata.album() != nil && metadata.album().length > 0) {
|
|
101
101
|
nowPlayingInfo[MPMediaItemPropertyAlbumTitle] = metadata.album();
|
|
102
102
|
}
|
|
103
103
|
|
|
@@ -110,17 +110,20 @@ RCT_EXPORT_METHOD(updateMetadata:(JS::NativeMediaControls::NativeMediaTrackMetad
|
|
|
110
110
|
double position = metadata.position().value();
|
|
111
111
|
nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = [NSNumber numberWithDouble:position];
|
|
112
112
|
}
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
|
|
114
|
+
if (metadata.isPlaying().has_value()) {
|
|
115
|
+
nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = metadata.isPlaying().value() ? [NSNumber numberWithDouble:1] : [NSNumber numberWithDouble:0];
|
|
116
|
+
}
|
|
117
|
+
|
|
115
118
|
|
|
116
119
|
_nowPlayingCenter.nowPlayingInfo = nowPlayingInfo;
|
|
117
120
|
|
|
118
|
-
if (@available(iOS 11.0, *)) {
|
|
121
|
+
if (@available(iOS 11.0, *) && metadata.isPlaying().has_value()) {
|
|
119
122
|
if (!self.audioInterrupted) {
|
|
120
123
|
self.explictlyPaused = false;
|
|
121
124
|
}
|
|
122
125
|
|
|
123
|
-
if (metadata.isPlaying()) {
|
|
126
|
+
if (metadata.isPlaying().value()) {
|
|
124
127
|
_nowPlayingCenter.playbackState = MPNowPlayingPlaybackStatePlaying;
|
|
125
128
|
} else {
|
|
126
129
|
_nowPlayingCenter.playbackState = MPNowPlayingPlaybackStatePaused;
|
|
@@ -133,7 +136,7 @@ RCT_EXPORT_METHOD(updateMetadata:(JS::NativeMediaControls::NativeMediaTrackMetad
|
|
|
133
136
|
|
|
134
137
|
|
|
135
138
|
// Load artwork if provided
|
|
136
|
-
if (metadata.artwork().length > 0) {
|
|
139
|
+
if (metadata.artwork() != nil && metadata.artwork().length > 0) {
|
|
137
140
|
NSString *artworkURL = metadata.artwork();
|
|
138
141
|
[self loadArtworkFromURL:artworkURL completion:^(UIImage *image) {
|
|
139
142
|
if (!image) {
|
|
@@ -4,8 +4,8 @@ export declare const ALL_MEDIA_EVENTS: readonly ["play", "pause", "stop", "skipT
|
|
|
4
4
|
export type MediaControl = (typeof ALL_MEDIA_EVENTS)[number];
|
|
5
5
|
export type MediaControlEvent = MediaControl | 'duck' | 'unDuck';
|
|
6
6
|
export interface NativeMediaTrackMetadata {
|
|
7
|
-
title
|
|
8
|
-
artist
|
|
7
|
+
title?: string;
|
|
8
|
+
artist?: string;
|
|
9
9
|
album?: string;
|
|
10
10
|
duration?: number;
|
|
11
11
|
artwork?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NativeMediaControls.d.ts","sourceRoot":"","sources":["../../../src/NativeMediaControls.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2CAA2C,CAAC;AAG9E,eAAO,MAAM,gBAAgB,oIAWnB,CAAC;AACX,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE7D,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEjE,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"NativeMediaControls.d.ts","sourceRoot":"","sources":["../../../src/NativeMediaControls.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2CAA2C,CAAC;AAG9E,eAAO,MAAM,gBAAgB,oIAWnB,CAAC;AACX,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE7D,MAAM,MAAM,iBAAiB,GAAG,YAAY,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEjE,MAAM,WAAW,wBAAwB;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;IACnC,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IACxD,cAAc,CAAC,QAAQ,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAGvC,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAGzD,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IAE7C,QAAQ,IAAI,IAAI,CAAC;IAGjB,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC,WAAW,CAAC,CAAC;CAC7C;;AAED,wBAAuE"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-media-notification",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.6",
|
|
4
4
|
"description": "Display and manage media style notifications based on react-native-music-control",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|
|
@@ -1,56 +1,56 @@
|
|
|
1
|
-
import type { TurboModule } from 'react-native';
|
|
2
|
-
import { TurboModuleRegistry } from 'react-native';
|
|
3
|
-
import type { EventEmitter } from 'react-native/Libraries/Types/CodegenTypes';
|
|
4
|
-
|
|
5
|
-
// Event types
|
|
6
|
-
export const ALL_MEDIA_EVENTS = [
|
|
7
|
-
'play',
|
|
8
|
-
'pause',
|
|
9
|
-
'stop',
|
|
10
|
-
'skipToNext',
|
|
11
|
-
'skipToPrevious',
|
|
12
|
-
'seekForward',
|
|
13
|
-
'seekBackward',
|
|
14
|
-
'seek',
|
|
15
|
-
'shuffle',
|
|
16
|
-
'repeatMode',
|
|
17
|
-
] as const;
|
|
18
|
-
export type MediaControl = (typeof ALL_MEDIA_EVENTS)[number];
|
|
19
|
-
|
|
20
|
-
export type MediaControlEvent = MediaControl | 'duck' | 'unDuck';
|
|
21
|
-
|
|
22
|
-
export interface NativeMediaTrackMetadata {
|
|
23
|
-
title
|
|
24
|
-
artist
|
|
25
|
-
album?: string;
|
|
26
|
-
duration?: number;
|
|
27
|
-
artwork?: string;
|
|
28
|
-
position?: number;
|
|
29
|
-
isPlaying?: boolean;
|
|
30
|
-
repeatMode?: 'off' | 'one' | 'all';
|
|
31
|
-
shuffle?: boolean;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export interface NativeEvent {
|
|
35
|
-
command: string;
|
|
36
|
-
seekPosition?: number; // Position in seconds for seek events
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface Spec extends TurboModule {
|
|
40
|
-
setControlEnabled(name: string, enabled: boolean): void;
|
|
41
|
-
updateMetadata(metadata: NativeMediaTrackMetadata): Promise<void>;
|
|
42
|
-
stopMediaNotification(): Promise<void>;
|
|
43
|
-
|
|
44
|
-
// Audio interruption handling
|
|
45
|
-
enableAudioInterruption(enabled: boolean): Promise<void>;
|
|
46
|
-
|
|
47
|
-
// Audio session activation
|
|
48
|
-
enableBackgroundMode(enabled: boolean): void;
|
|
49
|
-
|
|
50
|
-
shutdown(): void;
|
|
51
|
-
|
|
52
|
-
// Event listeners (native events will be emitted)
|
|
53
|
-
readonly onEvent: EventEmitter<NativeEvent>;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export default TurboModuleRegistry.getEnforcing<Spec>('MediaControls');
|
|
1
|
+
import type { TurboModule } from 'react-native';
|
|
2
|
+
import { TurboModuleRegistry } from 'react-native';
|
|
3
|
+
import type { EventEmitter } from 'react-native/Libraries/Types/CodegenTypes';
|
|
4
|
+
|
|
5
|
+
// Event types
|
|
6
|
+
export const ALL_MEDIA_EVENTS = [
|
|
7
|
+
'play',
|
|
8
|
+
'pause',
|
|
9
|
+
'stop',
|
|
10
|
+
'skipToNext',
|
|
11
|
+
'skipToPrevious',
|
|
12
|
+
'seekForward',
|
|
13
|
+
'seekBackward',
|
|
14
|
+
'seek',
|
|
15
|
+
'shuffle',
|
|
16
|
+
'repeatMode',
|
|
17
|
+
] as const;
|
|
18
|
+
export type MediaControl = (typeof ALL_MEDIA_EVENTS)[number];
|
|
19
|
+
|
|
20
|
+
export type MediaControlEvent = MediaControl | 'duck' | 'unDuck';
|
|
21
|
+
|
|
22
|
+
export interface NativeMediaTrackMetadata {
|
|
23
|
+
title?: string;
|
|
24
|
+
artist?: string;
|
|
25
|
+
album?: string;
|
|
26
|
+
duration?: number;
|
|
27
|
+
artwork?: string;
|
|
28
|
+
position?: number;
|
|
29
|
+
isPlaying?: boolean;
|
|
30
|
+
repeatMode?: 'off' | 'one' | 'all';
|
|
31
|
+
shuffle?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface NativeEvent {
|
|
35
|
+
command: string;
|
|
36
|
+
seekPosition?: number; // Position in seconds for seek events
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface Spec extends TurboModule {
|
|
40
|
+
setControlEnabled(name: string, enabled: boolean): void;
|
|
41
|
+
updateMetadata(metadata: NativeMediaTrackMetadata): Promise<void>;
|
|
42
|
+
stopMediaNotification(): Promise<void>;
|
|
43
|
+
|
|
44
|
+
// Audio interruption handling
|
|
45
|
+
enableAudioInterruption(enabled: boolean): Promise<void>;
|
|
46
|
+
|
|
47
|
+
// Audio session activation
|
|
48
|
+
enableBackgroundMode(enabled: boolean): void;
|
|
49
|
+
|
|
50
|
+
shutdown(): void;
|
|
51
|
+
|
|
52
|
+
// Event listeners (native events will be emitted)
|
|
53
|
+
readonly onEvent: EventEmitter<NativeEvent>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export default TurboModuleRegistry.getEnforcing<Spec>('MediaControls');
|