react-native-audio-browser 0.2.0-next.27 → 0.2.0-next.29
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/audiobrowser/ServiceBinding.kt +8 -4
- package/android/src/main/java/com/audiobrowser/browser/BrowserUrlResolution.kt +3 -4
- package/android/src/test/java/com/audiobrowser/ServiceBindingTest.kt +13 -0
- package/android/src/test/java/com/audiobrowser/browser/BrowserUrlResolutionTest.kt +2 -2
- package/ios/AVPlayerEngine.swift +334 -0
- package/ios/AVPlayerEngineCallbacks.swift +15 -0
- package/ios/Browser/BrowseGate.swift +58 -0
- package/ios/Browser/BrowserConfig.swift +31 -89
- package/ios/Browser/BrowserManager.swift +68 -487
- package/ios/Browser/NavigationErrorState.swift +41 -0
- package/ios/Browser/PageResolver.swift +227 -0
- package/ios/Browser/RequestLayer.swift +90 -0
- package/ios/Browser/{BrowserManager+URLResolution.swift → RequestPipeline.swift} +175 -107
- package/ios/CarPlay/CPInterfaceController+Safe.swift +11 -24
- package/ios/CarPlay/CarPlayArtworkResolver.swift +35 -83
- package/ios/CarPlay/CarPlayController.swift +42 -1442
- package/ios/CarPlay/CarPlayImageLoader.swift +42 -83
- package/ios/CarPlay/CarPlayListItemFactory.swift +9 -23
- package/ios/CarPlay/CarPlayNowPlayingManager.swift +119 -129
- package/ios/CarPlay/CarPlaySession+Navigator.swift +114 -0
- package/ios/CarPlay/CarPlaySession+PageLoader.swift +92 -0
- package/ios/CarPlay/CarPlaySession+Refresher.swift +43 -0
- package/ios/CarPlay/CarPlaySession+TabBar.swift +193 -0
- package/ios/CarPlay/CarPlaySession.swift +209 -0
- package/ios/CarPlay/CarPlayTemplates.swift +63 -0
- package/ios/CarPlay/HybridAudioBrowser+IntentFavorite.swift +20 -0
- package/ios/CarPlay/MediaIntentPlayback.swift +74 -0
- package/ios/CarPlay/RNABMediaAddHandler.swift +2 -18
- package/ios/CarPlay/RNABMediaAffinityHandler.swift +5 -21
- package/ios/CarPlay/RNABMediaIntentHandler.swift +2 -3
- package/ios/Extension/NavigationError+Conversion.swift +7 -4
- package/ios/Extension/UIImage+Adaptive.swift +12 -0
- package/ios/Extension/URLError+Connectivity.swift +15 -0
- package/ios/Http/HttpClient.swift +63 -145
- package/ios/HybridAudioBrowser+Browser.swift +153 -0
- package/ios/HybridAudioBrowser+Options.swift +144 -0
- package/ios/HybridAudioBrowser+Player.swift +397 -0
- package/ios/HybridAudioBrowser+PlayerCallbacks.swift +190 -0
- package/ios/HybridAudioBrowser+Stubs.swift +46 -0
- package/ios/HybridAudioBrowser.swift +74 -1687
- package/ios/Model/NitroTypeStubs.swift +18 -1
- package/ios/Model/PlayerUpdateOptions.swift +1 -1
- package/ios/NowPlayingInfo/NowPlayingInfoController.swift +18 -53
- package/ios/Observer/ItemObserver.swift +90 -0
- package/ios/Observer/PlayerObserver.swift +59 -0
- package/ios/Option/SessionCategories.swift +41 -92
- package/ios/Player/AudioSessionController.swift +177 -0
- package/ios/Player/DeferredSeek.swift +70 -0
- package/ios/Player/ErrorStatus.swift +27 -0
- package/ios/Player/MediaLoader.swift +24 -11
- package/ios/Player/NowPlayingUpdater.swift +157 -58
- package/ios/Player/PlaybackFailure.swift +125 -0
- package/ios/Player/PlaybackPersistence.swift +73 -0
- package/ios/Player/Player.swift +1083 -0
- package/ios/Player/{PlaybackCoordinatorCallbacks.swift → PlayerCallbacks.swift} +3 -4
- package/ios/Player/PlayerEngine.swift +89 -0
- package/ios/Player/PlayerStatusTypes.swift +2 -6
- package/ios/Player/QueueManager.swift +52 -59
- package/ios/Player/RetryLoop.swift +261 -0
- package/ios/Player/SleepTimerManager.swift +1 -1
- package/ios/RemoteCommand/RemoteCommandCallbacks.swift +2 -3
- package/ios/RemoteCommand/RemoteCommandController.swift +85 -367
- package/ios/Tests/CarPlayArtworkResolverTests.swift +60 -356
- package/ios/Tests/DeferredSeekTests.swift +70 -0
- package/ios/Tests/EmitterTests.swift +29 -0
- package/ios/Tests/GenerationMemoTests.swift +121 -0
- package/ios/Tests/LRUCacheTests.swift +4 -5
- package/ios/Tests/MediaLoaderTests.swift +12 -33
- package/ios/Tests/ObserverTests.swift +88 -0
- package/ios/Tests/PlaybackErrorTests.swift +76 -0
- package/ios/Tests/PlaybackFailureTests.swift +189 -0
- package/ios/Tests/PlaybackPersistenceTests.swift +93 -0
- package/ios/Tests/PlayerPolicyTests.swift +405 -0
- package/ios/Tests/{PlaybackCoordinatorTests.swift → PlayerTests.swift} +298 -248
- package/ios/Tests/PlayingStateTests.swift +42 -0
- package/ios/Tests/QueueManagerTests.swift +76 -107
- package/ios/Tests/ReadyValueTests.swift +73 -0
- package/ios/Tests/RemoteCommandControllerTests.swift +10 -30
- package/ios/Tests/RemoteCommandTests.swift +2 -4
- package/ios/Tests/RequestLayerTests.swift +112 -0
- package/ios/Tests/RetryLoopTests.swift +270 -0
- package/ios/Tests/Support/TestTypes.swift +111 -112
- package/ios/Tests/TrackIdTemplateTests.swift +1 -1
- package/ios/Tests/URLErrorConnectivityTests.swift +20 -0
- package/ios/TrackSelector.swift +1 -1
- package/ios/Util/ArtworkImageFetcher.swift +42 -0
- package/ios/Util/Emitter.swift +42 -0
- package/ios/Util/GenerationMemo.swift +36 -0
- package/ios/Util/LRUCache.swift +23 -162
- package/ios/Util/NetworkMonitor.swift +1 -1
- package/ios/Util/ReadyValue.swift +56 -0
- package/ios/Util/SFSymbolRenderer.swift +35 -29
- package/ios/Util/SVGProcessor.swift +0 -10
- package/lib/commonjs/web/NativeAudioBrowser.js +1 -1
- package/lib/commonjs/web/TrackPlayer/PlayingStateFactory.js +1 -1
- package/lib/module/web/NativeAudioBrowser.js +1 -1
- package/lib/module/web/TrackPlayer/PlayingStateFactory.js +1 -1
- package/lib/typescript/src/types/browser.d.ts +3 -3
- package/package.json +1 -1
- package/src/types/browser.ts +3 -3
- package/src/web/NativeAudioBrowser.ts +1 -1
- package/src/web/TrackPlayer/PlayingStateFactory.test.ts +1 -1
- package/src/web/TrackPlayer/PlayingStateFactory.ts +1 -1
- package/ios/Bridge.h +0 -8
- package/ios/Browser/MediaResolveComposer.swift +0 -114
- package/ios/NowPlayingInfo/MediaItemProperty.swift +0 -78
- package/ios/NowPlayingInfo/NowPlayingInfoKeyValue.swift +0 -6
- package/ios/NowPlayingInfo/NowPlayingInfoProperty.swift +0 -220
- package/ios/Observer/PlayerItemNotificationObserver.swift +0 -119
- package/ios/Observer/PlayerItemPropertyObserver.swift +0 -133
- package/ios/Observer/PlayerStateObserver.swift +0 -50
- package/ios/Observer/PlayerTimeObserver.swift +0 -55
- package/ios/Option/PitchAlgorithms.swift +0 -17
- package/ios/Option/TimeEventFrequency.swift +0 -18
- package/ios/Player/LoadSeekCoordinator.swift +0 -99
- package/ios/Player/MediaLoaderDelegate.swift +0 -11
- package/ios/Player/PlaybackCoordinator.swift +0 -761
- package/ios/Player/PlaybackEffectHandler.swift +0 -54
- package/ios/Player/PlaybackErrorHandler.swift +0 -175
- package/ios/Player/PlaybackStateStore.swift +0 -27
- package/ios/Player/PlayingStateManager.swift +0 -33
- package/ios/Player/RetryManager.swift +0 -384
- package/ios/Player/SeekCompletionHandler.swift +0 -5
- package/ios/Player/SleepTimerHandling.swift +0 -28
- package/ios/Support/RNTrackPlayer-Bridging-Header.h +0 -7
- package/ios/Tests/LoadSeekCoordinatorTests.swift +0 -327
- package/ios/Tests/MediaResolveTests.swift +0 -126
- package/ios/Tests/OnceValueTests.swift +0 -79
- package/ios/Tests/PlaybackErrorHandlerTests.swift +0 -549
- package/ios/Tests/PlaybackStateStoreTests.swift +0 -49
- package/ios/Tests/PlayerTimeObserverTests.swift +0 -37
- package/ios/Tests/PlayingStateManagerTests.swift +0 -57
- package/ios/Tests/RetryManagerTests.swift +0 -178
- package/ios/TrackPlayer.swift +0 -1042
- package/ios/TrackPlayerCallbacks.swift +0 -53
- package/ios/Util/MetadataAdapter.swift +0 -131
- package/ios/Util/OnceValue.swift +0 -104
- /package/ios/{Player → CarPlay}/MediaIntentCriteria.swift +0 -0
- /package/ios/{Browser → CarPlay}/SectionPresentation.swift +0 -0
|
@@ -2,8 +2,6 @@ import Foundation
|
|
|
2
2
|
import MediaPlayer
|
|
3
3
|
import os.log
|
|
4
4
|
|
|
5
|
-
typealias RemoteCommandHandler = @MainActor (MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus
|
|
6
|
-
|
|
7
5
|
extension RepeatMode {
|
|
8
6
|
/// Convert to MPRepeatType for MPRemoteCommandCenter sync
|
|
9
7
|
var mpRepeatType: MPRepeatType {
|
|
@@ -32,44 +30,25 @@ extension Bool {
|
|
|
32
30
|
}
|
|
33
31
|
}
|
|
34
32
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
This controller enables/disables remote commands and routes them to handlers. It provides default handlers
|
|
39
|
-
that invoke TrackPlayerCallbacks, but allows customization by setting the lazy handler properties.
|
|
40
|
-
|
|
41
|
-
Note: This class is @MainActor because it's owned by TrackPlayer (which is @MainActor) and
|
|
42
|
-
MPRemoteCommandCenter handlers are always called on the main thread.
|
|
43
|
-
*/
|
|
33
|
+
/// Enables the remote commands (lock screen, Control Center, CarPlay, headset) and routes
|
|
34
|
+
/// each event to `RemoteCommandCallbacks`.
|
|
44
35
|
@MainActor
|
|
45
|
-
class RemoteCommandController {
|
|
36
|
+
final class RemoteCommandController {
|
|
46
37
|
private let logger = Logger(subsystem: "com.audiobrowser", category: "RemoteCommandController")
|
|
47
38
|
private var center: MPRemoteCommandCenter
|
|
48
39
|
|
|
49
40
|
weak var callbacks: RemoteCommandCallbacks?
|
|
50
41
|
|
|
51
|
-
var commandTargetPointers: [String: Any] = [:]
|
|
52
42
|
private var enabledCommands: [RemoteCommand] = []
|
|
53
43
|
|
|
54
|
-
/// Whether
|
|
55
|
-
|
|
56
|
-
/// commands are enabled in this state rather than unconditionally.
|
|
57
|
-
private var canNext = true
|
|
58
|
-
private var canPrevious = true
|
|
44
|
+
/// Whether the queue has a next / previous track; read whenever those commands are enabled.
|
|
45
|
+
var skipAvailability: @MainActor () -> (canNext: Bool, canPrevious: Bool) = { (true, true) }
|
|
59
46
|
|
|
60
|
-
///
|
|
61
|
-
///
|
|
62
|
-
/// off; without re-applying, the CarPlay/lock-screen shuffle & repeat buttons
|
|
63
|
-
/// flash off on every switch.
|
|
47
|
+
/// The shuffle/repeat display state, kept so it survives a switch to a new command center,
|
|
48
|
+
/// which starts with both off.
|
|
64
49
|
private var shuffleEnabled = false
|
|
65
50
|
private var repeatMode: RepeatMode = .off
|
|
66
51
|
|
|
67
|
-
/**
|
|
68
|
-
Create a new RemoteCommandController.
|
|
69
|
-
|
|
70
|
-
- parameter remoteCommandCenter: The MPRemoteCommandCenter used. Default is `MPRemoteCommandCenter.shared()`
|
|
71
|
-
- parameter callbacks: The callbacks to invoke for remote command events
|
|
72
|
-
*/
|
|
73
52
|
init(
|
|
74
53
|
remoteCommandCenter: MPRemoteCommandCenter = MPRemoteCommandCenter.shared(),
|
|
75
54
|
callbacks: RemoteCommandCallbacks? = nil,
|
|
@@ -78,383 +57,122 @@ class RemoteCommandController {
|
|
|
78
57
|
self.callbacks = callbacks
|
|
79
58
|
}
|
|
80
59
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
This is used when MPNowPlayingSession is created/destroyed on iOS 16+,
|
|
84
|
-
as the session has its own command center that must be used instead of the shared one.
|
|
85
|
-
|
|
86
|
-
- parameter newCenter: The new MPRemoteCommandCenter to use
|
|
87
|
-
*/
|
|
60
|
+
/// Moves the enabled commands and their state to `newCenter`: the linked
|
|
61
|
+
/// `MPNowPlayingSession` has its own command center, which replaces the shared one.
|
|
88
62
|
func switchCommandCenter(_ newCenter: MPRemoteCommandCenter) {
|
|
89
|
-
guard newCenter !== center else {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
logger.info("Switching remote command center")
|
|
95
|
-
|
|
96
|
-
// Disable all commands on the old center
|
|
97
|
-
let commandsToReEnable = enabledCommands
|
|
98
|
-
disable(commands: commandsToReEnable)
|
|
99
|
-
|
|
100
|
-
// Switch to new center
|
|
63
|
+
guard newCenter !== center else { return }
|
|
64
|
+
let commands = enabledCommands
|
|
65
|
+
disableAll()
|
|
101
66
|
center = newCenter
|
|
102
|
-
|
|
103
|
-
// Re-enable commands on the new center
|
|
104
|
-
enable(commands: commandsToReEnable)
|
|
105
|
-
|
|
106
|
-
// Restore shuffle/repeat display state — the new center defaults them to off,
|
|
107
|
-
// so without this the shuffle & repeat buttons flash off on every switch.
|
|
67
|
+
enable(commands: commands)
|
|
108
68
|
center.changeShuffleModeCommand.currentShuffleType = shuffleEnabled.mpShuffleType
|
|
109
69
|
center.changeRepeatModeCommand.currentRepeatType = repeatMode.mpRepeatType
|
|
110
|
-
|
|
111
|
-
logger.info("Switched command center, re-enabled \(commandsToReEnable.count) commands")
|
|
70
|
+
logger.info("Switched command center, re-enabled \(commands.count) commands")
|
|
112
71
|
}
|
|
113
72
|
|
|
73
|
+
/// Enables exactly `commands`, disabling any enabled command they no longer include.
|
|
114
74
|
func enable(commands: [RemoteCommand]) {
|
|
115
|
-
|
|
116
|
-
enabled: enabledCommands, replacedBy: commands,
|
|
117
|
-
)
|
|
118
|
-
|
|
75
|
+
RemoteCommand.commandsToDisable(enabled: enabledCommands, replacedBy: commands).forEach(disable)
|
|
119
76
|
enabledCommands = commands
|
|
120
|
-
|
|
121
|
-
// is belt-and-braces: it keeps a disable from tearing down a command the
|
|
122
|
-
// enable pass just configured should the two ever overlap again.
|
|
123
|
-
disable(commands: commandsToDisable)
|
|
124
|
-
commands.forEach { self.enable(command: $0) }
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
func disable(commands: [RemoteCommand]) {
|
|
128
|
-
commands.forEach { self.disable(command: $0) }
|
|
77
|
+
commands.forEach(enable)
|
|
129
78
|
}
|
|
130
79
|
|
|
131
|
-
/// Disables all currently enabled remote commands and removes their targets.
|
|
132
80
|
func disableAll() {
|
|
133
|
-
disable
|
|
134
|
-
enabledCommands
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
Enables a remote command by setting it up with the provided handler.
|
|
139
|
-
Removes any existing target before adding the new one to prevent duplicate handlers.
|
|
140
|
-
*/
|
|
141
|
-
private func enableRemoteCommand(
|
|
142
|
-
_ command: MPRemoteCommand,
|
|
143
|
-
key: String,
|
|
144
|
-
handler: @escaping RemoteCommandHandler,
|
|
145
|
-
) {
|
|
146
|
-
command.isEnabled = true
|
|
147
|
-
command.removeTarget(commandTargetPointers[key])
|
|
148
|
-
commandTargetPointers[key] = command.addTarget(handler: handler)
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
Disables a remote command and cleans up its target from the command center.
|
|
153
|
-
*/
|
|
154
|
-
private func disableRemoteCommand(_ command: MPRemoteCommand, key: String) {
|
|
155
|
-
command.isEnabled = false
|
|
156
|
-
command.removeTarget(commandTargetPointers[key])
|
|
157
|
-
commandTargetPointers.removeValue(forKey: key)
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
private func enable(command: RemoteCommand) {
|
|
161
|
-
switch command {
|
|
162
|
-
case .play:
|
|
163
|
-
enableRemoteCommand(center.playCommand, key: command.key, handler: handlePlayCommand)
|
|
164
|
-
case .pause:
|
|
165
|
-
enableRemoteCommand(center.pauseCommand, key: command.key, handler: handlePauseCommand)
|
|
166
|
-
case .stop:
|
|
167
|
-
enableRemoteCommand(center.stopCommand, key: command.key, handler: handleStopCommand)
|
|
168
|
-
case .togglePlayPause:
|
|
169
|
-
enableRemoteCommand(
|
|
170
|
-
center.togglePlayPauseCommand,
|
|
171
|
-
key: command.key,
|
|
172
|
-
handler: handleTogglePlayPauseCommand,
|
|
173
|
-
)
|
|
174
|
-
case .next:
|
|
175
|
-
enableRemoteCommand(
|
|
176
|
-
center.nextTrackCommand,
|
|
177
|
-
key: command.key,
|
|
178
|
-
handler: handleNextTrackCommand,
|
|
179
|
-
)
|
|
180
|
-
center.nextTrackCommand.isEnabled = canNext
|
|
181
|
-
case .previous:
|
|
182
|
-
enableRemoteCommand(
|
|
183
|
-
center.previousTrackCommand,
|
|
184
|
-
key: command.key,
|
|
185
|
-
handler: handlePreviousTrackCommand,
|
|
186
|
-
)
|
|
187
|
-
center.previousTrackCommand.isEnabled = canPrevious
|
|
188
|
-
case .changePlaybackPosition:
|
|
189
|
-
enableRemoteCommand(
|
|
190
|
-
center.changePlaybackPositionCommand,
|
|
191
|
-
key: command.key,
|
|
192
|
-
handler: handleChangePlaybackPositionCommand,
|
|
193
|
-
)
|
|
194
|
-
case let .skipForward(preferredIntervals):
|
|
195
|
-
center.skipForwardCommand.preferredIntervals = preferredIntervals
|
|
196
|
-
enableRemoteCommand(
|
|
197
|
-
center.skipForwardCommand,
|
|
198
|
-
key: command.key,
|
|
199
|
-
handler: handleSkipForwardCommand,
|
|
200
|
-
)
|
|
201
|
-
case let .skipBackward(preferredIntervals):
|
|
202
|
-
center.skipBackwardCommand.preferredIntervals = preferredIntervals
|
|
203
|
-
enableRemoteCommand(
|
|
204
|
-
center.skipBackwardCommand,
|
|
205
|
-
key: command.key,
|
|
206
|
-
handler: handleSkipBackwardCommand,
|
|
207
|
-
)
|
|
208
|
-
case .changeRepeatMode:
|
|
209
|
-
enableRemoteCommand(
|
|
210
|
-
center.changeRepeatModeCommand,
|
|
211
|
-
key: command.key,
|
|
212
|
-
handler: handleChangeRepeatModeCommand,
|
|
213
|
-
)
|
|
214
|
-
case .changeShuffleMode:
|
|
215
|
-
enableRemoteCommand(
|
|
216
|
-
center.changeShuffleModeCommand,
|
|
217
|
-
key: command.key,
|
|
218
|
-
handler: handleChangeShuffleModeCommand,
|
|
219
|
-
)
|
|
220
|
-
case let .changePlaybackRate(supportedPlaybackRates):
|
|
221
|
-
center.changePlaybackRateCommand.supportedPlaybackRates = supportedPlaybackRates
|
|
222
|
-
enableRemoteCommand(
|
|
223
|
-
center.changePlaybackRateCommand,
|
|
224
|
-
key: command.key,
|
|
225
|
-
handler: handleChangePlaybackRateCommand,
|
|
226
|
-
)
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
private func disable(command: RemoteCommand) {
|
|
231
|
-
switch command {
|
|
232
|
-
case .play:
|
|
233
|
-
disableRemoteCommand(center.playCommand, key: command.key)
|
|
234
|
-
case .pause:
|
|
235
|
-
disableRemoteCommand(center.pauseCommand, key: command.key)
|
|
236
|
-
case .stop:
|
|
237
|
-
disableRemoteCommand(center.stopCommand, key: command.key)
|
|
238
|
-
case .togglePlayPause:
|
|
239
|
-
disableRemoteCommand(center.togglePlayPauseCommand, key: command.key)
|
|
240
|
-
case .next:
|
|
241
|
-
disableRemoteCommand(center.nextTrackCommand, key: command.key)
|
|
242
|
-
case .previous:
|
|
243
|
-
disableRemoteCommand(center.previousTrackCommand, key: command.key)
|
|
244
|
-
case .changePlaybackPosition:
|
|
245
|
-
disableRemoteCommand(center.changePlaybackPositionCommand, key: command.key)
|
|
246
|
-
case .skipForward:
|
|
247
|
-
disableRemoteCommand(center.skipForwardCommand, key: command.key)
|
|
248
|
-
case .skipBackward:
|
|
249
|
-
disableRemoteCommand(center.skipBackwardCommand, key: command.key)
|
|
250
|
-
case .changeRepeatMode:
|
|
251
|
-
disableRemoteCommand(center.changeRepeatModeCommand, key: command.key)
|
|
252
|
-
case .changeShuffleMode:
|
|
253
|
-
disableRemoteCommand(center.changeShuffleModeCommand, key: command.key)
|
|
254
|
-
case .changePlaybackRate:
|
|
255
|
-
disableRemoteCommand(center.changePlaybackRateCommand, key: command.key)
|
|
256
|
-
}
|
|
81
|
+
enabledCommands.forEach(disable)
|
|
82
|
+
enabledCommands = []
|
|
257
83
|
}
|
|
258
84
|
|
|
259
|
-
// MARK: - Repeat/Shuffle State Sync
|
|
260
|
-
|
|
261
|
-
/// Updates the repeat mode state shown on CarPlay and lock screen
|
|
262
85
|
func updateRepeatMode(_ mode: RepeatMode) {
|
|
263
86
|
repeatMode = mode
|
|
264
87
|
center.changeRepeatModeCommand.currentRepeatType = mode.mpRepeatType
|
|
265
88
|
}
|
|
266
89
|
|
|
267
|
-
/// Updates the shuffle mode state shown on CarPlay and lock screen
|
|
268
90
|
func updateShuffleMode(_ enabled: Bool) {
|
|
269
91
|
shuffleEnabled = enabled
|
|
270
92
|
center.changeShuffleModeCommand.currentShuffleType = enabled.mpShuffleType
|
|
271
93
|
}
|
|
272
94
|
|
|
273
|
-
/// Greys out next/previous
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
self.canNext = canNext
|
|
277
|
-
self.canPrevious = canPrevious
|
|
95
|
+
/// Greys out next/previous when `skipAvailability` says the queue has nowhere to skip.
|
|
96
|
+
func refreshSkipAvailability() {
|
|
97
|
+
let (canNext, canPrevious) = skipAvailability()
|
|
278
98
|
if enabledCommands.contains(.next) { center.nextTrackCommand.isEnabled = canNext }
|
|
279
99
|
if enabledCommands.contains(.previous) { center.previousTrackCommand.isEnabled = canPrevious }
|
|
280
100
|
}
|
|
281
101
|
|
|
282
|
-
// MARK: -
|
|
283
|
-
|
|
284
|
-
// Each default is wrapped in a `[weak self]` closure rather than assigned as a
|
|
285
|
-
// bare method reference (`= handlePlayCommandDefault`): an unapplied instance
|
|
286
|
-
// method curries to `{ [self] in self.method($0) }`, which strongly captures
|
|
287
|
-
// self. Stored back onto self, that is a permanent cycle — `enable(command:)`
|
|
288
|
-
// forces every lazy init, so the controller would never deallocate, leaking one
|
|
289
|
-
// per player instance across JS reloads and re-setups.
|
|
290
|
-
lazy var handlePlayCommand: RemoteCommandHandler = { [weak self] event in
|
|
291
|
-
self?.handlePlayCommandDefault(event: event) ?? .commandFailed
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
lazy var handlePauseCommand: RemoteCommandHandler = { [weak self] event in
|
|
295
|
-
self?.handlePauseCommandDefault(event: event) ?? .commandFailed
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
lazy var handleStopCommand: RemoteCommandHandler = { [weak self] event in
|
|
299
|
-
self?.handleStopCommandDefault(event: event) ?? .commandFailed
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
lazy var handleTogglePlayPauseCommand: RemoteCommandHandler = { [weak self] event in
|
|
303
|
-
self?.handleTogglePlayPauseCommandDefault(event: event) ?? .commandFailed
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
lazy var handleSkipForwardCommand: RemoteCommandHandler = { [weak self] event in
|
|
307
|
-
self?.handleSkipForwardCommandDefault(event: event) ?? .commandFailed
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
lazy var handleSkipBackwardCommand: RemoteCommandHandler = { [weak self] event in
|
|
311
|
-
self?.handleSkipBackwardDefault(event: event) ?? .commandFailed
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
lazy var handleChangePlaybackPositionCommand: RemoteCommandHandler = { [weak self] event in
|
|
315
|
-
self?.handleChangePlaybackPositionCommandDefault(event: event) ?? .commandFailed
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
lazy var handleNextTrackCommand: RemoteCommandHandler = { [weak self] event in
|
|
319
|
-
self?.handleNextTrackCommandDefault(event: event) ?? .commandFailed
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
lazy var handlePreviousTrackCommand: RemoteCommandHandler = { [weak self] event in
|
|
323
|
-
self?.handlePreviousTrackCommandDefault(event: event) ?? .commandFailed
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
lazy var handleChangeRepeatModeCommand: RemoteCommandHandler = { [weak self] event in
|
|
327
|
-
self?.handleChangeRepeatModeCommandDefault(event: event) ?? .commandFailed
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
lazy var handleChangeShuffleModeCommand: RemoteCommandHandler = { [weak self] event in
|
|
331
|
-
self?.handleChangeShuffleModeCommandDefault(event: event) ?? .commandFailed
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
lazy var handleChangePlaybackRateCommand: RemoteCommandHandler = { [weak self] event in
|
|
335
|
-
self?.handleChangePlaybackRateCommandDefault(event: event) ?? .commandFailed
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
private func handlePlayCommandDefault(event _: MPRemoteCommandEvent)
|
|
339
|
-
-> MPRemoteCommandHandlerStatus
|
|
340
|
-
{
|
|
341
|
-
callbacks?.remotePlay()
|
|
342
|
-
return MPRemoteCommandHandlerStatus.success
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
private func handlePauseCommandDefault(event _: MPRemoteCommandEvent)
|
|
346
|
-
-> MPRemoteCommandHandlerStatus
|
|
347
|
-
{
|
|
348
|
-
callbacks?.remotePause()
|
|
349
|
-
return MPRemoteCommandHandlerStatus.success
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
private func handleStopCommandDefault(event _: MPRemoteCommandEvent)
|
|
353
|
-
-> MPRemoteCommandHandlerStatus
|
|
354
|
-
{
|
|
355
|
-
callbacks?.remoteStop()
|
|
356
|
-
return MPRemoteCommandHandlerStatus.success
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
private func handleTogglePlayPauseCommandDefault(event _: MPRemoteCommandEvent)
|
|
360
|
-
-> MPRemoteCommandHandlerStatus
|
|
361
|
-
{
|
|
362
|
-
callbacks?.remotePlayPause()
|
|
363
|
-
return MPRemoteCommandHandlerStatus.success
|
|
364
|
-
}
|
|
102
|
+
// MARK: - Private
|
|
365
103
|
|
|
366
|
-
private func
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
104
|
+
private func mpCommand(for command: RemoteCommand) -> MPRemoteCommand {
|
|
105
|
+
switch command {
|
|
106
|
+
case .play: center.playCommand
|
|
107
|
+
case .pause: center.pauseCommand
|
|
108
|
+
case .stop: center.stopCommand
|
|
109
|
+
case .togglePlayPause: center.togglePlayPauseCommand
|
|
110
|
+
case .next: center.nextTrackCommand
|
|
111
|
+
case .previous: center.previousTrackCommand
|
|
112
|
+
case .changePlaybackPosition: center.changePlaybackPositionCommand
|
|
113
|
+
case .skipForward: center.skipForwardCommand
|
|
114
|
+
case .skipBackward: center.skipBackwardCommand
|
|
115
|
+
case .changeRepeatMode: center.changeRepeatModeCommand
|
|
116
|
+
case .changeShuffleMode: center.changeShuffleModeCommand
|
|
117
|
+
case .changePlaybackRate: center.changePlaybackRateCommand
|
|
374
118
|
}
|
|
375
|
-
return MPRemoteCommandHandlerStatus.commandFailed
|
|
376
119
|
}
|
|
377
120
|
|
|
378
|
-
private func
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
callbacks?.remoteJumpBackward(interval: Double(truncating: interval))
|
|
385
|
-
return MPRemoteCommandHandlerStatus.success
|
|
121
|
+
private func enable(_ command: RemoteCommand) {
|
|
122
|
+
switch command {
|
|
123
|
+
case let .skipForward(intervals): center.skipForwardCommand.preferredIntervals = intervals
|
|
124
|
+
case let .skipBackward(intervals): center.skipBackwardCommand.preferredIntervals = intervals
|
|
125
|
+
case let .changePlaybackRate(rates): center.changePlaybackRateCommand.supportedPlaybackRates = rates
|
|
126
|
+
default: break
|
|
386
127
|
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
-> MPRemoteCommandHandlerStatus
|
|
392
|
-
{
|
|
393
|
-
if let event = event as? MPChangePlaybackPositionCommandEvent {
|
|
394
|
-
logger.debug("changePlaybackPosition: \(event.positionTime)")
|
|
395
|
-
callbacks?.remoteSeek(position: event.positionTime)
|
|
396
|
-
return MPRemoteCommandHandlerStatus.success
|
|
128
|
+
let mpCommand = mpCommand(for: command)
|
|
129
|
+
mpCommand.removeTarget(nil)
|
|
130
|
+
mpCommand.addTarget { [weak self] event in
|
|
131
|
+
self?.handle(event, for: command) ?? .commandFailed
|
|
397
132
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
private func handleNextTrackCommandDefault(event _: MPRemoteCommandEvent)
|
|
403
|
-
-> MPRemoteCommandHandlerStatus
|
|
404
|
-
{
|
|
405
|
-
callbacks?.remoteNext()
|
|
406
|
-
return MPRemoteCommandHandlerStatus.success
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
private func handlePreviousTrackCommandDefault(event _: MPRemoteCommandEvent)
|
|
410
|
-
-> MPRemoteCommandHandlerStatus
|
|
411
|
-
{
|
|
412
|
-
callbacks?.remotePrevious()
|
|
413
|
-
return MPRemoteCommandHandlerStatus.success
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
private func handleChangeRepeatModeCommandDefault(event: MPRemoteCommandEvent)
|
|
417
|
-
-> MPRemoteCommandHandlerStatus
|
|
418
|
-
{
|
|
419
|
-
if let event = event as? MPChangeRepeatModeCommandEvent {
|
|
420
|
-
let mode = RepeatMode(from: event.repeatType)
|
|
421
|
-
callbacks?.remoteChangeRepeatMode(mode: mode)
|
|
422
|
-
// Update the command center state to reflect the new mode
|
|
423
|
-
center.changeRepeatModeCommand.currentRepeatType = event.repeatType
|
|
424
|
-
return MPRemoteCommandHandlerStatus.success
|
|
133
|
+
mpCommand.isEnabled = switch command {
|
|
134
|
+
case .next: skipAvailability().canNext
|
|
135
|
+
case .previous: skipAvailability().canPrevious
|
|
136
|
+
default: true
|
|
425
137
|
}
|
|
426
|
-
return MPRemoteCommandHandlerStatus.commandFailed
|
|
427
138
|
}
|
|
428
139
|
|
|
429
|
-
private func
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
let enabled = event.shuffleType != .off
|
|
434
|
-
callbacks?.remoteChangeShuffleMode(enabled: enabled)
|
|
435
|
-
// Update the command center state to reflect the new mode
|
|
436
|
-
center.changeShuffleModeCommand.currentShuffleType = event.shuffleType
|
|
437
|
-
return MPRemoteCommandHandlerStatus.success
|
|
438
|
-
}
|
|
439
|
-
return MPRemoteCommandHandlerStatus.commandFailed
|
|
140
|
+
private func disable(_ command: RemoteCommand) {
|
|
141
|
+
let mpCommand = mpCommand(for: command)
|
|
142
|
+
mpCommand.isEnabled = false
|
|
143
|
+
mpCommand.removeTarget(nil)
|
|
440
144
|
}
|
|
441
145
|
|
|
442
|
-
private func
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
146
|
+
private func handle(_ event: MPRemoteCommandEvent, for command: RemoteCommand) -> MPRemoteCommandHandlerStatus {
|
|
147
|
+
switch command {
|
|
148
|
+
case .play: callbacks?.remotePlay()
|
|
149
|
+
case .pause: callbacks?.remotePause()
|
|
150
|
+
case .stop: callbacks?.remoteStop()
|
|
151
|
+
case .togglePlayPause: callbacks?.remotePlayPause()
|
|
152
|
+
case .next: callbacks?.remoteNext()
|
|
153
|
+
case .previous: callbacks?.remotePrevious()
|
|
154
|
+
case .skipForward, .skipBackward:
|
|
155
|
+
guard let interval = (event.command as? MPSkipIntervalCommand)?.preferredIntervals.first else {
|
|
156
|
+
return .commandFailed
|
|
157
|
+
}
|
|
158
|
+
if case .skipForward = command {
|
|
159
|
+
callbacks?.remoteJumpForward(interval: interval.doubleValue)
|
|
160
|
+
} else {
|
|
161
|
+
callbacks?.remoteJumpBackward(interval: interval.doubleValue)
|
|
162
|
+
}
|
|
163
|
+
case .changePlaybackPosition:
|
|
164
|
+
guard let event = event as? MPChangePlaybackPositionCommandEvent else { return .commandFailed }
|
|
165
|
+
callbacks?.remoteSeek(position: event.positionTime)
|
|
166
|
+
case .changeRepeatMode:
|
|
167
|
+
guard let event = event as? MPChangeRepeatModeCommandEvent else { return .commandFailed }
|
|
168
|
+
callbacks?.remoteChangeRepeatMode(mode: RepeatMode(from: event.repeatType))
|
|
169
|
+
case .changeShuffleMode:
|
|
170
|
+
guard let event = event as? MPChangeShuffleModeCommandEvent else { return .commandFailed }
|
|
171
|
+
callbacks?.remoteChangeShuffleMode(enabled: event.shuffleType != .off)
|
|
172
|
+
case .changePlaybackRate:
|
|
173
|
+
guard let event = event as? MPChangePlaybackRateCommandEvent else { return .commandFailed }
|
|
174
|
+
callbacks?.remoteChangePlaybackRate(rate: event.playbackRate)
|
|
449
175
|
}
|
|
450
|
-
return
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
private func getRemoteCommandHandlerStatus(forError error: Error)
|
|
454
|
-
-> MPRemoteCommandHandlerStatus
|
|
455
|
-
{
|
|
456
|
-
error is TrackPlayerError.QueueError
|
|
457
|
-
? MPRemoteCommandHandlerStatus.noSuchContent
|
|
458
|
-
: MPRemoteCommandHandlerStatus.commandFailed
|
|
176
|
+
return .success
|
|
459
177
|
}
|
|
460
178
|
}
|