@neoskola/auto-play 0.3.3 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -3,7 +3,7 @@ import MediaPlayer
|
|
|
3
3
|
import AVFoundation
|
|
4
4
|
|
|
5
5
|
class NowPlayingTemplate: AutoPlayTemplate {
|
|
6
|
-
var template:
|
|
6
|
+
var template: CPListTemplate
|
|
7
7
|
var config: NowPlayingTemplateConfig
|
|
8
8
|
private var loadedImage: UIImage?
|
|
9
9
|
private var isSetupComplete = false
|
|
@@ -19,6 +19,9 @@ class NowPlayingTemplate: AutoPlayTemplate {
|
|
|
19
19
|
private var didFinishObserver: NSObjectProtocol?
|
|
20
20
|
private var statusObservation: NSKeyValueObservation?
|
|
21
21
|
private var completionFired = false
|
|
22
|
+
private var downloadTask: URLSessionDownloadTask?
|
|
23
|
+
private var localAudioFileURL: URL?
|
|
24
|
+
private var pendingStartFrom: Double = 0
|
|
22
25
|
|
|
23
26
|
var autoDismissMs: Double? {
|
|
24
27
|
return config.autoDismissMs
|
|
@@ -31,19 +34,43 @@ class NowPlayingTemplate: AutoPlayTemplate {
|
|
|
31
34
|
init(config: NowPlayingTemplateConfig) {
|
|
32
35
|
self.config = config
|
|
33
36
|
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
// Custom player screen using CPListTemplate instead of CPNowPlayingTemplate.shared
|
|
38
|
+
let titleText = Parser.parseText(text: config.title) ?? "Now Playing"
|
|
39
|
+
let subtitleText = config.subtitle.flatMap { Parser.parseText(text: $0) } ?? ""
|
|
40
|
+
|
|
41
|
+
let infoItem = CPListItem(
|
|
42
|
+
text: titleText,
|
|
43
|
+
detailText: subtitleText,
|
|
44
|
+
image: UIImage(systemName: "music.note"),
|
|
45
|
+
accessoryImage: nil,
|
|
46
|
+
accessoryType: .none
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
let statusItem = CPListItem(
|
|
50
|
+
text: "Yükleniyor...",
|
|
51
|
+
detailText: nil,
|
|
52
|
+
image: UIImage(systemName: "arrow.down.circle"),
|
|
53
|
+
accessoryImage: nil,
|
|
54
|
+
accessoryType: .none
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
let section = CPListSection(
|
|
58
|
+
items: [infoItem, statusItem],
|
|
59
|
+
header: nil,
|
|
60
|
+
sectionIndexTitle: nil
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
template = CPListTemplate(
|
|
64
|
+
title: "Now Playing",
|
|
65
|
+
sections: [section],
|
|
66
|
+
assistantCellConfiguration: nil,
|
|
67
|
+
id: config.id
|
|
68
|
+
)
|
|
36
69
|
|
|
37
|
-
// Constructor runs on the JS thread. Dispatch all CarPlay UI setup to main thread.
|
|
38
|
-
// CPNowPlayingTemplate.shared is Apple's singleton — must be modified on main thread.
|
|
39
70
|
DispatchQueue.main.async { [weak self] in
|
|
40
71
|
guard let self = self else { return }
|
|
41
|
-
|
|
42
|
-
// Activate AVAudioSession FIRST — iOS needs this to recognize the app
|
|
43
|
-
// as a media player before MPNowPlayingInfoCenter metadata is meaningful.
|
|
44
72
|
NowPlayingSessionManager.shared.ensureSessionActive()
|
|
45
|
-
|
|
46
|
-
self.setupNowPlayingButtons()
|
|
73
|
+
self.setupRemoteCommandCenter()
|
|
47
74
|
self.updateNowPlayingInfo()
|
|
48
75
|
self.isSetupComplete = true
|
|
49
76
|
|
|
@@ -53,6 +80,56 @@ class NowPlayingTemplate: AutoPlayTemplate {
|
|
|
53
80
|
}
|
|
54
81
|
}
|
|
55
82
|
|
|
83
|
+
// MARK: - Player UI
|
|
84
|
+
|
|
85
|
+
private func updatePlayerUI() {
|
|
86
|
+
let titleText = Parser.parseText(text: config.title) ?? "Now Playing"
|
|
87
|
+
let subtitleText = config.subtitle.flatMap { Parser.parseText(text: $0) } ?? ""
|
|
88
|
+
|
|
89
|
+
let infoItem = CPListItem(
|
|
90
|
+
text: titleText,
|
|
91
|
+
detailText: subtitleText,
|
|
92
|
+
image: loadedImage ?? UIImage(systemName: "music.note"),
|
|
93
|
+
accessoryImage: nil,
|
|
94
|
+
accessoryType: .none
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
let statusText: String
|
|
98
|
+
let statusIcon: String
|
|
99
|
+
if config.isPlaying {
|
|
100
|
+
let elapsed = formatTime(currentElapsedTime)
|
|
101
|
+
let total = currentDuration > 0 ? formatTime(currentDuration) : "--:--"
|
|
102
|
+
statusText = "Playing \(elapsed) / \(total)"
|
|
103
|
+
statusIcon = "play.circle.fill"
|
|
104
|
+
} else {
|
|
105
|
+
statusText = "Paused"
|
|
106
|
+
statusIcon = "pause.circle.fill"
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
let statusItem = CPListItem(
|
|
110
|
+
text: statusText,
|
|
111
|
+
detailText: nil,
|
|
112
|
+
image: UIImage(systemName: statusIcon),
|
|
113
|
+
accessoryImage: nil,
|
|
114
|
+
accessoryType: .none
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
let section = CPListSection(
|
|
118
|
+
items: [infoItem, statusItem],
|
|
119
|
+
header: nil,
|
|
120
|
+
sectionIndexTitle: nil
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
template.updateSections([section])
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private func formatTime(_ seconds: Double) -> String {
|
|
127
|
+
guard !seconds.isNaN && !seconds.isInfinite && seconds >= 0 else { return "0:00" }
|
|
128
|
+
let mins = Int(seconds) / 60
|
|
129
|
+
let secs = Int(seconds) % 60
|
|
130
|
+
return String(format: "%d:%02d", mins, secs)
|
|
131
|
+
}
|
|
132
|
+
|
|
56
133
|
// MARK: - Native Audio Playback
|
|
57
134
|
|
|
58
135
|
@MainActor
|
|
@@ -60,6 +137,7 @@ class NowPlayingTemplate: AutoPlayTemplate {
|
|
|
60
137
|
cleanupPlayer()
|
|
61
138
|
completionFired = false
|
|
62
139
|
lastReportedSecond = Int(startFrom)
|
|
140
|
+
pendingStartFrom = startFrom
|
|
63
141
|
|
|
64
142
|
guard let audioURL = URL(string: url) else {
|
|
65
143
|
print("[NowPlayingTemplate] Invalid audio URL: \(url)")
|
|
@@ -69,13 +147,52 @@ class NowPlayingTemplate: AutoPlayTemplate {
|
|
|
69
147
|
NowPlayingSessionManager.shared.ensureSessionActive()
|
|
70
148
|
config.isPlaying = true
|
|
71
149
|
updateNowPlayingInfo()
|
|
150
|
+
updatePlayerUI()
|
|
72
151
|
MPNowPlayingInfoCenter.default().playbackState = .playing
|
|
73
152
|
|
|
74
|
-
|
|
75
|
-
|
|
153
|
+
print("[NowPlayingTemplate] Downloading audio: \(url)")
|
|
154
|
+
|
|
155
|
+
downloadTask = URLSession.shared.downloadTask(with: audioURL) { [weak self] tempURL, response, error in
|
|
156
|
+
guard let self = self else { return }
|
|
157
|
+
|
|
158
|
+
if let error = error {
|
|
159
|
+
print("[NowPlayingTemplate] Download failed: \(error.localizedDescription)")
|
|
160
|
+
return
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
guard let tempURL = tempURL else {
|
|
164
|
+
print("[NowPlayingTemplate] Download returned no file")
|
|
165
|
+
return
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
let localURL = FileManager.default.temporaryDirectory
|
|
169
|
+
.appendingPathComponent("carplay_audio_\(UUID().uuidString).mp3")
|
|
170
|
+
|
|
171
|
+
do {
|
|
172
|
+
if let oldFile = self.localAudioFileURL {
|
|
173
|
+
try? FileManager.default.removeItem(at: oldFile)
|
|
174
|
+
}
|
|
175
|
+
try FileManager.default.moveItem(at: tempURL, to: localURL)
|
|
176
|
+
self.localAudioFileURL = localURL
|
|
177
|
+
print("[NowPlayingTemplate] Audio downloaded to: \(localURL.lastPathComponent)")
|
|
178
|
+
} catch {
|
|
179
|
+
print("[NowPlayingTemplate] Failed to move downloaded file: \(error)")
|
|
180
|
+
return
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
DispatchQueue.main.async { [weak self] in
|
|
184
|
+
self?.startPlaybackFromLocalFile(localURL: localURL, startFrom: self?.pendingStartFrom ?? 0)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
downloadTask?.resume()
|
|
188
|
+
|
|
189
|
+
return true
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private func startPlaybackFromLocalFile(localURL: URL, startFrom: Double) {
|
|
193
|
+
let asset = AVURLAsset(url: localURL)
|
|
76
194
|
playerItem = AVPlayerItem(asset: asset)
|
|
77
195
|
|
|
78
|
-
// KVO: detect duration as soon as asset loads (critical for progress bar)
|
|
79
196
|
statusObservation = playerItem?.observe(\.status, options: [.new]) { [weak self] item, _ in
|
|
80
197
|
guard item.status == .readyToPlay else {
|
|
81
198
|
if item.status == .failed {
|
|
@@ -89,6 +206,7 @@ class NowPlayingTemplate: AutoPlayTemplate {
|
|
|
89
206
|
if !duration.isNaN && !duration.isInfinite && duration > 0 {
|
|
90
207
|
self.currentDuration = duration
|
|
91
208
|
self.updateNowPlayingInfo()
|
|
209
|
+
self.updatePlayerUI()
|
|
92
210
|
print("[NowPlayingTemplate] Duration resolved via KVO: \(duration)s")
|
|
93
211
|
}
|
|
94
212
|
}
|
|
@@ -96,15 +214,11 @@ class NowPlayingTemplate: AutoPlayTemplate {
|
|
|
96
214
|
|
|
97
215
|
player = AVPlayer(playerItem: playerItem)
|
|
98
216
|
|
|
99
|
-
// Seek to start position
|
|
100
217
|
if startFrom > 0 {
|
|
101
218
|
let time = CMTime(seconds: startFrom, preferredTimescale: 600)
|
|
102
219
|
player?.seek(to: time)
|
|
103
220
|
}
|
|
104
221
|
|
|
105
|
-
player?.play()
|
|
106
|
-
|
|
107
|
-
// Periodic time observer (every 1 second) for MPNowPlayingInfoCenter updates
|
|
108
222
|
let interval = CMTime(seconds: 1.0, preferredTimescale: 600)
|
|
109
223
|
timeObserver = player?.addPeriodicTimeObserver(
|
|
110
224
|
forInterval: interval,
|
|
@@ -113,15 +227,6 @@ class NowPlayingTemplate: AutoPlayTemplate {
|
|
|
113
227
|
self?.handleTimeUpdate(time: time)
|
|
114
228
|
}
|
|
115
229
|
|
|
116
|
-
// Progress report timer (every 30 seconds) — calls JS callback for backend reporting
|
|
117
|
-
progressReportTimer = Timer.scheduledTimer(
|
|
118
|
-
withTimeInterval: 30.0,
|
|
119
|
-
repeats: true
|
|
120
|
-
) { [weak self] _ in
|
|
121
|
-
self?.reportProgress()
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Playback finished notification
|
|
125
230
|
didFinishObserver = NotificationCenter.default.addObserver(
|
|
126
231
|
forName: .AVPlayerItemDidPlayToEndTime,
|
|
127
232
|
object: playerItem,
|
|
@@ -130,8 +235,17 @@ class NowPlayingTemplate: AutoPlayTemplate {
|
|
|
130
235
|
self?.handlePlaybackFinished()
|
|
131
236
|
}
|
|
132
237
|
|
|
133
|
-
|
|
134
|
-
|
|
238
|
+
progressReportTimer = Timer.scheduledTimer(
|
|
239
|
+
withTimeInterval: 30.0,
|
|
240
|
+
repeats: true
|
|
241
|
+
) { [weak self] _ in
|
|
242
|
+
self?.reportProgress()
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
player?.play()
|
|
246
|
+
MPNowPlayingInfoCenter.default().playbackState = .playing
|
|
247
|
+
|
|
248
|
+
print("[NowPlayingTemplate] Playback started from local file")
|
|
135
249
|
}
|
|
136
250
|
|
|
137
251
|
private func handleTimeUpdate(time: CMTime) {
|
|
@@ -145,10 +259,8 @@ class NowPlayingTemplate: AutoPlayTemplate {
|
|
|
145
259
|
currentDuration = duration
|
|
146
260
|
}
|
|
147
261
|
|
|
148
|
-
// Use existing info or create fresh if nil (race condition safety)
|
|
149
262
|
var nowPlayingInfo = MPNowPlayingInfoCenter.default().nowPlayingInfo ?? [String: Any]()
|
|
150
263
|
|
|
151
|
-
// Ensure title/artist are present if this is a fresh dictionary
|
|
152
264
|
if nowPlayingInfo[MPMediaItemPropertyTitle] == nil {
|
|
153
265
|
let titleText = Parser.parseText(text: config.title) ?? ""
|
|
154
266
|
nowPlayingInfo[MPMediaItemPropertyTitle] = titleText
|
|
@@ -164,6 +276,9 @@ class NowPlayingTemplate: AutoPlayTemplate {
|
|
|
164
276
|
nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = config.isPlaying ? 1.0 : 0.0
|
|
165
277
|
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
|
|
166
278
|
|
|
279
|
+
// Update custom player UI
|
|
280
|
+
updatePlayerUI()
|
|
281
|
+
|
|
167
282
|
// 95% completion check
|
|
168
283
|
if !completionFired && currentDuration > 0 && currentTime / currentDuration >= 0.95 {
|
|
169
284
|
completionFired = true
|
|
@@ -181,7 +296,7 @@ class NowPlayingTemplate: AutoPlayTemplate {
|
|
|
181
296
|
private func handlePlaybackFinished() {
|
|
182
297
|
config.isPlaying = false
|
|
183
298
|
MPNowPlayingInfoCenter.default().playbackState = .stopped
|
|
184
|
-
|
|
299
|
+
updatePlayerUI()
|
|
185
300
|
if !completionFired {
|
|
186
301
|
completionFired = true
|
|
187
302
|
config.onComplete?()
|
|
@@ -194,6 +309,7 @@ class NowPlayingTemplate: AutoPlayTemplate {
|
|
|
194
309
|
player?.pause()
|
|
195
310
|
config.isPlaying = false
|
|
196
311
|
updatePlaybackState(isPlaying: false)
|
|
312
|
+
updatePlayerUI()
|
|
197
313
|
reportProgress()
|
|
198
314
|
}
|
|
199
315
|
|
|
@@ -203,6 +319,7 @@ class NowPlayingTemplate: AutoPlayTemplate {
|
|
|
203
319
|
player?.play()
|
|
204
320
|
config.isPlaying = true
|
|
205
321
|
updatePlaybackState(isPlaying: true)
|
|
322
|
+
updatePlayerUI()
|
|
206
323
|
}
|
|
207
324
|
|
|
208
325
|
@MainActor
|
|
@@ -227,9 +344,12 @@ class NowPlayingTemplate: AutoPlayTemplate {
|
|
|
227
344
|
cleanupPlayer()
|
|
228
345
|
config.isPlaying = false
|
|
229
346
|
MPNowPlayingInfoCenter.default().playbackState = .stopped
|
|
347
|
+
updatePlayerUI()
|
|
230
348
|
}
|
|
231
349
|
|
|
232
350
|
private func cleanupPlayer() {
|
|
351
|
+
downloadTask?.cancel()
|
|
352
|
+
downloadTask = nil
|
|
233
353
|
statusObservation?.invalidate()
|
|
234
354
|
statusObservation = nil
|
|
235
355
|
if let timeObserver = timeObserver {
|
|
@@ -245,32 +365,14 @@ class NowPlayingTemplate: AutoPlayTemplate {
|
|
|
245
365
|
player?.pause()
|
|
246
366
|
player = nil
|
|
247
367
|
playerItem = nil
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
private func setupNowPlayingButtons() {
|
|
253
|
-
var buttons: [CPNowPlayingButton] = []
|
|
254
|
-
|
|
255
|
-
let skipBackButton = CPNowPlayingImageButton(
|
|
256
|
-
image: UIImage(systemName: "gobackward.30")!
|
|
257
|
-
) { [weak self] _ in
|
|
258
|
-
self?.seekBackward(seconds: 30)
|
|
259
|
-
self?.config.onSkipBackward?()
|
|
260
|
-
}
|
|
261
|
-
buttons.append(skipBackButton)
|
|
262
|
-
|
|
263
|
-
let skipForwardButton = CPNowPlayingImageButton(
|
|
264
|
-
image: UIImage(systemName: "goforward.30")!
|
|
265
|
-
) { [weak self] _ in
|
|
266
|
-
self?.seekForward(seconds: 30)
|
|
267
|
-
self?.config.onSkipForward?()
|
|
368
|
+
if let localFile = localAudioFileURL {
|
|
369
|
+
try? FileManager.default.removeItem(at: localFile)
|
|
370
|
+
localAudioFileURL = nil
|
|
268
371
|
}
|
|
269
|
-
buttons.append(skipForwardButton)
|
|
270
|
-
|
|
271
|
-
template.updateNowPlayingButtons(buttons)
|
|
272
372
|
}
|
|
273
373
|
|
|
374
|
+
// MARK: - Now Playing Info & Remote Commands
|
|
375
|
+
|
|
274
376
|
private func updateNowPlayingInfo() {
|
|
275
377
|
let titleText = Parser.parseText(text: config.title) ?? ""
|
|
276
378
|
let subtitleText = config.subtitle.map { Parser.parseText(text: $0) } ?? nil
|
|
@@ -288,7 +390,6 @@ class NowPlayingTemplate: AutoPlayTemplate {
|
|
|
288
390
|
nowPlayingInfo[MPMediaItemPropertyArtwork] = artwork
|
|
289
391
|
}
|
|
290
392
|
|
|
291
|
-
// Include duration and elapsed time for the progress bar
|
|
292
393
|
if currentDuration > 0 {
|
|
293
394
|
nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = currentDuration
|
|
294
395
|
nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = currentElapsedTime
|
|
@@ -296,8 +397,6 @@ class NowPlayingTemplate: AutoPlayTemplate {
|
|
|
296
397
|
}
|
|
297
398
|
|
|
298
399
|
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
|
|
299
|
-
|
|
300
|
-
setupRemoteCommandCenter()
|
|
301
400
|
}
|
|
302
401
|
|
|
303
402
|
private func setupRemoteCommandCenter() {
|
|
@@ -348,6 +447,7 @@ class NowPlayingTemplate: AutoPlayTemplate {
|
|
|
348
447
|
self.player?.seek(to: time)
|
|
349
448
|
self.currentElapsedTime = positionEvent.positionTime
|
|
350
449
|
self.updateNowPlayingInfo()
|
|
450
|
+
self.updatePlayerUI()
|
|
351
451
|
return .success
|
|
352
452
|
}
|
|
353
453
|
}
|
|
@@ -363,6 +463,7 @@ class NowPlayingTemplate: AutoPlayTemplate {
|
|
|
363
463
|
DispatchQueue.main.async {
|
|
364
464
|
self.loadedImage = uiImage
|
|
365
465
|
self.updateNowPlayingInfo()
|
|
466
|
+
self.updatePlayerUI()
|
|
366
467
|
}
|
|
367
468
|
}.resume()
|
|
368
469
|
}
|
|
@@ -372,7 +473,7 @@ class NowPlayingTemplate: AutoPlayTemplate {
|
|
|
372
473
|
|
|
373
474
|
@MainActor
|
|
374
475
|
func invalidate() {
|
|
375
|
-
|
|
476
|
+
updatePlayerUI()
|
|
376
477
|
updateNowPlayingInfo()
|
|
377
478
|
|
|
378
479
|
if loadedImage == nil, let image = config.image {
|
|
@@ -407,7 +508,6 @@ class NowPlayingTemplate: AutoPlayTemplate {
|
|
|
407
508
|
commandCenter.skipBackwardCommand.removeTarget(nil)
|
|
408
509
|
commandCenter.changePlaybackPositionCommand.removeTarget(nil)
|
|
409
510
|
|
|
410
|
-
// Clear now playing info so CarPlay hides the Now Playing bar
|
|
411
511
|
MPNowPlayingInfoCenter.default().nowPlayingInfo = nil
|
|
412
512
|
MPNowPlayingInfoCenter.default().playbackState = .stopped
|
|
413
513
|
}
|
|
@@ -415,19 +515,14 @@ class NowPlayingTemplate: AutoPlayTemplate {
|
|
|
415
515
|
@MainActor
|
|
416
516
|
func updatePlaybackState(isPlaying: Bool) {
|
|
417
517
|
config.isPlaying = isPlaying
|
|
418
|
-
|
|
419
|
-
// Ensure AVAudioSession is active — required for CarPlay Now Playing bar
|
|
420
518
|
NowPlayingSessionManager.shared.ensureSessionActive()
|
|
421
519
|
|
|
422
|
-
// If constructor's DispatchQueue.main.async hasn't run yet,
|
|
423
|
-
// nowPlayingInfo could be nil. Set it up now to fix the race condition.
|
|
424
520
|
if !isSetupComplete {
|
|
425
|
-
|
|
521
|
+
updatePlayerUI()
|
|
426
522
|
updateNowPlayingInfo()
|
|
427
523
|
isSetupComplete = true
|
|
428
524
|
}
|
|
429
525
|
|
|
430
|
-
// Update playback rate — use existing info or create fresh if nil
|
|
431
526
|
var nowPlayingInfo = MPNowPlayingInfoCenter.default().nowPlayingInfo ?? [String: Any]()
|
|
432
527
|
|
|
433
528
|
if nowPlayingInfo[MPMediaItemPropertyTitle] == nil {
|
|
@@ -450,6 +545,7 @@ class NowPlayingTemplate: AutoPlayTemplate {
|
|
|
450
545
|
config.title = AutoText(text: title, distance: nil, duration: nil)
|
|
451
546
|
config.subtitle = AutoText(text: subtitle, distance: nil, duration: nil)
|
|
452
547
|
updateNowPlayingInfo()
|
|
548
|
+
updatePlayerUI()
|
|
453
549
|
}
|
|
454
550
|
|
|
455
551
|
@MainActor
|
|
@@ -457,7 +553,6 @@ class NowPlayingTemplate: AutoPlayTemplate {
|
|
|
457
553
|
self.currentElapsedTime = elapsedTime
|
|
458
554
|
self.currentDuration = duration
|
|
459
555
|
|
|
460
|
-
// Update time-related fields — use existing info or create fresh if nil
|
|
461
556
|
var nowPlayingInfo = MPNowPlayingInfoCenter.default().nowPlayingInfo ?? [String: Any]()
|
|
462
557
|
|
|
463
558
|
if nowPlayingInfo[MPMediaItemPropertyTitle] == nil {
|
|
@@ -472,5 +567,7 @@ class NowPlayingTemplate: AutoPlayTemplate {
|
|
|
472
567
|
nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = elapsedTime
|
|
473
568
|
nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = config.isPlaying ? 1.0 : 0.0
|
|
474
569
|
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
|
|
570
|
+
|
|
571
|
+
updatePlayerUI()
|
|
475
572
|
}
|
|
476
573
|
}
|