@neoskola/auto-play 0.3.18 → 0.3.19

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.
@@ -2,7 +2,8 @@ import CarPlay
2
2
  import MediaPlayer
3
3
  import AVFoundation
4
4
 
5
- class NowPlayingTemplate: NSObject, AutoPlayTemplate {
5
+ class NowPlayingTemplate: AutoPlayTemplate {
6
+ var template: CPListTemplate
6
7
  var config: NowPlayingTemplateConfig
7
8
  private var loadedImage: UIImage?
8
9
  private var isSetupComplete = false
@@ -27,33 +28,106 @@ class NowPlayingTemplate: NSObject, AutoPlayTemplate {
27
28
  }
28
29
 
29
30
  func getTemplate() -> CPTemplate {
30
- return CPNowPlayingTemplate.shared
31
+ return template
31
32
  }
32
33
 
33
34
  init(config: NowPlayingTemplateConfig) {
34
35
  self.config = config
35
36
 
36
- super.init()
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
+ )
69
+
70
+ DispatchQueue.main.async { [weak self] in
71
+ guard let self = self else { return }
72
+ NowPlayingSessionManager.shared.ensureSessionActive()
73
+ self.setupRemoteCommandCenter()
74
+ self.updateNowPlayingInfo()
75
+ self.isSetupComplete = true
37
76
 
38
- // Set the config ID on the shared singleton so TemplateStore can find it
39
- initTemplate(template: CPNowPlayingTemplate.shared, id: config.id)
77
+ if let image = config.image {
78
+ self.loadImageAsync(image: image)
79
+ }
80
+ }
81
+ }
40
82
 
41
- // Setup remote commands and now playing info synchronously
42
- // so they're ready before CPNowPlayingTemplate.shared is pushed
43
- NowPlayingSessionManager.shared.ensureSessionActive()
44
- setupRemoteCommandCenter()
45
- updateNowPlayingInfo()
83
+ // MARK: - Player UI
46
84
 
47
- // Set playbackState before push so CarPlay sees an active now-playing session
48
- if config.isPlaying {
49
- MPNowPlayingInfoCenter.default().playbackState = .playing
50
- }
85
+ private func updatePlayerUI() {
86
+ let titleText = Parser.parseText(text: config.title) ?? "Now Playing"
87
+ let subtitleText = config.subtitle.flatMap { Parser.parseText(text: $0) } ?? ""
51
88
 
52
- isSetupComplete = true
89
+ let infoItem = CPListItem(
90
+ text: titleText,
91
+ detailText: subtitleText,
92
+ image: loadedImage ?? UIImage(systemName: "music.note"),
93
+ accessoryImage: nil,
94
+ accessoryType: .none
95
+ )
53
96
 
54
- if let image = config.image {
55
- loadImageAsync(image: image)
56
- }
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)
57
131
  }
58
132
 
59
133
  // MARK: - Native Audio Playback
@@ -73,6 +147,7 @@ class NowPlayingTemplate: NSObject, AutoPlayTemplate {
73
147
  NowPlayingSessionManager.shared.ensureSessionActive()
74
148
  config.isPlaying = true
75
149
  updateNowPlayingInfo()
150
+ updatePlayerUI()
76
151
  MPNowPlayingInfoCenter.default().playbackState = .playing
77
152
 
78
153
  print("[NowPlayingTemplate] Downloading audio: \(url)")
@@ -131,6 +206,7 @@ class NowPlayingTemplate: NSObject, AutoPlayTemplate {
131
206
  if !duration.isNaN && !duration.isInfinite && duration > 0 {
132
207
  self.currentDuration = duration
133
208
  self.updateNowPlayingInfo()
209
+ self.updatePlayerUI()
134
210
  print("[NowPlayingTemplate] Duration resolved via KVO: \(duration)s")
135
211
  }
136
212
  }
@@ -200,6 +276,9 @@ class NowPlayingTemplate: NSObject, AutoPlayTemplate {
200
276
  nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = config.isPlaying ? 1.0 : 0.0
201
277
  MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
202
278
 
279
+ // Update custom player UI
280
+ updatePlayerUI()
281
+
203
282
  // 95% completion check
204
283
  if !completionFired && currentDuration > 0 && currentTime / currentDuration >= 0.95 {
205
284
  completionFired = true
@@ -217,6 +296,7 @@ class NowPlayingTemplate: NSObject, AutoPlayTemplate {
217
296
  private func handlePlaybackFinished() {
218
297
  config.isPlaying = false
219
298
  MPNowPlayingInfoCenter.default().playbackState = .stopped
299
+ updatePlayerUI()
220
300
  if !completionFired {
221
301
  completionFired = true
222
302
  config.onComplete?()
@@ -229,6 +309,7 @@ class NowPlayingTemplate: NSObject, AutoPlayTemplate {
229
309
  player?.pause()
230
310
  config.isPlaying = false
231
311
  updatePlaybackState(isPlaying: false)
312
+ updatePlayerUI()
232
313
  reportProgress()
233
314
  }
234
315
 
@@ -238,6 +319,7 @@ class NowPlayingTemplate: NSObject, AutoPlayTemplate {
238
319
  player?.play()
239
320
  config.isPlaying = true
240
321
  updatePlaybackState(isPlaying: true)
322
+ updatePlayerUI()
241
323
  }
242
324
 
243
325
  @MainActor
@@ -262,6 +344,7 @@ class NowPlayingTemplate: NSObject, AutoPlayTemplate {
262
344
  cleanupPlayer()
263
345
  config.isPlaying = false
264
346
  MPNowPlayingInfoCenter.default().playbackState = .stopped
347
+ updatePlayerUI()
265
348
  }
266
349
 
267
350
  private func cleanupPlayer() {
@@ -307,8 +390,6 @@ class NowPlayingTemplate: NSObject, AutoPlayTemplate {
307
390
  nowPlayingInfo[MPMediaItemPropertyArtwork] = artwork
308
391
  }
309
392
 
310
- // Keep playback metadata populated even before duration is known.
311
- // This helps CarPlay recognize an active now-playing session.
312
393
  nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = currentElapsedTime
313
394
  nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = config.isPlaying ? 1.0 : 0.0
314
395
 
@@ -381,6 +462,7 @@ class NowPlayingTemplate: NSObject, AutoPlayTemplate {
381
462
  self.player?.seek(to: time)
382
463
  self.currentElapsedTime = positionEvent.positionTime
383
464
  self.updateNowPlayingInfo()
465
+ self.updatePlayerUI()
384
466
  return .success
385
467
  }
386
468
  }
@@ -396,6 +478,7 @@ class NowPlayingTemplate: NSObject, AutoPlayTemplate {
396
478
  DispatchQueue.main.async {
397
479
  self.loadedImage = uiImage
398
480
  self.updateNowPlayingInfo()
481
+ self.updatePlayerUI()
399
482
  }
400
483
  }.resume()
401
484
  }
@@ -405,6 +488,7 @@ class NowPlayingTemplate: NSObject, AutoPlayTemplate {
405
488
 
406
489
  @MainActor
407
490
  func invalidate() {
491
+ updatePlayerUI()
408
492
  updateNowPlayingInfo()
409
493
 
410
494
  if loadedImage == nil, let image = config.image {
@@ -451,6 +535,7 @@ class NowPlayingTemplate: NSObject, AutoPlayTemplate {
451
535
  NowPlayingSessionManager.shared.ensureSessionActive()
452
536
 
453
537
  if !isSetupComplete {
538
+ updatePlayerUI()
454
539
  updateNowPlayingInfo()
455
540
  isSetupComplete = true
456
541
  }
@@ -477,6 +562,7 @@ class NowPlayingTemplate: NSObject, AutoPlayTemplate {
477
562
  config.title = AutoText(text: title, distance: nil, duration: nil)
478
563
  config.subtitle = AutoText(text: subtitle, distance: nil, duration: nil)
479
564
  updateNowPlayingInfo()
565
+ updatePlayerUI()
480
566
  }
481
567
 
482
568
  @MainActor
@@ -498,5 +584,7 @@ class NowPlayingTemplate: NSObject, AutoPlayTemplate {
498
584
  nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = elapsedTime
499
585
  nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = config.isPlaying ? 1.0 : 0.0
500
586
  MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
587
+
588
+ updatePlayerUI()
501
589
  }
502
590
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neoskola/auto-play",
3
- "version": "0.3.18",
3
+ "version": "0.3.19",
4
4
  "description": "Android Auto and Apple CarPlay for react-native",
5
5
  "main": "lib/index",
6
6
  "module": "lib/index",