react-native-media-notification 0.3.4 → 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 +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,
|
|
@@ -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
|
@@ -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.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');
|