react-native-audio-browser 0.2.0-next.31 → 0.2.0-next.32
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/ios/AVPlayerEngine.swift +4 -8
- package/ios/Browser/JsonModels.swift +93 -182
- package/ios/HybridAudioBrowser+Player.swift +3 -3
- package/ios/Model/NitroTypeStubs.swift +13 -0
- package/ios/Player/AudioSessionController.swift +2 -0
- package/ios/Player/ErrorStatus.swift +25 -2
- package/ios/Player/MediaLoader.swift +23 -71
- package/ios/Player/NowPlayingLines.swift +36 -0
- package/ios/Player/NowPlayingUpdater.swift +27 -85
- package/ios/Player/Player.swift +108 -261
- package/ios/Player/RetryLoop.swift +26 -0
- package/ios/Player/SleepTimerManager.swift +13 -15
- package/ios/Player/VolumeFader.swift +5 -0
- package/ios/Tests/ErrorStatusTests.swift +50 -0
- package/ios/Tests/JsonModelsTests.swift +30 -0
- package/ios/Tests/MediaLoaderTests.swift +26 -21
- package/ios/Tests/NowPlayingLinesTests.swift +43 -0
- package/ios/Tests/PlayerPolicyTests.swift +19 -3
- package/ios/Tests/PlayerTests.swift +81 -74
- package/ios/Tests/RetryLoopTests.swift +35 -0
- package/ios/Tests/SleepTimerManagerTests.swift +65 -41
- package/ios/Tests/Support/TestTypes.swift +2 -2
- package/package.json +1 -1
|
@@ -3,16 +3,6 @@ import MediaPlayer
|
|
|
3
3
|
import NitroModules
|
|
4
4
|
import os.log
|
|
5
5
|
|
|
6
|
-
/// What the now-playing surface renders from: the player's track and state.
|
|
7
|
-
@MainActor
|
|
8
|
-
protocol NowPlayingSource: AnyObject {
|
|
9
|
-
var currentTrack: Track? { get }
|
|
10
|
-
var currentTime: Double { get }
|
|
11
|
-
var playWhenReady: Bool { get }
|
|
12
|
-
var isStalled: Bool { get }
|
|
13
|
-
var nitroPlaybackError: PlaybackError? { get }
|
|
14
|
-
}
|
|
15
|
-
|
|
16
6
|
/// Owns the now-playing surface: publishes a track's static fields + artwork, and renders the
|
|
17
7
|
/// title/artist line — a flash, else the formatter's result, else the override or the track's own
|
|
18
8
|
/// fields. Holds that state (override, flash, latest timed metadata, formatter) and reports each
|
|
@@ -86,26 +76,10 @@ final class NowPlayingUpdater {
|
|
|
86
76
|
}
|
|
87
77
|
|
|
88
78
|
/// The metadata JS sees for the current track, before any formatter result.
|
|
79
|
+
/// Applies the override whether or not `enabled` is set.
|
|
89
80
|
var nowPlaying: NowPlayingMetadata? {
|
|
90
81
|
guard let track = source?.currentTrack else { return nil }
|
|
91
|
-
|
|
92
|
-
return metadata(track: track, title: lines.title, artist: lines.artist, album: lines.album)
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/// Re-renders the text lines for the current track and state. Called on every playback
|
|
96
|
-
/// transition; unchanged lines are not re-published.
|
|
97
|
-
func render() {
|
|
98
|
-
guard let source, let track = source.currentTrack else { return }
|
|
99
|
-
render(
|
|
100
|
-
track: track,
|
|
101
|
-
timedMetadata: timedMetadata,
|
|
102
|
-
playWhenReady: source.playWhenReady,
|
|
103
|
-
stalled: source.isStalled ? (isOnline ? .buffering : .offline) : nil,
|
|
104
|
-
error: source.nitroPlaybackError,
|
|
105
|
-
flash: flash,
|
|
106
|
-
override: enabled ? override : nil,
|
|
107
|
-
formatter: enabled ? formatter : nil,
|
|
108
|
-
)
|
|
82
|
+
return metadata(track: track, lines: .resolve(track: track, flash: flash, override: override))
|
|
109
83
|
}
|
|
110
84
|
|
|
111
85
|
/// Empties the now-playing surface and drops any artwork fetch or formatter result in flight.
|
|
@@ -148,11 +122,11 @@ final class NowPlayingUpdater {
|
|
|
148
122
|
private static let maxArtworkPixelWidth: CGFloat = 1200
|
|
149
123
|
|
|
150
124
|
/// Publishes the static, per-track fields + artwork on a track change. The title/artist/album
|
|
151
|
-
/// lines are owned by `render` (routed through `
|
|
125
|
+
/// lines are owned by `render` (routed through `apply` here so it dedupes against later
|
|
152
126
|
/// renders).
|
|
153
127
|
func loadMetaValues(for track: Track) {
|
|
154
128
|
nowPlayingInfoController.set([MPNowPlayingInfoPropertyIsLiveStream: track.live])
|
|
155
|
-
|
|
129
|
+
apply(.resolve(track: track), for: track)
|
|
156
130
|
loadArtwork(for: track)
|
|
157
131
|
}
|
|
158
132
|
|
|
@@ -160,34 +134,19 @@ final class NowPlayingUpdater {
|
|
|
160
134
|
/// immediately, then — when a formatter is configured — its async result overlaid (each field
|
|
161
135
|
/// falling back to the default). A `flash` (transient line, e.g. feedback for a refused remote
|
|
162
136
|
/// command) outranks both: while one is active the formatter pass is skipped entirely, so its
|
|
163
|
-
/// async result can't land on top.
|
|
164
|
-
///
|
|
165
|
-
|
|
166
|
-
track
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
stalled: StallReason?,
|
|
170
|
-
error: PlaybackError?,
|
|
171
|
-
flash: NowPlayingUpdate?,
|
|
172
|
-
override: NowPlayingUpdate?,
|
|
173
|
-
formatter: ((_ params: FormatNowPlayingParams) -> Promise<NowPlayingUpdate?>)?,
|
|
174
|
-
) {
|
|
137
|
+
/// async result can't land on top. Called on every playback transition; unchanged lines are not
|
|
138
|
+
/// re-published.
|
|
139
|
+
func render() {
|
|
140
|
+
guard let source, let track = source.currentTrack else { return }
|
|
141
|
+
let override = enabled ? self.override : nil
|
|
142
|
+
let formatter = enabled ? self.formatter : nil
|
|
175
143
|
renderGeneration &+= 1
|
|
176
144
|
let generation = renderGeneration
|
|
177
145
|
|
|
178
|
-
let defaultTitle = override?.title ?? track.title
|
|
179
|
-
let defaultSecondary = override?.artist ?? track.artist
|
|
180
|
-
let defaultAlbum = override?.album ?? track.album
|
|
181
|
-
|
|
182
146
|
let trackId = track.identity ?? track.path
|
|
183
147
|
|
|
184
148
|
if let flash {
|
|
185
|
-
|
|
186
|
-
track: track,
|
|
187
|
-
title: flash.title ?? defaultTitle,
|
|
188
|
-
artist: flash.artist ?? defaultSecondary,
|
|
189
|
-
album: flash.album ?? defaultAlbum,
|
|
190
|
-
)
|
|
149
|
+
apply(.resolve(track: track, flash: flash, override: override), for: track)
|
|
191
150
|
// The flash replaced the formatter's line, so the surface no longer shows
|
|
192
151
|
// one: the render that follows the flash reverting must publish the default
|
|
193
152
|
// immediately rather than sit on stale flash text awaiting the formatter.
|
|
@@ -202,16 +161,16 @@ final class NowPlayingUpdater {
|
|
|
202
161
|
// until the async result lands, since the two differ by construction on a live
|
|
203
162
|
// stream (station name vs. song line).
|
|
204
163
|
if formatter == nil || formattedTrackId != trackId {
|
|
205
|
-
|
|
164
|
+
apply(.resolve(track: track, override: override), for: track)
|
|
206
165
|
}
|
|
207
166
|
|
|
208
167
|
guard let formatter else { return }
|
|
209
168
|
let params = FormatNowPlayingParams(
|
|
210
169
|
track: track,
|
|
211
170
|
timedMetadata: timedMetadata,
|
|
212
|
-
playWhenReady: playWhenReady,
|
|
213
|
-
stalled:
|
|
214
|
-
error:
|
|
171
|
+
playWhenReady: source.playWhenReady,
|
|
172
|
+
stalled: source.isStalled ? (isOnline ? .buffering : .offline) : nil,
|
|
173
|
+
error: source.nitroPlaybackError,
|
|
215
174
|
)
|
|
216
175
|
// `@Sendable` breaks the @MainActor isolation inheritance: Nitro resolves the promise on the JS
|
|
217
176
|
// thread, so the resolver must NOT be main-isolated (a main-isolated closure run off-main traps
|
|
@@ -223,12 +182,7 @@ final class NowPlayingUpdater {
|
|
|
223
182
|
Task { @MainActor in
|
|
224
183
|
// Apply only if no newer render has superseded this one (fast skip / state moved on).
|
|
225
184
|
guard self.renderGeneration == generation else { return }
|
|
226
|
-
self.
|
|
227
|
-
track: track,
|
|
228
|
-
title: formatted.title ?? defaultTitle,
|
|
229
|
-
artist: formatted.artist ?? defaultSecondary,
|
|
230
|
-
album: formatted.album ?? defaultAlbum,
|
|
231
|
-
)
|
|
185
|
+
self.apply(.resolve(track: track, formatted: formatted, override: override), for: track)
|
|
232
186
|
// A formatter result now owns the surface for this track, so later
|
|
233
187
|
// renders skip the interim default write.
|
|
234
188
|
self.formattedTrackId = trackId
|
|
@@ -244,21 +198,18 @@ final class NowPlayingUpdater {
|
|
|
244
198
|
|
|
245
199
|
// MARK: - Private
|
|
246
200
|
|
|
247
|
-
/// Stamps the
|
|
248
|
-
///
|
|
249
|
-
private func
|
|
250
|
-
let resolvedTitle = title ?? track.title
|
|
251
|
-
let resolvedArtist = artist ?? track.artist
|
|
252
|
-
let resolvedAlbum = album ?? track.album
|
|
201
|
+
/// Stamps the lines onto the now-playing surface, and notifies `onChanged` when the text or the
|
|
202
|
+
/// track changed.
|
|
203
|
+
private func apply(_ lines: NowPlayingLines, for track: Track) {
|
|
253
204
|
let textChanged = nowPlayingInfoController.set([
|
|
254
|
-
MPMediaItemPropertyTitle:
|
|
255
|
-
MPMediaItemPropertyArtist:
|
|
256
|
-
MPMediaItemPropertyAlbumTitle:
|
|
205
|
+
MPMediaItemPropertyTitle: lines.title,
|
|
206
|
+
MPMediaItemPropertyArtist: lines.artist,
|
|
207
|
+
MPMediaItemPropertyAlbumTitle: lines.album,
|
|
257
208
|
])
|
|
258
209
|
let trackId = track.identity ?? track.path
|
|
259
210
|
guard textChanged || trackId != publishedTrackId else { return }
|
|
260
211
|
publishedTrackId = trackId
|
|
261
|
-
onChanged?(metadata(track: track,
|
|
212
|
+
onChanged?(metadata(track: track, lines: lines))
|
|
262
213
|
}
|
|
263
214
|
|
|
264
215
|
private func cancelFlash() {
|
|
@@ -267,21 +218,12 @@ final class NowPlayingUpdater {
|
|
|
267
218
|
flash = nil
|
|
268
219
|
}
|
|
269
220
|
|
|
270
|
-
|
|
271
|
-
private func defaultLines(for track: Track) -> (title: String, artist: String?, album: String?) {
|
|
272
|
-
(
|
|
273
|
-
flash?.title ?? override?.title ?? track.title,
|
|
274
|
-
flash?.artist ?? override?.artist ?? track.artist,
|
|
275
|
-
flash?.album ?? override?.album ?? track.album,
|
|
276
|
-
)
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
private func metadata(track: Track, title: String, artist: String?, album: String?) -> NowPlayingMetadata {
|
|
221
|
+
private func metadata(track: Track, lines: NowPlayingLines) -> NowPlayingMetadata {
|
|
280
222
|
NowPlayingMetadata(
|
|
281
223
|
elapsedTime: source?.currentTime,
|
|
282
|
-
title: title,
|
|
283
|
-
album: album,
|
|
284
|
-
artist: artist,
|
|
224
|
+
title: lines.title,
|
|
225
|
+
album: lines.album,
|
|
226
|
+
artist: lines.artist,
|
|
285
227
|
duration: track.duration,
|
|
286
228
|
artwork: Self.jsArtwork(for: track),
|
|
287
229
|
description: track.description,
|