react-native-media-notification 0.2.0 → 0.3.1
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/android/src/main/java/com/mediacontrols/CustomCommandButton.kt +26 -4
- package/android/src/main/java/com/mediacontrols/MediaControlsModule.kt +4 -0
- package/android/src/main/java/com/mediacontrols/MediaControlsPlayer.kt +6 -2
- package/android/src/main/java/com/mediacontrols/MediaControlsService.kt +8 -0
- package/ios/MediaControls.mm +35 -0
- package/lib/module/NativeMediaControls.js.map +1 -1
- package/lib/module/index.js +16 -13
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/NativeMediaControls.d.ts +1 -0
- package/lib/typescript/src/NativeMediaControls.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/NativeMediaControls.ts +56 -54
- package/src/index.tsx +91 -87
|
@@ -1,37 +1,59 @@
|
|
|
1
1
|
package com.mediacontrols
|
|
2
2
|
|
|
3
3
|
import android.os.Bundle
|
|
4
|
+
import androidx.media3.common.util.UnstableApi
|
|
4
5
|
import androidx.media3.session.CommandButton
|
|
5
|
-
import androidx.media3.session.CommandButton.
|
|
6
|
-
import androidx.media3.session.CommandButton.
|
|
6
|
+
import androidx.media3.session.CommandButton.ICON_NEXT
|
|
7
|
+
import androidx.media3.session.CommandButton.ICON_PREVIOUS
|
|
7
8
|
import androidx.media3.session.CommandButton.ICON_SHUFFLE_OFF
|
|
8
9
|
import androidx.media3.session.CommandButton.ICON_SHUFFLE_ON
|
|
10
|
+
import androidx.media3.session.CommandButton.ICON_SKIP_BACK_15
|
|
11
|
+
import androidx.media3.session.CommandButton.ICON_SKIP_FORWARD_15
|
|
9
12
|
import androidx.media3.session.SessionCommand
|
|
10
13
|
|
|
11
14
|
private const val CUSTOM_COMMAND_REWIND_ACTION_ID = "REWIND_15"
|
|
12
15
|
private const val CUSTOM_COMMAND_FORWARD_ACTION_ID = "FAST_FWD_15"
|
|
16
|
+
private const val CUSTOM_COMMAND_NEXT_ACTION_ID = "SKIP_TO_NEXT"
|
|
17
|
+
private const val CUSTOM_COMMAND_PREVIOUS_ACTION_ID = "SKIP_TO_PREVIOUS"
|
|
13
18
|
private const val CUSTOM_COMMAND_SHUFFLE_ON_ACTION_ID = "SHUFFLE_ON"
|
|
14
19
|
private const val CUSTOM_COMMAND_SHUFFLE_OFF_ACTION_ID = "SHUFFLE_OFF"
|
|
15
20
|
private const val CUSTOM_COMMAND_REPEAT_ONE_ACTION_ID = "REPEAT_ONE"
|
|
16
21
|
private const val CUSTOM_COMMAND_REPEAT_ALL_ACTION_ID = "REPEAT_ALL"
|
|
17
22
|
private const val CUSTOM_COMMAND_REPEAT_OFF_ACTION_ID = "REPEAT_OFF"
|
|
18
23
|
|
|
24
|
+
@UnstableApi
|
|
19
25
|
enum class CustomCommandButton(
|
|
20
26
|
val customAction: String,
|
|
21
27
|
val commandButton: CommandButton,
|
|
22
28
|
) {
|
|
29
|
+
PREVIOUS(
|
|
30
|
+
customAction = CUSTOM_COMMAND_PREVIOUS_ACTION_ID,
|
|
31
|
+
commandButton = CommandButton.Builder(ICON_PREVIOUS)
|
|
32
|
+
.setDisplayName("Previous")
|
|
33
|
+
.setSessionCommand(SessionCommand(CUSTOM_COMMAND_PREVIOUS_ACTION_ID, Bundle()))
|
|
34
|
+
.build(),
|
|
35
|
+
),
|
|
23
36
|
REWIND(
|
|
24
37
|
customAction = CUSTOM_COMMAND_REWIND_ACTION_ID,
|
|
25
|
-
commandButton = CommandButton.Builder(
|
|
38
|
+
commandButton = CommandButton.Builder(ICON_SKIP_BACK_15)
|
|
26
39
|
.setDisplayName("Rewind")
|
|
27
40
|
.setSessionCommand(SessionCommand(CUSTOM_COMMAND_REWIND_ACTION_ID, Bundle()))
|
|
41
|
+
.setSlots(CommandButton.SLOT_BACK, CommandButton.SLOT_OVERFLOW)
|
|
28
42
|
.build(),
|
|
29
43
|
),
|
|
30
44
|
FORWARD(
|
|
31
45
|
customAction = CUSTOM_COMMAND_FORWARD_ACTION_ID,
|
|
32
|
-
commandButton = CommandButton.Builder(
|
|
46
|
+
commandButton = CommandButton.Builder(ICON_SKIP_FORWARD_15)
|
|
33
47
|
.setDisplayName("Forward")
|
|
34
48
|
.setSessionCommand(SessionCommand(CUSTOM_COMMAND_FORWARD_ACTION_ID, Bundle()))
|
|
49
|
+
.setSlots(CommandButton.SLOT_FORWARD, CommandButton.SLOT_OVERFLOW)
|
|
50
|
+
.build(),
|
|
51
|
+
),
|
|
52
|
+
NEXT(
|
|
53
|
+
customAction = CUSTOM_COMMAND_NEXT_ACTION_ID,
|
|
54
|
+
commandButton = CommandButton.Builder(ICON_NEXT)
|
|
55
|
+
.setDisplayName("Next")
|
|
56
|
+
.setSessionCommand(SessionCommand(CUSTOM_COMMAND_NEXT_ACTION_ID, Bundle()))
|
|
35
57
|
.build(),
|
|
36
58
|
),
|
|
37
59
|
SHUFFLE_ON(
|
|
@@ -238,6 +238,12 @@ class MediaControlsPlayer(
|
|
|
238
238
|
|
|
239
239
|
fun getAvailableCustomCommands(): Set<CommandButton> {
|
|
240
240
|
return mutableSetOf<CommandButton>().apply {
|
|
241
|
+
if (isControlEnabled(Controls.PREVIOUS)) add(CustomCommandButton.PREVIOUS.commandButton)
|
|
242
|
+
if (isControlEnabled(Controls.NEXT)) add(CustomCommandButton.NEXT.commandButton)
|
|
243
|
+
|
|
244
|
+
if (isControlEnabled(Controls.SEEK_BACKWARD)) add(CustomCommandButton.REWIND.commandButton)
|
|
245
|
+
if (isControlEnabled(Controls.SEEK_FORWARD)) add(CustomCommandButton.FORWARD.commandButton)
|
|
246
|
+
|
|
241
247
|
if(isControlEnabled(Controls.SHUFFLE)) {
|
|
242
248
|
if (state.shuffleModeEnabled) {
|
|
243
249
|
add(CustomCommandButton.SHUFFLE_ON.commandButton)
|
|
@@ -252,8 +258,6 @@ class MediaControlsPlayer(
|
|
|
252
258
|
REPEAT_MODE_ALL -> add(CustomCommandButton.REPEAT_ALL.commandButton)
|
|
253
259
|
}
|
|
254
260
|
}
|
|
255
|
-
if (isControlEnabled(Controls.SEEK_BACKWARD)) add(CustomCommandButton.REWIND.commandButton)
|
|
256
|
-
if (isControlEnabled(Controls.SEEK_FORWARD)) add(CustomCommandButton.FORWARD.commandButton)
|
|
257
261
|
}
|
|
258
262
|
}
|
|
259
263
|
|
|
@@ -189,6 +189,10 @@ class MediaControlsService : MediaSessionService() {
|
|
|
189
189
|
}
|
|
190
190
|
Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
|
|
191
191
|
}
|
|
192
|
+
CustomCommandButton.PREVIOUS.customAction -> {
|
|
193
|
+
player?.seekToPreviousMediaItem()
|
|
194
|
+
Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
|
|
195
|
+
}
|
|
192
196
|
CustomCommandButton.FORWARD.customAction -> {
|
|
193
197
|
player?.seekForward()
|
|
194
198
|
Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
|
|
@@ -197,6 +201,10 @@ class MediaControlsService : MediaSessionService() {
|
|
|
197
201
|
player?.seekBack()
|
|
198
202
|
Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
|
|
199
203
|
}
|
|
204
|
+
CustomCommandButton.NEXT.customAction -> {
|
|
205
|
+
player?.seekToNextMediaItem()
|
|
206
|
+
Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
|
|
207
|
+
}
|
|
200
208
|
CustomCommandButton.SHUFFLE_ON.customAction, CustomCommandButton.SHUFFLE_OFF.customAction -> {
|
|
201
209
|
player?.emitShuffleClicked()
|
|
202
210
|
Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
|
package/ios/MediaControls.mm
CHANGED
|
@@ -165,6 +165,41 @@ RCT_EXPORT_METHOD(stopMediaNotification:(RCTPromiseResolveBlock)resolve
|
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
+
RCT_EXPORT_METHOD(shutdown) {
|
|
169
|
+
[MPNowPlayingInfoCenter defaultCenter].nowPlayingInfo = nil;
|
|
170
|
+
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVAudioSessionInterruptionNotification object:nil];
|
|
171
|
+
_audioInterruptionEnabled = false;
|
|
172
|
+
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVAudioSessionRouteChangeNotification object:nil];
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
MPRemoteCommandCenter *remoteCenter = [MPRemoteCommandCenter sharedCommandCenter];
|
|
176
|
+
|
|
177
|
+
[remoteCenter.playCommand removeTarget:self action:@selector(handlePlayCommand:)];
|
|
178
|
+
remoteCenter.playCommand.enabled = false;
|
|
179
|
+
|
|
180
|
+
[remoteCenter.pauseCommand removeTarget:self action:@selector(handlePauseCommand:)];
|
|
181
|
+
remoteCenter.pauseCommand.enabled = false;
|
|
182
|
+
|
|
183
|
+
[remoteCenter.stopCommand removeTarget:self action:@selector(handleStopCommand:)];
|
|
184
|
+
remoteCenter.stopCommand.enabled = false;
|
|
185
|
+
|
|
186
|
+
[remoteCenter.nextTrackCommand removeTarget:self action:@selector(handleNextTrackCommand:)];
|
|
187
|
+
remoteCenter.nextTrackCommand.enabled = false;
|
|
188
|
+
|
|
189
|
+
[remoteCenter.previousTrackCommand removeTarget:self action:@selector(handlePreviousTrackCommand:)];
|
|
190
|
+
remoteCenter.previousTrackCommand.enabled = false;
|
|
191
|
+
|
|
192
|
+
[remoteCenter.seekForwardCommand removeTarget:self action:@selector(handleSeekForwardCommand:)];
|
|
193
|
+
remoteCenter.seekForwardCommand.enabled = false;
|
|
194
|
+
|
|
195
|
+
[remoteCenter.seekBackwardCommand removeTarget:self action:@selector(handleSeekBackwardCommand:)];
|
|
196
|
+
remoteCenter.seekBackwardCommand.enabled = false;
|
|
197
|
+
|
|
198
|
+
[remoteCenter.changePlaybackPositionCommand removeTarget:self action:@selector(handleChangePlaybackPositionCommand:)];
|
|
199
|
+
remoteCenter.changePlaybackPositionCommand.enabled = false;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
|
|
168
203
|
RCT_EXPORT_METHOD(enableAudioInterruption:(BOOL)enabled
|
|
169
204
|
resolve:(RCTPromiseResolveBlock)resolve
|
|
170
205
|
reject:(RCTPromiseRejectBlock)reject) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["TurboModuleRegistry","ALL_MEDIA_EVENTS","getEnforcing"],"sourceRoot":"..\\..\\src","sources":["NativeMediaControls.ts"],"mappings":";;AACA,SAASA,mBAAmB,QAAQ,cAAc;AAGlD;AACA,OAAO,MAAMC,gBAAgB,GAAG,CAC9B,MAAM,EACN,OAAO,EACP,MAAM,EACN,YAAY,EACZ,gBAAgB,EAChB,aAAa,EACb,cAAc,EACd,MAAM,EACN,SAAS,EACT,YAAY,CACJ;
|
|
1
|
+
{"version":3,"names":["TurboModuleRegistry","ALL_MEDIA_EVENTS","getEnforcing"],"sourceRoot":"..\\..\\src","sources":["NativeMediaControls.ts"],"mappings":";;AACA,SAASA,mBAAmB,QAAQ,cAAc;AAGlD;AACA,OAAO,MAAMC,gBAAgB,GAAG,CAC9B,MAAM,EACN,OAAO,EACP,MAAM,EACN,YAAY,EACZ,gBAAgB,EAChB,aAAa,EACb,cAAc,EACd,MAAM,EACN,SAAS,EACT,YAAY,CACJ;AAuCV,eAAeD,mBAAmB,CAACE,YAAY,CAAO,eAAe,CAAC","ignoreList":[]}
|
package/lib/module/index.js
CHANGED
|
@@ -17,30 +17,30 @@ const setUpNativeEventListener = () => {
|
|
|
17
17
|
});
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
-
/**
|
|
21
|
-
* Updates the metadata for the current media track.
|
|
20
|
+
/**
|
|
21
|
+
* Updates the metadata for the current media track.
|
|
22
22
|
*/
|
|
23
23
|
export async function updateMetadata(metadata) {
|
|
24
24
|
return MediaControls.updateMetadata(metadata);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
/**
|
|
28
|
-
* Stops the media notification and clears any ongoing playback state.
|
|
27
|
+
/**
|
|
28
|
+
* Stops the media notification and clears any ongoing playback state.
|
|
29
29
|
*/
|
|
30
30
|
export async function stopMediaNotification() {
|
|
31
31
|
return MediaControls.stopMediaNotification();
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
/**
|
|
35
|
-
* Activates or deactivates audio interruption handling.
|
|
34
|
+
/**
|
|
35
|
+
* Activates or deactivates audio interruption handling.
|
|
36
36
|
*/
|
|
37
37
|
export async function enableAudioInterruption(enabled) {
|
|
38
38
|
return MediaControls.enableAudioInterruption(enabled);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
/**
|
|
42
|
-
* Activates the audio session for media playback.
|
|
43
|
-
* Call this method before starting media playback to ensure proper Control Center integration.
|
|
41
|
+
/**
|
|
42
|
+
* Activates the audio session for media playback.
|
|
43
|
+
* Call this method before starting media playback to ensure proper Control Center integration.
|
|
44
44
|
*/
|
|
45
45
|
export function enableBackgroundMode(enabled) {
|
|
46
46
|
MediaControls.enableBackgroundMode(enabled);
|
|
@@ -51,17 +51,20 @@ export function setControlEnabled(name, enabled) {
|
|
|
51
51
|
}
|
|
52
52
|
MediaControls.setControlEnabled(name, enabled);
|
|
53
53
|
}
|
|
54
|
+
export function shutdown() {
|
|
55
|
+
MediaControls.shutdown();
|
|
56
|
+
}
|
|
54
57
|
|
|
55
|
-
/**
|
|
56
|
-
* Register an event listener for media control events.
|
|
58
|
+
/**
|
|
59
|
+
* Register an event listener for media control events.
|
|
57
60
|
*/
|
|
58
61
|
export function addEventListener(event, handler) {
|
|
59
62
|
setUpNativeEventListener();
|
|
60
63
|
return eventEmitter.addListener(event, handler);
|
|
61
64
|
}
|
|
62
65
|
|
|
63
|
-
/**
|
|
64
|
-
* Remove a specific event listener for media control events.
|
|
66
|
+
/**
|
|
67
|
+
* Remove a specific event listener for media control events.
|
|
65
68
|
*/
|
|
66
69
|
export function removeAllListeners(event) {
|
|
67
70
|
if (event) {
|
package/lib/module/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["MediaControls","ALL_MEDIA_EVENTS","EventEmitter","eventEmitter","unsubscribe","setUpNativeEventListener","onEvent","event","command","seekPosition","emit","position","updateMetadata","metadata","stopMediaNotification","enableAudioInterruption","enabled","enableBackgroundMode","setControlEnabled","name","includes","Error","addEventListener","handler","addListener","removeAllListeners","forEach","e"],"sourceRoot":"..\\..\\src","sources":["index.tsx"],"mappings":";;AAAA,OAAOA,aAAa,IAClBC,gBAAgB,QAGX,0BAAuB;AAE9B,SAASC,YAAY,QAA2B,WAAW;AAO3D,MAAMC,YAAY,GAAG,IAAID,YAAY,CAAC,CAAC;AACvC,IAAIE,WAA2C,GAAG,IAAI;AAEtD,MAAMC,wBAAwB,GAAGA,CAAA,KAAM;EACrC,IAAID,WAAW,EAAE;EAEjBA,WAAW,GAAGJ,aAAa,CAACM,OAAO,CAAEC,KAAK,IAAK;IAC7C,MAAM;MAAEC,OAAO;MAAEC;IAAa,CAAC,GAAGF,KAAK;IACvCJ,YAAY,CAACO,IAAI,CAACF,OAAO,EAAE;MAAEG,QAAQ,EAAEF;IAAa,CAAC,CAAC;EACxD,CAAC,CAAC;AACJ,CAAC;;AAED;AACA;AACA;AACA,OAAO,eAAeG,cAAcA,CAClCC,QAA4B,EACb;EACf,OAAOb,aAAa,CAACY,cAAc,CAACC,QAAQ,CAAC;AAC/C;;AAEA;AACA;AACA;AACA,OAAO,eAAeC,qBAAqBA,CAAA,EAAkB;EAC3D,OAAOd,aAAa,CAACc,qBAAqB,CAAC,CAAC;AAC9C;;AAEA;AACA;AACA;AACA,OAAO,eAAeC,uBAAuBA,CAACC,OAAgB,EAAiB;EAC7E,OAAOhB,aAAa,CAACe,uBAAuB,CAACC,OAAO,CAAC;AACvD;;AAEA;AACA;AACA;AACA;AACA,OAAO,SAASC,oBAAoBA,CAACD,OAAgB,EAAQ;EAC3DhB,aAAa,CAACiB,oBAAoB,CAACD,OAAO,CAAC;AAC7C;AAEA,OAAO,SAASE,iBAAiBA,CAACC,IAAkB,EAAEH,OAAgB,EAAQ;EAC5E,IAAI,CAACf,gBAAgB,CAACmB,QAAQ,CAACD,IAAI,CAAC,EAAE;IACpC,MAAM,IAAIE,KAAK,CAAC,gCAAgCF,IAAI,EAAE,CAAC;EACzD;EACAnB,aAAa,CAACkB,iBAAiB,CAACC,IAAI,EAAEH,OAAO,CAAC;AAChD;;AAEA;AACA;AACA;AACA,OAAO,
|
|
1
|
+
{"version":3,"names":["MediaControls","ALL_MEDIA_EVENTS","EventEmitter","eventEmitter","unsubscribe","setUpNativeEventListener","onEvent","event","command","seekPosition","emit","position","updateMetadata","metadata","stopMediaNotification","enableAudioInterruption","enabled","enableBackgroundMode","setControlEnabled","name","includes","Error","shutdown","addEventListener","handler","addListener","removeAllListeners","forEach","e"],"sourceRoot":"..\\..\\src","sources":["index.tsx"],"mappings":";;AAAA,OAAOA,aAAa,IAClBC,gBAAgB,QAGX,0BAAuB;AAE9B,SAASC,YAAY,QAA2B,WAAW;AAO3D,MAAMC,YAAY,GAAG,IAAID,YAAY,CAAC,CAAC;AACvC,IAAIE,WAA2C,GAAG,IAAI;AAEtD,MAAMC,wBAAwB,GAAGA,CAAA,KAAM;EACrC,IAAID,WAAW,EAAE;EAEjBA,WAAW,GAAGJ,aAAa,CAACM,OAAO,CAAEC,KAAK,IAAK;IAC7C,MAAM;MAAEC,OAAO;MAAEC;IAAa,CAAC,GAAGF,KAAK;IACvCJ,YAAY,CAACO,IAAI,CAACF,OAAO,EAAE;MAAEG,QAAQ,EAAEF;IAAa,CAAC,CAAC;EACxD,CAAC,CAAC;AACJ,CAAC;;AAED;AACA;AACA;AACA,OAAO,eAAeG,cAAcA,CAClCC,QAA4B,EACb;EACf,OAAOb,aAAa,CAACY,cAAc,CAACC,QAAQ,CAAC;AAC/C;;AAEA;AACA;AACA;AACA,OAAO,eAAeC,qBAAqBA,CAAA,EAAkB;EAC3D,OAAOd,aAAa,CAACc,qBAAqB,CAAC,CAAC;AAC9C;;AAEA;AACA;AACA;AACA,OAAO,eAAeC,uBAAuBA,CAACC,OAAgB,EAAiB;EAC7E,OAAOhB,aAAa,CAACe,uBAAuB,CAACC,OAAO,CAAC;AACvD;;AAEA;AACA;AACA;AACA;AACA,OAAO,SAASC,oBAAoBA,CAACD,OAAgB,EAAQ;EAC3DhB,aAAa,CAACiB,oBAAoB,CAACD,OAAO,CAAC;AAC7C;AAEA,OAAO,SAASE,iBAAiBA,CAACC,IAAkB,EAAEH,OAAgB,EAAQ;EAC5E,IAAI,CAACf,gBAAgB,CAACmB,QAAQ,CAACD,IAAI,CAAC,EAAE;IACpC,MAAM,IAAIE,KAAK,CAAC,gCAAgCF,IAAI,EAAE,CAAC;EACzD;EACAnB,aAAa,CAACkB,iBAAiB,CAACC,IAAI,EAAEH,OAAO,CAAC;AAChD;AAEA,OAAO,SAASM,QAAQA,CAAA,EAAS;EAC/BtB,aAAa,CAACsB,QAAQ,CAAC,CAAC;AAC1B;;AAEA;AACA;AACA;AACA,OAAO,SAASC,gBAAgBA,CAC9BhB,KAAwB,EACxBiB,OAA+C,EAC5B;EACnBnB,wBAAwB,CAAC,CAAC;EAC1B,OAAOF,YAAY,CAACsB,WAAW,CAAClB,KAAK,EAAEiB,OAAO,CAAC;AACjD;;AAEA;AACA;AACA;AACA,OAAO,SAASE,kBAAkBA,CAACnB,KAAyB,EAAQ;EAClE,IAAIA,KAAK,EAAE;IACTJ,YAAY,CAACuB,kBAAkB,CAACnB,KAAK,CAAC;EACxC,CAAC,MAAM;IACLN,gBAAgB,CAAC0B,OAAO,CAAEC,CAAC,IAAKzB,YAAY,CAACuB,kBAAkB,CAACE,CAAC,CAAC,CAAC;EACrE;AACF;;AAEA","ignoreList":[]}
|
|
@@ -24,6 +24,7 @@ export interface Spec extends TurboModule {
|
|
|
24
24
|
stopMediaNotification(): Promise<void>;
|
|
25
25
|
enableAudioInterruption(enabled: boolean): Promise<void>;
|
|
26
26
|
enableBackgroundMode(enabled: boolean): void;
|
|
27
|
+
shutdown(): void;
|
|
27
28
|
readonly onEvent: EventEmitter<NativeEvent>;
|
|
28
29
|
}
|
|
29
30
|
declare const _default: Spec;
|
|
@@ -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,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,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,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,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;
|
|
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,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,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,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,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"}
|
|
@@ -22,6 +22,7 @@ export declare function enableAudioInterruption(enabled: boolean): Promise<void>
|
|
|
22
22
|
*/
|
|
23
23
|
export declare function enableBackgroundMode(enabled: boolean): void;
|
|
24
24
|
export declare function setControlEnabled(name: MediaControl, enabled: boolean): void;
|
|
25
|
+
export declare function shutdown(): void;
|
|
25
26
|
/**
|
|
26
27
|
* Register an event listener for media control events.
|
|
27
28
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAsB,EAEpB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACvB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAgB,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAG5D,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAcF;;GAEG;AACH,wBAAsB,cAAc,CAClC,QAAQ,EAAE,kBAAkB,GAC3B,OAAO,CAAC,IAAI,CAAC,CAEf;AAED;;GAEG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC,CAE3D;AAED;;GAEG;AACH,wBAAsB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAE7E;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAE3D;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAK5E;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,iBAAiB,EACxB,OAAO,EAAE,CAAC,IAAI,CAAC,EAAE,qBAAqB,KAAK,IAAI,GAC9C,iBAAiB,CAGnB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAMlE;AAGD,YAAY,EAAE,kBAAkB,EAAE,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAsB,EAEpB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACvB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAgB,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAG5D,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAcF;;GAEG;AACH,wBAAsB,cAAc,CAClC,QAAQ,EAAE,kBAAkB,GAC3B,OAAO,CAAC,IAAI,CAAC,CAEf;AAED;;GAEG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC,CAE3D;AAED;;GAEG;AACH,wBAAsB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAE7E;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAE3D;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAK5E;AAED,wBAAgB,QAAQ,IAAI,IAAI,CAE/B;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,iBAAiB,EACxB,OAAO,EAAE,CAAC,IAAI,CAAC,EAAE,qBAAqB,KAAK,IAAI,GAC9C,iBAAiB,CAGnB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAMlE;AAGD,YAAY,EAAE,kBAAkB,EAAE,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-media-notification",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
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,54 +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 MediaTrackMetadata {
|
|
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: MediaTrackMetadata): 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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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 MediaTrackMetadata {
|
|
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: MediaTrackMetadata): 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');
|
package/src/index.tsx
CHANGED
|
@@ -1,87 +1,91 @@
|
|
|
1
|
-
import MediaControls, {
|
|
2
|
-
ALL_MEDIA_EVENTS,
|
|
3
|
-
type MediaControl,
|
|
4
|
-
type MediaControlEvent,
|
|
5
|
-
} from './NativeMediaControls';
|
|
6
|
-
import type { MediaTrackMetadata } from './NativeMediaControls';
|
|
7
|
-
import { EventEmitter, EventSubscription } from 'fbemitter';
|
|
8
|
-
import { type EventSubscription as NativeEventSubscription } from 'react-native';
|
|
9
|
-
|
|
10
|
-
export type MediaControlEventData = {
|
|
11
|
-
position?: number; // für seek events
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
const eventEmitter = new EventEmitter();
|
|
15
|
-
let unsubscribe: NativeEventSubscription | null = null;
|
|
16
|
-
|
|
17
|
-
const setUpNativeEventListener = () => {
|
|
18
|
-
if (unsubscribe) return;
|
|
19
|
-
|
|
20
|
-
unsubscribe = MediaControls.onEvent((event) => {
|
|
21
|
-
const { command, seekPosition } = event;
|
|
22
|
-
eventEmitter.emit(command, { position: seekPosition });
|
|
23
|
-
});
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Updates the metadata for the current media track.
|
|
28
|
-
*/
|
|
29
|
-
export async function updateMetadata(
|
|
30
|
-
metadata: MediaTrackMetadata
|
|
31
|
-
): Promise<void> {
|
|
32
|
-
return MediaControls.updateMetadata(metadata);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Stops the media notification and clears any ongoing playback state.
|
|
37
|
-
*/
|
|
38
|
-
export async function stopMediaNotification(): Promise<void> {
|
|
39
|
-
return MediaControls.stopMediaNotification();
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Activates or deactivates audio interruption handling.
|
|
44
|
-
*/
|
|
45
|
-
export async function enableAudioInterruption(enabled: boolean): Promise<void> {
|
|
46
|
-
return MediaControls.enableAudioInterruption(enabled);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Activates the audio session for media playback.
|
|
51
|
-
* Call this method before starting media playback to ensure proper Control Center integration.
|
|
52
|
-
*/
|
|
53
|
-
export function enableBackgroundMode(enabled: boolean): void {
|
|
54
|
-
MediaControls.enableBackgroundMode(enabled);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export function setControlEnabled(name: MediaControl, enabled: boolean): void {
|
|
58
|
-
if (!ALL_MEDIA_EVENTS.includes(name)) {
|
|
59
|
-
throw new Error(`Unknown media control event: ${name}`);
|
|
60
|
-
}
|
|
61
|
-
MediaControls.setControlEnabled(name, enabled);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
1
|
+
import MediaControls, {
|
|
2
|
+
ALL_MEDIA_EVENTS,
|
|
3
|
+
type MediaControl,
|
|
4
|
+
type MediaControlEvent,
|
|
5
|
+
} from './NativeMediaControls';
|
|
6
|
+
import type { MediaTrackMetadata } from './NativeMediaControls';
|
|
7
|
+
import { EventEmitter, EventSubscription } from 'fbemitter';
|
|
8
|
+
import { type EventSubscription as NativeEventSubscription } from 'react-native';
|
|
9
|
+
|
|
10
|
+
export type MediaControlEventData = {
|
|
11
|
+
position?: number; // für seek events
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const eventEmitter = new EventEmitter();
|
|
15
|
+
let unsubscribe: NativeEventSubscription | null = null;
|
|
16
|
+
|
|
17
|
+
const setUpNativeEventListener = () => {
|
|
18
|
+
if (unsubscribe) return;
|
|
19
|
+
|
|
20
|
+
unsubscribe = MediaControls.onEvent((event) => {
|
|
21
|
+
const { command, seekPosition } = event;
|
|
22
|
+
eventEmitter.emit(command, { position: seekPosition });
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Updates the metadata for the current media track.
|
|
28
|
+
*/
|
|
29
|
+
export async function updateMetadata(
|
|
30
|
+
metadata: MediaTrackMetadata
|
|
31
|
+
): Promise<void> {
|
|
32
|
+
return MediaControls.updateMetadata(metadata);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Stops the media notification and clears any ongoing playback state.
|
|
37
|
+
*/
|
|
38
|
+
export async function stopMediaNotification(): Promise<void> {
|
|
39
|
+
return MediaControls.stopMediaNotification();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Activates or deactivates audio interruption handling.
|
|
44
|
+
*/
|
|
45
|
+
export async function enableAudioInterruption(enabled: boolean): Promise<void> {
|
|
46
|
+
return MediaControls.enableAudioInterruption(enabled);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Activates the audio session for media playback.
|
|
51
|
+
* Call this method before starting media playback to ensure proper Control Center integration.
|
|
52
|
+
*/
|
|
53
|
+
export function enableBackgroundMode(enabled: boolean): void {
|
|
54
|
+
MediaControls.enableBackgroundMode(enabled);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function setControlEnabled(name: MediaControl, enabled: boolean): void {
|
|
58
|
+
if (!ALL_MEDIA_EVENTS.includes(name)) {
|
|
59
|
+
throw new Error(`Unknown media control event: ${name}`);
|
|
60
|
+
}
|
|
61
|
+
MediaControls.setControlEnabled(name, enabled);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function shutdown(): void {
|
|
65
|
+
MediaControls.shutdown();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Register an event listener for media control events.
|
|
70
|
+
*/
|
|
71
|
+
export function addEventListener(
|
|
72
|
+
event: MediaControlEvent,
|
|
73
|
+
handler: (data?: MediaControlEventData) => void
|
|
74
|
+
): EventSubscription {
|
|
75
|
+
setUpNativeEventListener();
|
|
76
|
+
return eventEmitter.addListener(event, handler);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Remove a specific event listener for media control events.
|
|
81
|
+
*/
|
|
82
|
+
export function removeAllListeners(event?: MediaControlEvent): void {
|
|
83
|
+
if (event) {
|
|
84
|
+
eventEmitter.removeAllListeners(event);
|
|
85
|
+
} else {
|
|
86
|
+
ALL_MEDIA_EVENTS.forEach((e) => eventEmitter.removeAllListeners(e));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Export types
|
|
91
|
+
export type { MediaTrackMetadata };
|