react-native-media-notification 0.3.3 → 0.3.5
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 +30 -17
- package/ios/MediaControls.mm +44 -11
- 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,
|
|
@@ -143,19 +143,19 @@ class MediaControlsPlayer(
|
|
|
143
143
|
audioFocusListener.requestAudioFocus()
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
this.currentMetadata = metadata
|
|
146
|
+
this.currentMetadata = this.currentMetadata?.merge(metadata) ?: metadata
|
|
147
147
|
|
|
148
148
|
val mediaMetadata = MediaMetadata.Builder()
|
|
149
|
-
.setTitle(
|
|
150
|
-
.setArtist(
|
|
151
|
-
.setAlbumTitle(
|
|
152
|
-
.setDurationMs(
|
|
149
|
+
.setTitle(this.currentMetadata!!.title)
|
|
150
|
+
.setArtist(this.currentMetadata!!.artist)
|
|
151
|
+
.setAlbumTitle(this.currentMetadata!!.album)
|
|
152
|
+
.setDurationMs(this.currentMetadata!!.duration?.times(1000)?.toLong())
|
|
153
153
|
.setIsPlayable(true)
|
|
154
154
|
.setIsBrowsable(false)
|
|
155
155
|
.setMediaType(MediaMetadata.MEDIA_TYPE_MUSIC)
|
|
156
|
-
.
|
|
157
|
-
|
|
158
|
-
setArtworkUri(artworkUrl.toUri())
|
|
156
|
+
.also {
|
|
157
|
+
this.currentMetadata!!.artwork?.let { artworkUrl ->
|
|
158
|
+
it.setArtworkUri(artworkUrl.toUri())
|
|
159
159
|
}
|
|
160
160
|
}
|
|
161
161
|
.build()
|
|
@@ -171,23 +171,23 @@ class MediaControlsPlayer(
|
|
|
171
171
|
|
|
172
172
|
val mediaItemData = MediaItemData.Builder(mediaId)
|
|
173
173
|
.setMediaItem(mediaItem)
|
|
174
|
-
.setDefaultPositionUs(
|
|
175
|
-
.setDurationUs(
|
|
174
|
+
.setDefaultPositionUs(this.currentMetadata!!.position?.times(1_000_000)?.toLong() ?: 0)
|
|
175
|
+
.setDurationUs(this.currentMetadata!!.duration?.times(1_000_000)?.toLong() ?: androidx.media3.common.C.TIME_UNSET)
|
|
176
176
|
.setIsSeekable(true)
|
|
177
177
|
.build()
|
|
178
178
|
|
|
179
179
|
updateState { builder ->
|
|
180
180
|
builder.setPlaylist(listOf(mediaItemData))
|
|
181
181
|
.setCurrentMediaItemIndex(0)
|
|
182
|
-
.setContentPositionMs(
|
|
182
|
+
.setContentPositionMs(this.currentMetadata!!.position?.times(1000)?.toLong() ?: 0)
|
|
183
183
|
.setPlayWhenReady(
|
|
184
|
-
|
|
184
|
+
this.currentMetadata!!.isPlaying ?: false,
|
|
185
185
|
Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST
|
|
186
186
|
)
|
|
187
187
|
.setPlaybackState(Player.STATE_READY)
|
|
188
188
|
.setAvailableCommands(state.availableCommands)
|
|
189
|
-
.setRepeatMode(
|
|
190
|
-
.setShuffleModeEnabled(
|
|
189
|
+
.setRepeatMode(this.currentMetadata!!.repeatMode)
|
|
190
|
+
.setShuffleModeEnabled(this.currentMetadata!!.shuffleMode)
|
|
191
191
|
}
|
|
192
192
|
}
|
|
193
193
|
|
|
@@ -313,8 +313,8 @@ class MediaControlsPlayer(
|
|
|
313
313
|
|
|
314
314
|
// Data class for metadata
|
|
315
315
|
data class MediaTrackMetadata(
|
|
316
|
-
val title: String,
|
|
317
|
-
val artist: String,
|
|
316
|
+
val title: String? = null,
|
|
317
|
+
val artist: String? = null,
|
|
318
318
|
val album: String? = null,
|
|
319
319
|
val duration: Double? = null,
|
|
320
320
|
val artwork: String? = null,
|
|
@@ -322,4 +322,17 @@ data class MediaTrackMetadata(
|
|
|
322
322
|
val isPlaying: Boolean? = null,
|
|
323
323
|
val repeatMode: String? = null,
|
|
324
324
|
val shuffleMode: Boolean? = null
|
|
325
|
-
)
|
|
325
|
+
) {
|
|
326
|
+
|
|
327
|
+
fun merge(other: MediaTrackMetadata): MediaTrackMetadata = MediaTrackMetadata(
|
|
328
|
+
title = other.title ?: this.title,
|
|
329
|
+
artist = other.artist ?: this.artist,
|
|
330
|
+
album = other.album ?: this.album,
|
|
331
|
+
duration = other.duration ?: this.duration,
|
|
332
|
+
artwork = other.artwork ?: this.artwork,
|
|
333
|
+
position = other.position ?: this.position,
|
|
334
|
+
isPlaying = other.isPlaying ?: this.isPlaying,
|
|
335
|
+
repeatMode = other.repeatMode ?: this.repeatMode,
|
|
336
|
+
shuffleMode = other.shuffleMode ?: this.shuffleMode
|
|
337
|
+
)
|
|
338
|
+
}
|
package/ios/MediaControls.mm
CHANGED
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
@property (nonatomic, assign) BOOL hasListeners;
|
|
8
8
|
@property (nonatomic, assign) BOOL audioInterrupted;
|
|
9
9
|
@property (nonatomic, assign) BOOL explictlyPaused;
|
|
10
|
+
@property (nonatomic, assign) BOOL isSeeking;
|
|
11
|
+
@property (nonatomic, assign) BOOL isBackwardSeeking;
|
|
12
|
+
@property (nonatomic, assign) float seekTime;
|
|
13
|
+
@property (nonatomic, assign) NSTimer* seekTimer;
|
|
10
14
|
@end
|
|
11
15
|
|
|
12
16
|
@implementation MediaControls
|
|
@@ -83,17 +87,17 @@ RCT_EXPORT_METHOD(updateMetadata:(JS::NativeMediaControls::NativeMediaTrackMetad
|
|
|
83
87
|
MPNowPlayingInfoCenter *_nowPlayingCenter = [MPNowPlayingInfoCenter defaultCenter];
|
|
84
88
|
|
|
85
89
|
@try {
|
|
86
|
-
NSMutableDictionary *nowPlayingInfo = [NSMutableDictionary
|
|
90
|
+
NSMutableDictionary *nowPlayingInfo = [[NSMutableDictionary alloc] initWithDictionary: _nowPlayingCenter.nowPlayingInfo];
|
|
87
91
|
|
|
88
|
-
if (metadata.title().length > 0) {
|
|
92
|
+
if (metadata.title() != nil && metadata.title().length > 0) {
|
|
89
93
|
nowPlayingInfo[MPMediaItemPropertyTitle] = metadata.title();
|
|
90
94
|
}
|
|
91
95
|
|
|
92
|
-
if (metadata.artist().length > 0) {
|
|
96
|
+
if (metadata.artist() != nil && metadata.artist().length > 0) {
|
|
93
97
|
nowPlayingInfo[MPMediaItemPropertyArtist] = metadata.artist();
|
|
94
98
|
}
|
|
95
99
|
|
|
96
|
-
if (metadata.album().length > 0) {
|
|
100
|
+
if (metadata.album() != nil && metadata.album().length > 0) {
|
|
97
101
|
nowPlayingInfo[MPMediaItemPropertyAlbumTitle] = metadata.album();
|
|
98
102
|
}
|
|
99
103
|
|
|
@@ -106,17 +110,20 @@ RCT_EXPORT_METHOD(updateMetadata:(JS::NativeMediaControls::NativeMediaTrackMetad
|
|
|
106
110
|
double position = metadata.position().value();
|
|
107
111
|
nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = [NSNumber numberWithDouble:position];
|
|
108
112
|
}
|
|
109
|
-
|
|
110
|
-
|
|
113
|
+
|
|
114
|
+
if (metadata.isPlaying().has_value()) {
|
|
115
|
+
nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = metadata.isPlaying().value() ? [NSNumber numberWithDouble:1] : [NSNumber numberWithDouble:0];
|
|
116
|
+
}
|
|
117
|
+
|
|
111
118
|
|
|
112
119
|
_nowPlayingCenter.nowPlayingInfo = nowPlayingInfo;
|
|
113
120
|
|
|
114
|
-
if (@available(iOS 11.0, *)) {
|
|
121
|
+
if (@available(iOS 11.0, *) && metadata.isPlaying().has_value()) {
|
|
115
122
|
if (!self.audioInterrupted) {
|
|
116
123
|
self.explictlyPaused = false;
|
|
117
124
|
}
|
|
118
125
|
|
|
119
|
-
if (metadata.isPlaying()) {
|
|
126
|
+
if (metadata.isPlaying().value()) {
|
|
120
127
|
_nowPlayingCenter.playbackState = MPNowPlayingPlaybackStatePlaying;
|
|
121
128
|
} else {
|
|
122
129
|
_nowPlayingCenter.playbackState = MPNowPlayingPlaybackStatePaused;
|
|
@@ -129,7 +136,7 @@ RCT_EXPORT_METHOD(updateMetadata:(JS::NativeMediaControls::NativeMediaTrackMetad
|
|
|
129
136
|
|
|
130
137
|
|
|
131
138
|
// Load artwork if provided
|
|
132
|
-
if (metadata.artwork().length > 0) {
|
|
139
|
+
if (metadata.artwork() != nil && metadata.artwork().length > 0) {
|
|
133
140
|
NSString *artworkURL = metadata.artwork();
|
|
134
141
|
[self loadArtworkFromURL:artworkURL completion:^(UIImage *image) {
|
|
135
142
|
if (!image) {
|
|
@@ -256,12 +263,29 @@ RCT_EXPORT_METHOD(enableBackgroundMode:(BOOL) enabled){
|
|
|
256
263
|
}
|
|
257
264
|
|
|
258
265
|
- (MPRemoteCommandHandlerStatus)handleSeekForwardCommand:(MPRemoteCommandEvent *)event {
|
|
259
|
-
|
|
266
|
+
if (self.isSeeking) {
|
|
267
|
+
[self emitEvent:@"seek" position:[NSNumber numberWithFloat:self.seekTime]];
|
|
268
|
+
self.isSeeking = false;
|
|
269
|
+
[self.seekTimer invalidate];
|
|
270
|
+
self.seekTimer = nil;
|
|
271
|
+
return MPRemoteCommandHandlerStatusSuccess;
|
|
272
|
+
}
|
|
273
|
+
self.isSeeking = true;
|
|
274
|
+
self.isBackwardSeeking = false;
|
|
275
|
+
|
|
276
|
+
MPNowPlayingInfoCenter *center = [MPNowPlayingInfoCenter defaultCenter];
|
|
277
|
+
self.seekTime = [[center.nowPlayingInfo objectForKey:MPNowPlayingInfoPropertyElapsedPlaybackTime] floatValue];
|
|
278
|
+
self.seekTimer = [NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:@selector(seekTimerCallback) userInfo:nil repeats:YES];
|
|
260
279
|
return MPRemoteCommandHandlerStatusSuccess;
|
|
261
280
|
}
|
|
262
281
|
|
|
263
282
|
- (MPRemoteCommandHandlerStatus)handleSeekBackwardCommand:(MPRemoteCommandEvent *)event {
|
|
264
|
-
|
|
283
|
+
self.isSeeking = true;
|
|
284
|
+
self.isBackwardSeeking = true;
|
|
285
|
+
|
|
286
|
+
MPNowPlayingInfoCenter *center = [MPNowPlayingInfoCenter defaultCenter];
|
|
287
|
+
self.seekTime = [[center.nowPlayingInfo objectForKey:MPNowPlayingInfoPropertyElapsedPlaybackTime] floatValue];
|
|
288
|
+
self.seekTimer = [NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:@selector(seekTimerCallback) userInfo:nil repeats:YES];
|
|
265
289
|
return MPRemoteCommandHandlerStatusSuccess;
|
|
266
290
|
}
|
|
267
291
|
|
|
@@ -326,6 +350,15 @@ RCT_EXPORT_METHOD(enableBackgroundMode:(BOOL) enabled){
|
|
|
326
350
|
});
|
|
327
351
|
}
|
|
328
352
|
|
|
353
|
+
-(void)seekTimerCallback{
|
|
354
|
+
self.seekTime += 2 * (self.isBackwardSeeking ? -1 : 1);
|
|
355
|
+
|
|
356
|
+
MPNowPlayingInfoCenter *center = [MPNowPlayingInfoCenter defaultCenter];
|
|
357
|
+
NSMutableDictionary *mediaDict = [[NSMutableDictionary alloc] initWithDictionary: center.nowPlayingInfo];
|
|
358
|
+
mediaDict[MPNowPlayingInfoPropertyElapsedPlaybackTime] = @(self.seekTime);
|
|
359
|
+
center.nowPlayingInfo = mediaDict;
|
|
360
|
+
}
|
|
361
|
+
|
|
329
362
|
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
|
|
330
363
|
(const facebook::react::ObjCTurboModule::InitParams &)params
|
|
331
364
|
{
|
|
@@ -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.5",
|
|
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');
|