@neoskola/auto-play 0.3.14 → 0.3.16

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.
@@ -188,13 +188,6 @@ class HybridAutoPlay: HybridAutoPlaySpec {
188
188
  )
189
189
  }
190
190
 
191
- // NowPlayingTemplate: inject custom UIKit view into CPWindow
192
- if let nowPlaying = template as? NowPlayingTemplate {
193
- try await MainActor.run {
194
- try nowPlaying.injectCustomView()
195
- }
196
- }
197
-
198
191
  if let autoDismissMs = TemplateStore.getTemplate(
199
192
  templateId: templateId
200
193
  )?.autoDismissMs {
@@ -2,18 +2,13 @@ import CarPlay
2
2
  import MediaPlayer
3
3
  import AVFoundation
4
4
 
5
- class NowPlayingTemplate: NSObject, AutoPlayTemplate, CPMapTemplateDelegate {
6
- var template: CPMapTemplate
5
+ class NowPlayingTemplate: NSObject, AutoPlayTemplate {
7
6
  var config: NowPlayingTemplateConfig
8
7
  private var loadedImage: UIImage?
9
8
  private var isSetupComplete = false
10
9
  private var currentElapsedTime: Double = 0
11
10
  private var currentDuration: Double = 0
12
11
 
13
- // Custom UIKit view
14
- private var customViewController: NeoSkolaNowPlayingViewController?
15
- private var previousRootVC: UIViewController?
16
-
17
12
  // Native audio player
18
13
  private var player: AVPlayer?
19
14
  private var playerItem: AVPlayerItem?
@@ -32,125 +27,27 @@ class NowPlayingTemplate: NSObject, AutoPlayTemplate, CPMapTemplateDelegate {
32
27
  }
33
28
 
34
29
  func getTemplate() -> CPTemplate {
35
- return template
30
+ return CPNowPlayingTemplate.shared
36
31
  }
37
32
 
38
33
  init(config: NowPlayingTemplateConfig) {
39
34
  self.config = config
40
- template = CPMapTemplate(id: config.id)
41
35
 
42
36
  super.init()
43
37
 
44
- template.mapDelegate = self
45
- template.mapButtons = Self.buildMapButtons(isPlaying: config.isPlaying, owner: self)
46
-
47
- DispatchQueue.main.async { [weak self] in
48
- guard let self = self else { return }
49
- NowPlayingSessionManager.shared.ensureSessionActive()
50
- self.setupRemoteCommandCenter()
51
- self.updateNowPlayingInfo()
52
- self.isSetupComplete = true
53
-
54
- if let image = config.image {
55
- self.loadImageAsync(image: image)
56
- }
57
- }
58
- }
59
-
60
- // MARK: - CPMapButton Controls
61
-
62
- private static func buildMapButtons(isPlaying: Bool, owner: NowPlayingTemplate) -> [CPMapButton] {
63
- let buttonSize = CPButtonMaximumImageSize
64
-
65
- // Previous track button
66
- let prevImage = UIImage(systemName: "backward.end.fill")?
67
- .withTintColor(.white, renderingMode: .alwaysOriginal)
68
- .resized(to: buttonSize)
69
- let prevButton = CPMapButton(image: prevImage ?? UIImage()) { [weak owner] _ in
70
- owner?.config.onPreviousTrack?()
71
- }
72
-
73
- // Play/Pause button
74
- let playPauseIconName = isPlaying ? "pause.circle.fill" : "play.circle.fill"
75
- let playPauseImage = UIImage(systemName: playPauseIconName)?
76
- .withTintColor(.white, renderingMode: .alwaysOriginal)
77
- .resized(to: buttonSize)
78
- let playPauseButton = CPMapButton(image: playPauseImage ?? UIImage()) { [weak owner] _ in
79
- guard let owner = owner else { return }
80
- DispatchQueue.main.async {
81
- if owner.config.isPlaying {
82
- owner.pauseAudio()
83
- owner.config.onPause?()
84
- } else {
85
- owner.resumeAudio()
86
- owner.config.onPlay?()
87
- }
88
- }
89
- }
90
-
91
- // Next track button
92
- let nextImage = UIImage(systemName: "forward.end.fill")?
93
- .withTintColor(.white, renderingMode: .alwaysOriginal)
94
- .resized(to: buttonSize)
95
- let nextButton = CPMapButton(image: nextImage ?? UIImage()) { [weak owner] _ in
96
- owner?.config.onNextTrack?()
97
- }
98
-
99
- return [prevButton, playPauseButton, nextButton]
100
- }
101
-
102
- private func updateMapButtons() {
103
- template.mapButtons = Self.buildMapButtons(isPlaying: config.isPlaying, owner: self)
104
- }
105
-
106
- // MARK: - Custom View Injection
38
+ // Set the config ID on the shared singleton so TemplateStore can find it
39
+ initTemplate(template: CPNowPlayingTemplate.shared, id: config.id)
107
40
 
108
- @MainActor
109
- func injectCustomView() throws {
110
- guard let scene = SceneStore.getRootScene(),
111
- let window = scene.window else {
112
- throw AutoPlayError.noUiWindow("NowPlaying: window nil")
113
- }
114
-
115
- previousRootVC = window.rootViewController
116
-
117
- let customVC = NeoSkolaNowPlayingViewController()
118
-
119
- let titleText = Parser.parseText(text: config.title) ?? "Now Playing"
120
- let subtitleText = config.subtitle.flatMap { Parser.parseText(text: $0) } ?? ""
121
- customVC.updateInfo(courseName: subtitleText, lessonName: titleText)
122
- customVC.updatePlaybackState(isPlaying: config.isPlaying)
123
-
124
- window.rootViewController = customVC
125
- window.makeKeyAndVisible()
126
-
127
- self.customViewController = customVC
128
- }
129
-
130
- @MainActor
131
- private func restoreOriginalView() {
132
- guard let scene = SceneStore.getRootScene(),
133
- let window = scene.window else { return }
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()
46
+ isSetupComplete = true
134
47
 
135
- if let previousRootVC = previousRootVC {
136
- window.rootViewController = previousRootVC
137
- window.makeKeyAndVisible()
48
+ if let image = config.image {
49
+ loadImageAsync(image: image)
138
50
  }
139
- self.previousRootVC = nil
140
- self.customViewController = nil
141
- }
142
-
143
- // MARK: - Player UI
144
-
145
- private func updatePlayerUI() {
146
- guard let customVC = customViewController else { return }
147
-
148
- let titleText = Parser.parseText(text: config.title) ?? ""
149
- let subtitleText = config.subtitle.flatMap { Parser.parseText(text: $0) } ?? ""
150
- customVC.updateInfo(courseName: subtitleText, lessonName: titleText)
151
- customVC.updatePlaybackState(isPlaying: config.isPlaying)
152
- customVC.updateTime(elapsed: currentElapsedTime, duration: currentDuration)
153
- updateMapButtons()
154
51
  }
155
52
 
156
53
  // MARK: - Native Audio Playback
@@ -170,7 +67,6 @@ class NowPlayingTemplate: NSObject, AutoPlayTemplate, CPMapTemplateDelegate {
170
67
  NowPlayingSessionManager.shared.ensureSessionActive()
171
68
  config.isPlaying = true
172
69
  updateNowPlayingInfo()
173
- updatePlayerUI()
174
70
  MPNowPlayingInfoCenter.default().playbackState = .playing
175
71
 
176
72
  print("[NowPlayingTemplate] Downloading audio: \(url)")
@@ -229,7 +125,6 @@ class NowPlayingTemplate: NSObject, AutoPlayTemplate, CPMapTemplateDelegate {
229
125
  if !duration.isNaN && !duration.isInfinite && duration > 0 {
230
126
  self.currentDuration = duration
231
127
  self.updateNowPlayingInfo()
232
- self.updatePlayerUI()
233
128
  print("[NowPlayingTemplate] Duration resolved via KVO: \(duration)s")
234
129
  }
235
130
  }
@@ -299,8 +194,6 @@ class NowPlayingTemplate: NSObject, AutoPlayTemplate, CPMapTemplateDelegate {
299
194
  nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = config.isPlaying ? 1.0 : 0.0
300
195
  MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
301
196
 
302
- updatePlayerUI()
303
-
304
197
  // 95% completion check
305
198
  if !completionFired && currentDuration > 0 && currentTime / currentDuration >= 0.95 {
306
199
  completionFired = true
@@ -318,7 +211,6 @@ class NowPlayingTemplate: NSObject, AutoPlayTemplate, CPMapTemplateDelegate {
318
211
  private func handlePlaybackFinished() {
319
212
  config.isPlaying = false
320
213
  MPNowPlayingInfoCenter.default().playbackState = .stopped
321
- updatePlayerUI()
322
214
  if !completionFired {
323
215
  completionFired = true
324
216
  config.onComplete?()
@@ -331,7 +223,6 @@ class NowPlayingTemplate: NSObject, AutoPlayTemplate, CPMapTemplateDelegate {
331
223
  player?.pause()
332
224
  config.isPlaying = false
333
225
  updatePlaybackState(isPlaying: false)
334
- updatePlayerUI()
335
226
  reportProgress()
336
227
  }
337
228
 
@@ -341,7 +232,6 @@ class NowPlayingTemplate: NSObject, AutoPlayTemplate, CPMapTemplateDelegate {
341
232
  player?.play()
342
233
  config.isPlaying = true
343
234
  updatePlaybackState(isPlaying: true)
344
- updatePlayerUI()
345
235
  }
346
236
 
347
237
  @MainActor
@@ -366,7 +256,6 @@ class NowPlayingTemplate: NSObject, AutoPlayTemplate, CPMapTemplateDelegate {
366
256
  cleanupPlayer()
367
257
  config.isPlaying = false
368
258
  MPNowPlayingInfoCenter.default().playbackState = .stopped
369
- updatePlayerUI()
370
259
  }
371
260
 
372
261
  private func cleanupPlayer() {
@@ -483,7 +372,6 @@ class NowPlayingTemplate: NSObject, AutoPlayTemplate, CPMapTemplateDelegate {
483
372
  self.player?.seek(to: time)
484
373
  self.currentElapsedTime = positionEvent.positionTime
485
374
  self.updateNowPlayingInfo()
486
- self.updatePlayerUI()
487
375
  return .success
488
376
  }
489
377
  }
@@ -508,7 +396,6 @@ class NowPlayingTemplate: NSObject, AutoPlayTemplate, CPMapTemplateDelegate {
508
396
 
509
397
  @MainActor
510
398
  func invalidate() {
511
- updatePlayerUI()
512
399
  updateNowPlayingInfo()
513
400
 
514
401
  if loadedImage == nil, let image = config.image {
@@ -535,7 +422,6 @@ class NowPlayingTemplate: NSObject, AutoPlayTemplate, CPMapTemplateDelegate {
535
422
  func onPopped() {
536
423
  config.onPopped?()
537
424
  cleanupPlayer()
538
- restoreOriginalView()
539
425
 
540
426
  let commandCenter = MPRemoteCommandCenter.shared()
541
427
  commandCenter.playCommand.removeTarget(nil)
@@ -556,7 +442,6 @@ class NowPlayingTemplate: NSObject, AutoPlayTemplate, CPMapTemplateDelegate {
556
442
  NowPlayingSessionManager.shared.ensureSessionActive()
557
443
 
558
444
  if !isSetupComplete {
559
- updatePlayerUI()
560
445
  updateNowPlayingInfo()
561
446
  isSetupComplete = true
562
447
  }
@@ -583,7 +468,6 @@ class NowPlayingTemplate: NSObject, AutoPlayTemplate, CPMapTemplateDelegate {
583
468
  config.title = AutoText(text: title, distance: nil, duration: nil)
584
469
  config.subtitle = AutoText(text: subtitle, distance: nil, duration: nil)
585
470
  updateNowPlayingInfo()
586
- updatePlayerUI()
587
471
  }
588
472
 
589
473
  @MainActor
@@ -605,11 +489,5 @@ class NowPlayingTemplate: NSObject, AutoPlayTemplate, CPMapTemplateDelegate {
605
489
  nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = elapsedTime
606
490
  nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = config.isPlaying ? 1.0 : 0.0
607
491
  MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
608
-
609
- updatePlayerUI()
610
492
  }
611
-
612
- // MARK: - CPMapTemplateDelegate
613
-
614
- func mapTemplate(_ mapTemplate: CPMapTemplate, panWith direction: CPMapTemplate.PanDirection) {}
615
493
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neoskola/auto-play",
3
- "version": "0.3.14",
3
+ "version": "0.3.16",
4
4
  "description": "Android Auto and Apple CarPlay for react-native",
5
5
  "main": "lib/index",
6
6
  "module": "lib/index",