@neoskola/auto-play 0.3.22 → 0.3.23

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.
@@ -0,0 +1,69 @@
1
+ import CarPlay
2
+ import Security
3
+ import UIKit
4
+
5
+ enum NeoSkolaNowPlayingPresentationStyle {
6
+ case systemNowPlaying
7
+ case fallbackList
8
+ }
9
+
10
+ enum NeoSkolaNowPlayingTemplate {
11
+ private static let carPlayAudioEntitlement = "com.apple.developer.carplay-audio"
12
+
13
+ static func preferredPresentationStyle() -> NeoSkolaNowPlayingPresentationStyle {
14
+ return hasCarPlayAudioEntitlement() ? .systemNowPlaying : .fallbackList
15
+ }
16
+
17
+ @MainActor
18
+ static func configureSystemButtons(
19
+ isPlaying: Bool,
20
+ canGoPrevious: Bool,
21
+ canGoNext: Bool,
22
+ onPrevious: @escaping () -> Void,
23
+ onTogglePlayPause: @escaping () -> Void,
24
+ onNext: @escaping () -> Void
25
+ ) {
26
+ let previous = CPNowPlayingImageButton(
27
+ image: symbolImage("backward.fill"),
28
+ handler: { _ in onPrevious() }
29
+ )
30
+ previous.isEnabled = canGoPrevious
31
+
32
+ let playPause = CPNowPlayingImageButton(
33
+ image: symbolImage(isPlaying ? "pause.fill" : "play.fill"),
34
+ handler: { _ in onTogglePlayPause() }
35
+ )
36
+ playPause.isEnabled = true
37
+
38
+ let next = CPNowPlayingImageButton(
39
+ image: symbolImage("forward.fill"),
40
+ handler: { _ in onNext() }
41
+ )
42
+ next.isEnabled = canGoNext
43
+
44
+ CPNowPlayingTemplate.shared.updateNowPlayingButtons([previous, playPause, next])
45
+ }
46
+
47
+ @MainActor
48
+ static func clearSystemButtons() {
49
+ CPNowPlayingTemplate.shared.updateNowPlayingButtons([])
50
+ }
51
+
52
+ private static func hasCarPlayAudioEntitlement() -> Bool {
53
+ guard let task = SecTaskCreateFromSelf(nil) else {
54
+ return false
55
+ }
56
+ guard let value = SecTaskCopyValueForEntitlement(
57
+ task,
58
+ carPlayAudioEntitlement as CFString,
59
+ nil
60
+ ) else {
61
+ return false
62
+ }
63
+ return (value as? Bool) ?? false
64
+ }
65
+
66
+ private static func symbolImage(_ name: String) -> UIImage {
67
+ return UIImage(systemName: name) ?? UIImage()
68
+ }
69
+ }
@@ -3,8 +3,10 @@ import MediaPlayer
3
3
  import AVFoundation
4
4
 
5
5
  class NowPlayingTemplate: AutoPlayTemplate {
6
- var template: CPListTemplate
6
+ var template: CPTemplate
7
7
  var config: NowPlayingTemplateConfig
8
+ private let presentationStyle: NeoSkolaNowPlayingPresentationStyle
9
+ private var fallbackTemplate: CPListTemplate?
8
10
  private var loadedImage: UIImage?
9
11
  private var isSetupComplete = false
10
12
  private var currentElapsedTime: Double = 0
@@ -33,32 +35,42 @@ class NowPlayingTemplate: AutoPlayTemplate {
33
35
 
34
36
  init(config: NowPlayingTemplateConfig) {
35
37
  self.config = config
38
+ self.presentationStyle = NeoSkolaNowPlayingTemplate.preferredPresentationStyle()
36
39
 
37
- // CarPlay-safe custom player screen using CPListTemplate.
38
- let titleText = Parser.parseText(text: config.title) ?? "Now Playing"
39
- let subtitleText = config.subtitle.flatMap { Parser.parseText(text: $0) } ?? ""
40
-
41
- let initialInfoItem = CPListItem(
42
- text: titleText,
43
- detailText: subtitleText,
44
- image: UIImage(systemName: "music.note"),
45
- accessoryImage: nil,
46
- accessoryType: .none
47
- )
48
- initialInfoItem.isEnabled = false
49
-
50
- let initialSection = CPListSection(
51
- items: [initialInfoItem],
52
- header: nil,
53
- sectionIndexTitle: nil
54
- )
55
-
56
- template = CPListTemplate(
57
- title: "Now Playing",
58
- sections: [initialSection],
59
- assistantCellConfiguration: nil,
60
- id: config.id
61
- )
40
+ if presentationStyle == .systemNowPlaying {
41
+ template = CPNowPlayingTemplate.shared
42
+ fallbackTemplate = nil
43
+ print("[NowPlayingTemplate] Using CPNowPlayingTemplate (carplay-audio entitlement detected)")
44
+ } else {
45
+ let titleText = Parser.parseText(text: config.title) ?? "Now Playing"
46
+ let subtitleText = config.subtitle.flatMap { Parser.parseText(text: $0) } ?? ""
47
+
48
+ let initialInfoItem = CPListItem(
49
+ text: titleText,
50
+ detailText: subtitleText,
51
+ image: UIImage(systemName: "music.note"),
52
+ accessoryImage: nil,
53
+ accessoryType: .none
54
+ )
55
+ initialInfoItem.isEnabled = false
56
+
57
+ let initialSection = CPListSection(
58
+ items: [initialInfoItem],
59
+ header: nil,
60
+ sectionIndexTitle: nil
61
+ )
62
+
63
+ let listTemplate = CPListTemplate(
64
+ title: "Now Playing",
65
+ sections: [initialSection],
66
+ assistantCellConfiguration: nil,
67
+ id: config.id
68
+ )
69
+
70
+ template = listTemplate
71
+ fallbackTemplate = listTemplate
72
+ print("[NowPlayingTemplate] carplay-audio entitlement missing, using fallback CPListTemplate UI")
73
+ }
62
74
 
63
75
  DispatchQueue.main.async { [weak self] in
64
76
  guard let self = self else { return }
@@ -77,6 +89,38 @@ class NowPlayingTemplate: AutoPlayTemplate {
77
89
  // MARK: - Player UI
78
90
 
79
91
  private func updatePlayerUI() {
92
+ if presentationStyle == .systemNowPlaying {
93
+ DispatchQueue.main.async { [weak self] in
94
+ guard let self = self else { return }
95
+ NeoSkolaNowPlayingTemplate.configureSystemButtons(
96
+ isPlaying: self.config.isPlaying,
97
+ canGoPrevious: self.config.onPreviousTrack != nil,
98
+ canGoNext: self.config.onNextTrack != nil,
99
+ onPrevious: { [weak self] in
100
+ self?.config.onPreviousTrack?()
101
+ },
102
+ onTogglePlayPause: { [weak self] in
103
+ guard let self = self else { return }
104
+ Task { @MainActor in
105
+ if self.config.isPlaying {
106
+ self.pauseAudio()
107
+ self.config.onPause?()
108
+ } else {
109
+ self.resumeAudio()
110
+ self.config.onPlay?()
111
+ }
112
+ }
113
+ },
114
+ onNext: { [weak self] in
115
+ self?.config.onNextTrack?()
116
+ }
117
+ )
118
+ }
119
+ return
120
+ }
121
+
122
+ guard let fallbackTemplate = fallbackTemplate else { return }
123
+
80
124
  let titleText = Parser.parseText(text: config.title) ?? "Now Playing"
81
125
  let subtitleText = config.subtitle.flatMap { Parser.parseText(text: $0) } ?? ""
82
126
  let elapsedText = formatTime(currentElapsedTime)
@@ -187,7 +231,7 @@ class NowPlayingTemplate: AutoPlayTemplate {
187
231
  sectionIndexTitle: nil
188
232
  )
189
233
 
190
- template.updateSections([infoSection, timelineSection, controlSection])
234
+ fallbackTemplate.updateSections([infoSection, timelineSection, controlSection])
191
235
  }
192
236
 
193
237
  private func formatTime(_ seconds: Double) -> String {
@@ -596,6 +640,12 @@ class NowPlayingTemplate: AutoPlayTemplate {
596
640
  config.onPopped?()
597
641
  cleanupPlayer()
598
642
 
643
+ if presentationStyle == .systemNowPlaying {
644
+ Task { @MainActor in
645
+ NeoSkolaNowPlayingTemplate.clearSystemButtons()
646
+ }
647
+ }
648
+
599
649
  let commandCenter = MPRemoteCommandCenter.shared()
600
650
  commandCenter.playCommand.removeTarget(nil)
601
651
  commandCenter.pauseCommand.removeTarget(nil)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neoskola/auto-play",
3
- "version": "0.3.22",
3
+ "version": "0.3.23",
4
4
  "description": "Android Auto and Apple CarPlay for react-native",
5
5
  "main": "lib/index",
6
6
  "module": "lib/index",