@javascriptcommon/react-native-track-player 4.1.13 → 4.1.14
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/doublesymmetry/trackplayer/model/Track.kt +4 -1
- package/android/src/main/java/com/doublesymmetry/trackplayer/model/TrackAudioItem.kt +2 -1
- package/android/src/main/java/com/doublesymmetry/trackplayer/module/MusicEvents.kt +1 -0
- package/android/src/main/java/com/doublesymmetry/trackplayer/module/MusicModule.kt +13 -0
- package/android/src/main/java/com/doublesymmetry/trackplayer/service/MusicService.kt +31 -0
- package/android/src/main/java/com/lovegaoshi/kotlinaudio/player/QueuedAudioPlayer.kt +24 -0
- package/ios/RNTrackPlayer/Models/Track.swift +7 -0
- package/ios/RNTrackPlayer/RNTrackPlayer.swift +42 -0
- package/ios/RNTrackPlayer/Utils/EventType.swift +1 -0
- package/ios/SwiftAudioEx/Sources/SwiftAudioEx/Event.swift +11 -0
- package/ios/SwiftAudioEx/Sources/SwiftAudioEx/QueuedAudioPlayer.swift +27 -1
- package/lib/specs/NativeTrackPlayer.d.ts +1 -0
- package/lib/src/constants/Event.d.ts +6 -1
- package/lib/src/constants/Event.js +5 -0
- package/lib/src/hooks/useTrackPlayerEvents.d.ts +2 -3
- package/lib/src/hooks/useTrackPlayerEvents.js +0 -1
- package/lib/src/interfaces/Track.d.ts +5 -0
- package/lib/src/interfaces/events/EventPayloadByEvent.d.ts +1 -0
- package/lib/src/trackPlayer.d.ts +11 -2
- package/lib/src/trackPlayer.js +11 -0
- package/package.json +1 -1
- package/specs/NativeTrackPlayer.ts +1 -0
- package/src/constants/Event.ts +5 -0
- package/src/hooks/useTrackPlayerEvents.ts +2 -3
- package/src/interfaces/Track.ts +5 -0
- package/src/interfaces/events/EventPayloadByEvent.ts +1 -0
- package/src/trackPlayer.ts +16 -1
|
@@ -24,15 +24,17 @@ class Track
|
|
|
24
24
|
var originalItem: Bundle?
|
|
25
25
|
var headers: HashMap<String, String>? = null
|
|
26
26
|
val queueId: Long
|
|
27
|
+
var notPlayable: Boolean = false
|
|
27
28
|
|
|
28
29
|
override fun setMetadata(context: Context, bundle: Bundle?, ratingType: Int) {
|
|
29
30
|
super.setMetadata(context, bundle, ratingType)
|
|
30
31
|
if (originalItem != null && originalItem != bundle) originalItem!!.putAll(bundle)
|
|
32
|
+
bundle?.getBoolean("notPlayable", false)?.let { notPlayable = it }
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
fun toAudioItem(): TrackAudioItem {
|
|
34
36
|
return TrackAudioItem(this, type, uri.toString(), artist, title, album, artwork.toString(), duration,
|
|
35
|
-
AudioItemOptions(headers, userAgent, resourceId), mediaId)
|
|
37
|
+
AudioItemOptions(headers, userAgent, resourceId), mediaId, notPlayable)
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
init {
|
|
@@ -60,6 +62,7 @@ class Track
|
|
|
60
62
|
headers!![header] = httpHeaders.getString(header)!!
|
|
61
63
|
}
|
|
62
64
|
}
|
|
65
|
+
notPlayable = bundle.getBoolean("notPlayable", false)
|
|
63
66
|
setMetadata(context, bundle, ratingType)
|
|
64
67
|
queueId = System.currentTimeMillis()
|
|
65
68
|
originalItem = bundle
|
|
@@ -14,5 +14,6 @@ data class TrackAudioItem(
|
|
|
14
14
|
override val artwork: String? = null,
|
|
15
15
|
override val duration: Long? = null,
|
|
16
16
|
override val options: AudioItemOptions? = null,
|
|
17
|
-
override val mediaId: String? = null
|
|
17
|
+
override val mediaId: String? = null,
|
|
18
|
+
val notPlayable: Boolean = false
|
|
18
19
|
): AudioItem
|
|
@@ -48,6 +48,7 @@ class MusicEvents(private val reactContext: ReactContext) : BroadcastReceiver()
|
|
|
48
48
|
const val PLAYBACK_PROGRESS_UPDATED = "playback-progress-updated"
|
|
49
49
|
const val PLAYBACK_ERROR = "playback-error"
|
|
50
50
|
const val PLAYBACK_ANIMATED_VOLUME_CHANGED = "playback-animated-volume-changed"
|
|
51
|
+
const val PLAYBACK_NOT_PLAYABLE_TRACK_ACTIVE = "playback-not-playable-track-active"
|
|
51
52
|
const val PLAYBACK_RESUME = "playback-resume-android"
|
|
52
53
|
const val FFT_UPDATED = "fft-updated"
|
|
53
54
|
|
|
@@ -485,6 +485,19 @@ class MusicModule(reactContext: ReactApplicationContext) : NativeTrackPlayerSpec
|
|
|
485
485
|
callback.resolve(null)
|
|
486
486
|
}
|
|
487
487
|
|
|
488
|
+
override fun setTrackPlayable(trackIndex: Double, playable: Boolean, callback: Promise) = launchInScope {
|
|
489
|
+
if (verifyServiceBoundOrReject(callback)) return@launchInScope
|
|
490
|
+
|
|
491
|
+
val index = trackIndex.toInt()
|
|
492
|
+
if (index < 0 || index >= musicService.tracks.size) {
|
|
493
|
+
callback.reject("index_out_of_bounds", "The track index is out of bounds")
|
|
494
|
+
return@launchInScope
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
musicService.setTrackPlayable(index, playable)
|
|
498
|
+
callback.resolve(null)
|
|
499
|
+
}
|
|
500
|
+
|
|
488
501
|
override fun removeUpcomingTracks(callback: Promise) = launchInScope {
|
|
489
502
|
if (verifyServiceBoundOrReject(callback)) return@launchInScope
|
|
490
503
|
|
|
@@ -357,6 +357,16 @@ class MusicService : HeadlessJsMediaService() {
|
|
|
357
357
|
putDoubleArray("data", v)
|
|
358
358
|
|
|
359
359
|
})}
|
|
360
|
+
// Set up callback for notPlayable tracks
|
|
361
|
+
player.onNotPlayableTrackActive = { index, item ->
|
|
362
|
+
val bundle = Bundle().apply {
|
|
363
|
+
putInt("index", index)
|
|
364
|
+
if (item is TrackAudioItem) {
|
|
365
|
+
putBundle("track", item.track.originalItem)
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
emit(MusicEvents.PLAYBACK_NOT_PLAYABLE_TRACK_ACTIVE, bundle)
|
|
369
|
+
}
|
|
360
370
|
fakePlayer.release()
|
|
361
371
|
mediaSession.player = player.player
|
|
362
372
|
observeEvents()
|
|
@@ -713,6 +723,27 @@ class MusicService : HeadlessJsMediaService() {
|
|
|
713
723
|
updateMetadataForTrack(player.currentIndex, track)
|
|
714
724
|
}
|
|
715
725
|
|
|
726
|
+
@MainThread
|
|
727
|
+
fun setTrackPlayable(index: Int, playable: Boolean) {
|
|
728
|
+
val track = tracks.getOrNull(index) ?: return
|
|
729
|
+
val wasNotPlayable = track.notPlayable
|
|
730
|
+
track.notPlayable = !playable
|
|
731
|
+
player.replaceItem(index, track.toAudioItem())
|
|
732
|
+
|
|
733
|
+
// If current track: notPlayable -> playable, load it
|
|
734
|
+
if (wasNotPlayable && playable && player.currentIndex == index) {
|
|
735
|
+
player.load(track.toAudioItem())
|
|
736
|
+
}
|
|
737
|
+
// If current track: playable -> notPlayable, stop and emit event
|
|
738
|
+
else if (!wasNotPlayable && !playable && player.currentIndex == index) {
|
|
739
|
+
player.stop()
|
|
740
|
+
emit(MusicEvents.PLAYBACK_NOT_PLAYABLE_TRACK_ACTIVE, Bundle().apply {
|
|
741
|
+
putInt("index", index)
|
|
742
|
+
putBundle("track", track.originalItem)
|
|
743
|
+
})
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
716
747
|
@MainThread
|
|
717
748
|
fun clearNotificationMetadata() {
|
|
718
749
|
}
|
|
@@ -8,6 +8,7 @@ import androidx.media3.common.MediaItem
|
|
|
8
8
|
import androidx.media3.common.Player
|
|
9
9
|
import androidx.media3.common.util.UnstableApi
|
|
10
10
|
import com.lovegaoshi.kotlinaudio.models.*
|
|
11
|
+
import com.doublesymmetry.trackplayer.model.TrackAudioItem
|
|
11
12
|
import java.util.*
|
|
12
13
|
import kotlin.math.max
|
|
13
14
|
import kotlin.math.min
|
|
@@ -19,6 +20,12 @@ class QueuedAudioPlayer(
|
|
|
19
20
|
|
|
20
21
|
var parseEmbeddedArtwork: Boolean = false
|
|
21
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Callback invoked when a track with notPlayable=true becomes current.
|
|
25
|
+
* The player will stay on this track but won't load or play it.
|
|
26
|
+
*/
|
|
27
|
+
var onNotPlayableTrackActive: ((index: Int, item: AudioItem) -> Unit)? = null
|
|
28
|
+
|
|
22
29
|
private val queue = LinkedList<MediaItem>()
|
|
23
30
|
|
|
24
31
|
private fun parseAudioItem(audioItem: AudioItem): MediaItem {
|
|
@@ -91,11 +98,21 @@ class QueuedAudioPlayer(
|
|
|
91
98
|
get() = items.getOrNull(currentIndex - 1)
|
|
92
99
|
|
|
93
100
|
override fun load(item: AudioItem, playWhenReady: Boolean) {
|
|
101
|
+
// Check if item is notPlayable
|
|
102
|
+
if (item is TrackAudioItem && item.notPlayable) {
|
|
103
|
+
onNotPlayableTrackActive?.invoke(currentIndex, item)
|
|
104
|
+
return
|
|
105
|
+
}
|
|
94
106
|
load(item)
|
|
95
107
|
exoPlayer.playWhenReady = playWhenReady
|
|
96
108
|
}
|
|
97
109
|
|
|
98
110
|
override fun load(item: AudioItem) {
|
|
111
|
+
// Check if item is notPlayable
|
|
112
|
+
if (item is TrackAudioItem && item.notPlayable) {
|
|
113
|
+
onNotPlayableTrackActive?.invoke(currentIndex, item)
|
|
114
|
+
return
|
|
115
|
+
}
|
|
99
116
|
if (queue.isEmpty()) {
|
|
100
117
|
add(item)
|
|
101
118
|
} else {
|
|
@@ -230,6 +247,13 @@ class QueuedAudioPlayer(
|
|
|
230
247
|
*/
|
|
231
248
|
fun jumpToItem(index: Int) {
|
|
232
249
|
try {
|
|
250
|
+
// Check if the target item is notPlayable
|
|
251
|
+
val item = items.getOrNull(index)
|
|
252
|
+
if (item is TrackAudioItem && item.notPlayable) {
|
|
253
|
+
exoPlayer.seekTo(index, C.TIME_UNSET)
|
|
254
|
+
onNotPlayableTrackActive?.invoke(index, item)
|
|
255
|
+
return
|
|
256
|
+
}
|
|
233
257
|
exoPlayer.seekTo(index, C.TIME_UNSET)
|
|
234
258
|
exoPlayer.prepare()
|
|
235
259
|
} catch (e: IllegalSeekPositionException) {
|
|
@@ -29,6 +29,9 @@ class Track: AudioItem, TimePitching, AssetOptionsProviding {
|
|
|
29
29
|
var album: String?
|
|
30
30
|
var artwork: MPMediaItemArtwork?
|
|
31
31
|
|
|
32
|
+
/// When true, the track won't load or play when it becomes current.
|
|
33
|
+
var notPlayable: Bool = false
|
|
34
|
+
|
|
32
35
|
private var originalObject: [String: Any] = [:]
|
|
33
36
|
|
|
34
37
|
init?(dictionary: [String: Any]) {
|
|
@@ -37,6 +40,7 @@ class Track: AudioItem, TimePitching, AssetOptionsProviding {
|
|
|
37
40
|
self.headers = dictionary["headers"] as? [String: Any]
|
|
38
41
|
self.userAgent = dictionary["userAgent"] as? String
|
|
39
42
|
self.pitchAlgorithm = dictionary["pitchAlgorithm"] as? String
|
|
43
|
+
self.notPlayable = dictionary["notPlayable"] as? Bool ?? false
|
|
40
44
|
|
|
41
45
|
updateMetadata(dictionary: dictionary);
|
|
42
46
|
}
|
|
@@ -58,6 +62,9 @@ class Track: AudioItem, TimePitching, AssetOptionsProviding {
|
|
|
58
62
|
self.duration = dictionary["duration"] as? Double
|
|
59
63
|
self.artworkURL = MediaURL(object: dictionary["artwork"])
|
|
60
64
|
self.isLiveStream = dictionary["isLiveStream"] as? Bool
|
|
65
|
+
if let notPlayable = dictionary["notPlayable"] as? Bool {
|
|
66
|
+
self.notPlayable = notPlayable
|
|
67
|
+
}
|
|
61
68
|
|
|
62
69
|
self.originalObject = self.originalObject.merging(dictionary) { (_, new) in new }
|
|
63
70
|
}
|
|
@@ -59,6 +59,7 @@ public class RNTrackPlayer: NSObject, AudioSessionControllerDelegate {
|
|
|
59
59
|
player.event.currentItem.addListener(self, handleAudioPlayerCurrentItemChange)
|
|
60
60
|
player.event.secondElapse.addListener(self, handleAudioPlayerSecondElapse)
|
|
61
61
|
player.event.playWhenReadyChange.addListener(self, handlePlayWhenReadyChange)
|
|
62
|
+
player.event.notPlayableTrackActive.addListener(self, handleNotPlayableTrackActive)
|
|
62
63
|
|
|
63
64
|
// Store global reference to the underlying AVPlayer so that other native views can reuse it.
|
|
64
65
|
if let wrapper = player.wrapper as? AVPlayerWrapper {
|
|
@@ -898,6 +899,47 @@ public class RNTrackPlayer: NSObject, AudioSessionControllerDelegate {
|
|
|
898
899
|
)
|
|
899
900
|
}
|
|
900
901
|
|
|
902
|
+
func handleNotPlayableTrackActive(item: AudioItem?, index: Int?) {
|
|
903
|
+
var body: Dictionary<String, Any> = [:]
|
|
904
|
+
if let index = index {
|
|
905
|
+
body["index"] = index
|
|
906
|
+
}
|
|
907
|
+
if let track = (item as? Track)?.toObject() {
|
|
908
|
+
body["track"] = track
|
|
909
|
+
}
|
|
910
|
+
emit(event: EventType.PlaybackNotPlayableTrackActive, body: body)
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
// MARK: - NotPlayable Track Control
|
|
914
|
+
|
|
915
|
+
@objc(setTrackPlayable:playable:resolver:rejecter:)
|
|
916
|
+
public func setTrackPlayable(
|
|
917
|
+
trackIndex: Int,
|
|
918
|
+
playable: Bool,
|
|
919
|
+
resolve: RCTPromiseResolveBlock,
|
|
920
|
+
reject: RCTPromiseRejectBlock
|
|
921
|
+
) {
|
|
922
|
+
if (rejectWhenNotInitialized(reject: reject)) { return }
|
|
923
|
+
if (rejectWhenTrackIndexOutOfBounds(index: trackIndex, reject: reject)) { return }
|
|
924
|
+
|
|
925
|
+
let track: Track = player.items[trackIndex] as! Track
|
|
926
|
+
let wasNotPlayable = track.notPlayable
|
|
927
|
+
track.notPlayable = !playable
|
|
928
|
+
|
|
929
|
+
// If current track: notPlayable -> playable, load it
|
|
930
|
+
if wasNotPlayable && playable && player.currentIndex == trackIndex {
|
|
931
|
+
player.load(item: track)
|
|
932
|
+
}
|
|
933
|
+
// If current track: playable -> notPlayable, unload it
|
|
934
|
+
else if !wasNotPlayable && !playable && player.currentIndex == trackIndex {
|
|
935
|
+
player.wrapper.unload()
|
|
936
|
+
player.wrapper.state = .idle
|
|
937
|
+
handleNotPlayableTrackActive(item: track, index: trackIndex)
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
resolve(NSNull())
|
|
941
|
+
}
|
|
942
|
+
|
|
901
943
|
// MARK: - iOS Equalizer Methods
|
|
902
944
|
|
|
903
945
|
@objc(setEqualizerEnabled:resolver:rejecter:)
|
|
@@ -38,6 +38,7 @@ enum EventType: String, CaseIterable {
|
|
|
38
38
|
case ControllerConnected = "android-controller-connected"
|
|
39
39
|
case ControllerDisconnected = "android-controller-disconnected"
|
|
40
40
|
case PlaybackAnimatedVolumeChanged = "playback-animated-volume-changed"
|
|
41
|
+
case PlaybackNotPlayableTrackActive = "playback-not-playable-track-active"
|
|
41
42
|
|
|
42
43
|
static func allRawValues() -> [String] {
|
|
43
44
|
return allCases.map { $0.rawValue }
|
|
@@ -27,6 +27,10 @@ extension AudioPlayer {
|
|
|
27
27
|
lastIndex: Int?,
|
|
28
28
|
lastPosition: Double?
|
|
29
29
|
)
|
|
30
|
+
public typealias NotPlayableTrackActiveEventData = (
|
|
31
|
+
item: AudioItem?,
|
|
32
|
+
index: Int?
|
|
33
|
+
)
|
|
30
34
|
|
|
31
35
|
public struct EventHolder {
|
|
32
36
|
|
|
@@ -104,6 +108,13 @@ extension AudioPlayer {
|
|
|
104
108
|
- Note: It is only fired for instances of a QueuedAudioPlayer.
|
|
105
109
|
*/
|
|
106
110
|
public let currentItem: AudioPlayer.Event<CurrentItemEventData> = AudioPlayer.Event()
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
Emitted when a track with notPlayable=true becomes the active track.
|
|
114
|
+
The track stays current but won't load or play.
|
|
115
|
+
- Important: Remember to dispatch to the main queue if any UI is updated in the event handler.
|
|
116
|
+
*/
|
|
117
|
+
public let notPlayableTrackActive: AudioPlayer.Event<NotPlayableTrackActiveEventData> = AudioPlayer.Event()
|
|
107
118
|
}
|
|
108
119
|
|
|
109
120
|
public typealias EventClosure<EventData> = (EventData) -> Void
|
|
@@ -207,7 +207,20 @@ public class QueuedAudioPlayer: AudioPlayer, QueueManagerDelegate {
|
|
|
207
207
|
func onCurrentItemChanged() {
|
|
208
208
|
let lastPosition = currentTime;
|
|
209
209
|
if let currentItem = currentItem {
|
|
210
|
-
|
|
210
|
+
// Check if the track is marked as notPlayable
|
|
211
|
+
if isTrackNotPlayable(currentItem) {
|
|
212
|
+
// Don't load the item, emit notPlayableTrackActive event
|
|
213
|
+
event.notPlayableTrackActive.emit(
|
|
214
|
+
data: (
|
|
215
|
+
item: currentItem,
|
|
216
|
+
index: currentIndex == -1 ? nil : currentIndex
|
|
217
|
+
)
|
|
218
|
+
)
|
|
219
|
+
// Set player to idle state without loading
|
|
220
|
+
wrapper.state = .idle
|
|
221
|
+
} else {
|
|
222
|
+
super.load(item: currentItem)
|
|
223
|
+
}
|
|
211
224
|
} else {
|
|
212
225
|
super.clear()
|
|
213
226
|
}
|
|
@@ -224,6 +237,19 @@ public class QueuedAudioPlayer: AudioPlayer, QueueManagerDelegate {
|
|
|
224
237
|
lastIndex = currentIndex
|
|
225
238
|
}
|
|
226
239
|
|
|
240
|
+
/// Check if an AudioItem is marked as notPlayable.
|
|
241
|
+
/// This method checks if the item has a notPlayable property set to true.
|
|
242
|
+
private func isTrackNotPlayable(_ item: AudioItem) -> Bool {
|
|
243
|
+
// Use reflection to check for notPlayable property
|
|
244
|
+
let mirror = Mirror(reflecting: item)
|
|
245
|
+
for child in mirror.children {
|
|
246
|
+
if child.label == "notPlayable", let value = child.value as? Bool {
|
|
247
|
+
return value
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return false
|
|
251
|
+
}
|
|
252
|
+
|
|
227
253
|
func onSkippedToSameCurrentItem() {
|
|
228
254
|
if (wrapper.playbackActive) {
|
|
229
255
|
replay()
|
|
@@ -28,6 +28,7 @@ export interface Spec extends TurboModule {
|
|
|
28
28
|
skipToPrevious(initialPosition: number): Promise<void>;
|
|
29
29
|
updateMetadataForTrack(trackIndex: number, metadata: UnsafeObject): Promise<void>;
|
|
30
30
|
updateNowPlayingMetadata(metadata: UnsafeObject): Promise<void>;
|
|
31
|
+
setTrackPlayable(trackIndex: number, playable: boolean): Promise<void>;
|
|
31
32
|
setQueue(tracks: UnsafeObject[]): Promise<void>;
|
|
32
33
|
getQueue(): Promise<UnsafeObject[]>;
|
|
33
34
|
setRepeatMode(mode: number): Promise<number>;
|
|
@@ -168,5 +168,10 @@ export declare enum Event {
|
|
|
168
168
|
/**
|
|
169
169
|
* Fired when there is an fft update
|
|
170
170
|
**/
|
|
171
|
-
fftUpdate = "fft-updated"
|
|
171
|
+
fftUpdate = "fft-updated",
|
|
172
|
+
/**
|
|
173
|
+
* Fired when a track with notPlayable=true becomes the active track.
|
|
174
|
+
* The track stays current but won't load or play.
|
|
175
|
+
*/
|
|
176
|
+
PlaybackNotPlayableTrackActive = "playback-not-playable-track-active"
|
|
172
177
|
}
|
|
@@ -170,4 +170,9 @@ export var Event;
|
|
|
170
170
|
* Fired when there is an fft update
|
|
171
171
|
**/
|
|
172
172
|
Event["fftUpdate"] = "fft-updated";
|
|
173
|
+
/**
|
|
174
|
+
* Fired when a track with notPlayable=true becomes the active track.
|
|
175
|
+
* The track stays current but won't load or play.
|
|
176
|
+
*/
|
|
177
|
+
Event["PlaybackNotPlayableTrackActive"] = "playback-not-playable-track-active";
|
|
173
178
|
})(Event || (Event = {}));
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { EventPayloadByEventWithType } from '../interfaces';
|
|
1
|
+
import type { EventPayloadByEvent, EventPayloadByEventWithType } from '../interfaces';
|
|
3
2
|
/**
|
|
4
3
|
* Attaches a handler to the given TrackPlayer events and performs cleanup on unmount
|
|
5
4
|
* @param events - TrackPlayer events to subscribe to
|
|
6
5
|
* @param handler - callback invoked when the event fires
|
|
7
6
|
*/
|
|
8
|
-
export declare const useTrackPlayerEvents: <T extends
|
|
7
|
+
export declare const useTrackPlayerEvents: <T extends (keyof EventPayloadByEvent)[], H extends (data: EventPayloadByEventWithType[T[number]]) => void>(events: T, handler: H) => void;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { useEffect, useRef } from 'react';
|
|
2
2
|
import { addEventListener } from '../trackPlayer';
|
|
3
|
-
import { Event } from '../constants';
|
|
4
3
|
/**
|
|
5
4
|
* Attaches a handler to the given TrackPlayer events and performs cleanup on unmount
|
|
6
5
|
* @param events - TrackPlayer events to subscribe to
|
|
@@ -13,6 +13,11 @@ export interface Track extends TrackMetadataBase {
|
|
|
13
13
|
headers?: {
|
|
14
14
|
[key: string]: any;
|
|
15
15
|
};
|
|
16
|
+
/**
|
|
17
|
+
* When true, the track won't load or play when it becomes current.
|
|
18
|
+
* The track stays in the queue but playback is blocked.
|
|
19
|
+
*/
|
|
20
|
+
notPlayable?: boolean;
|
|
16
21
|
[key: string]: any;
|
|
17
22
|
}
|
|
18
23
|
export type AddTrack = Track & {
|
|
@@ -63,6 +63,7 @@ export type EventPayloadByEvent = {
|
|
|
63
63
|
[Event.connectorConnected]: ControllerConnectedEvent;
|
|
64
64
|
[Event.connectorDisconnected]: ControllerDisconnectedEvent;
|
|
65
65
|
[Event.fftUpdate]: FFTUpdateEvent;
|
|
66
|
+
[Event.PlaybackNotPlayableTrackActive]: never;
|
|
66
67
|
};
|
|
67
68
|
type Simplify<T> = {
|
|
68
69
|
[KeyType in keyof T]: T[KeyType];
|
package/lib/src/trackPlayer.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RepeatMode, AndroidAutoContentStyle } from './constants';
|
|
2
2
|
import type { AddTrack, EventPayloadByEvent, NowPlayingMetadata, PlaybackState, PlayerOptions, Progress, ServiceHandler, Track, TrackMetadataBase, UpdateOptions, AndroidAutoBrowseTree, MediaItem } from './interfaces';
|
|
3
3
|
/**
|
|
4
4
|
* Initializes the player with the specified options.
|
|
@@ -16,7 +16,7 @@ export declare function setupPlayer(options?: PlayerOptions, background?: boolea
|
|
|
16
16
|
* Register the playback service. The service will run as long as the player runs.
|
|
17
17
|
*/
|
|
18
18
|
export declare function registerPlaybackService(factory: () => ServiceHandler): void;
|
|
19
|
-
export declare function addEventListener<T extends
|
|
19
|
+
export declare function addEventListener<T extends keyof EventPayloadByEvent>(event: T, listener: EventPayloadByEvent[T] extends never ? () => void : (event: EventPayloadByEvent[T]) => void): import("react-native").EmitterSubscription;
|
|
20
20
|
/**
|
|
21
21
|
* Adds one or more tracks to the queue.
|
|
22
22
|
*
|
|
@@ -110,6 +110,15 @@ export declare function updateMetadataForTrack(trackIndex: number, metadata: Tra
|
|
|
110
110
|
* without affecting the data stored for the current track.
|
|
111
111
|
*/
|
|
112
112
|
export declare function updateNowPlayingMetadata(metadata: NowPlayingMetadata): Promise<void>;
|
|
113
|
+
/**
|
|
114
|
+
* Updates the playable status of a track in the queue.
|
|
115
|
+
* When playable is false (notPlayable is true), the track won't load or play when current.
|
|
116
|
+
* When playable is true (notPlayable is false), normal playback behavior resumes.
|
|
117
|
+
*
|
|
118
|
+
* @param trackIndex The index of the track to update.
|
|
119
|
+
* @param playable Whether the track should be playable.
|
|
120
|
+
*/
|
|
121
|
+
export declare function setTrackPlayable(trackIndex: number, playable: boolean): Promise<void>;
|
|
113
122
|
/**
|
|
114
123
|
* Resets the player stopping the current track and clearing the queue.
|
|
115
124
|
*/
|
package/lib/src/trackPlayer.js
CHANGED
|
@@ -163,6 +163,17 @@ export function updateNowPlayingMetadata(metadata) {
|
|
|
163
163
|
});
|
|
164
164
|
}
|
|
165
165
|
// MARK: - Player API
|
|
166
|
+
/**
|
|
167
|
+
* Updates the playable status of a track in the queue.
|
|
168
|
+
* When playable is false (notPlayable is true), the track won't load or play when current.
|
|
169
|
+
* When playable is true (notPlayable is false), normal playback behavior resumes.
|
|
170
|
+
*
|
|
171
|
+
* @param trackIndex The index of the track to update.
|
|
172
|
+
* @param playable Whether the track should be playable.
|
|
173
|
+
*/
|
|
174
|
+
export async function setTrackPlayable(trackIndex, playable) {
|
|
175
|
+
return TrackPlayer.setTrackPlayable(trackIndex, playable);
|
|
176
|
+
}
|
|
166
177
|
/**
|
|
167
178
|
* Resets the player stopping the current track and clearing the queue.
|
|
168
179
|
*/
|
package/package.json
CHANGED
|
@@ -41,6 +41,7 @@ export interface Spec extends TurboModule {
|
|
|
41
41
|
metadata: UnsafeObject,
|
|
42
42
|
): Promise<void>;
|
|
43
43
|
updateNowPlayingMetadata(metadata: UnsafeObject): Promise<void>;
|
|
44
|
+
setTrackPlayable(trackIndex: number, playable: boolean): Promise<void>;
|
|
44
45
|
setQueue(tracks: UnsafeObject[]): Promise<void>;
|
|
45
46
|
getQueue(): Promise<UnsafeObject[]>;
|
|
46
47
|
setRepeatMode(mode: number): Promise<number>;
|
package/src/constants/Event.ts
CHANGED
|
@@ -170,4 +170,9 @@ export enum Event {
|
|
|
170
170
|
* Fired when there is an fft update
|
|
171
171
|
**/
|
|
172
172
|
fftUpdate = 'fft-updated',
|
|
173
|
+
/**
|
|
174
|
+
* Fired when a track with notPlayable=true becomes the active track.
|
|
175
|
+
* The track stays current but won't load or play.
|
|
176
|
+
*/
|
|
177
|
+
PlaybackNotPlayableTrackActive = 'playback-not-playable-track-active',
|
|
173
178
|
}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { useEffect, useRef } from 'react';
|
|
2
2
|
|
|
3
3
|
import { addEventListener } from '../trackPlayer';
|
|
4
|
-
import {
|
|
5
|
-
import type { EventPayloadByEventWithType } from '../interfaces';
|
|
4
|
+
import type { EventPayloadByEvent, EventPayloadByEventWithType } from '../interfaces';
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
7
|
* Attaches a handler to the given TrackPlayer events and performs cleanup on unmount
|
|
@@ -10,7 +9,7 @@ import type { EventPayloadByEventWithType } from '../interfaces';
|
|
|
10
9
|
* @param handler - callback invoked when the event fires
|
|
11
10
|
*/
|
|
12
11
|
export const useTrackPlayerEvents = <
|
|
13
|
-
T extends
|
|
12
|
+
T extends (keyof EventPayloadByEvent)[],
|
|
14
13
|
H extends (data: EventPayloadByEventWithType[T[number]]) => void
|
|
15
14
|
>(
|
|
16
15
|
events: T,
|
package/src/interfaces/Track.ts
CHANGED
|
@@ -13,6 +13,11 @@ export interface Track extends TrackMetadataBase {
|
|
|
13
13
|
pitchAlgorithm?: PitchAlgorithm;
|
|
14
14
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
15
15
|
headers?: { [key: string]: any };
|
|
16
|
+
/**
|
|
17
|
+
* When true, the track won't load or play when it becomes current.
|
|
18
|
+
* The track stays in the queue but playback is blocked.
|
|
19
|
+
*/
|
|
20
|
+
notPlayable?: boolean;
|
|
16
21
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
17
22
|
[key: string]: any;
|
|
18
23
|
}
|
|
@@ -68,6 +68,7 @@ export type EventPayloadByEvent = {
|
|
|
68
68
|
[Event.connectorConnected]: ControllerConnectedEvent;
|
|
69
69
|
[Event.connectorDisconnected]: ControllerDisconnectedEvent;
|
|
70
70
|
[Event.fftUpdate]: FFTUpdateEvent;
|
|
71
|
+
[Event.PlaybackNotPlayableTrackActive]: never;
|
|
71
72
|
};
|
|
72
73
|
|
|
73
74
|
// eslint-disable-next-line
|
package/src/trackPlayer.ts
CHANGED
|
@@ -82,7 +82,7 @@ export function registerPlaybackService(factory: () => ServiceHandler) {
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
export function addEventListener<T extends
|
|
85
|
+
export function addEventListener<T extends keyof EventPayloadByEvent>(
|
|
86
86
|
event: T,
|
|
87
87
|
listener: EventPayloadByEvent[T] extends never
|
|
88
88
|
? () => void
|
|
@@ -275,6 +275,21 @@ export function updateNowPlayingMetadata(
|
|
|
275
275
|
|
|
276
276
|
// MARK: - Player API
|
|
277
277
|
|
|
278
|
+
/**
|
|
279
|
+
* Updates the playable status of a track in the queue.
|
|
280
|
+
* When playable is false (notPlayable is true), the track won't load or play when current.
|
|
281
|
+
* When playable is true (notPlayable is false), normal playback behavior resumes.
|
|
282
|
+
*
|
|
283
|
+
* @param trackIndex The index of the track to update.
|
|
284
|
+
* @param playable Whether the track should be playable.
|
|
285
|
+
*/
|
|
286
|
+
export async function setTrackPlayable(
|
|
287
|
+
trackIndex: number,
|
|
288
|
+
playable: boolean,
|
|
289
|
+
): Promise<void> {
|
|
290
|
+
return TrackPlayer.setTrackPlayable(trackIndex, playable);
|
|
291
|
+
}
|
|
292
|
+
|
|
278
293
|
/**
|
|
279
294
|
* Resets the player stopping the current track and clearing the queue.
|
|
280
295
|
*/
|