@neoskola/auto-play 0.3.21 → 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:
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
text:
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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)
|
|
@@ -144,14 +188,16 @@ class NowPlayingTemplate: AutoPlayTemplate {
|
|
|
144
188
|
return
|
|
145
189
|
}
|
|
146
190
|
|
|
147
|
-
|
|
148
|
-
self.
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
191
|
+
Task { @MainActor in
|
|
192
|
+
if self.config.isPlaying {
|
|
193
|
+
self.pauseAudio()
|
|
194
|
+
self.config.onPause?()
|
|
195
|
+
} else {
|
|
196
|
+
self.resumeAudio()
|
|
197
|
+
self.config.onPlay?()
|
|
198
|
+
}
|
|
199
|
+
completion()
|
|
153
200
|
}
|
|
154
|
-
completion()
|
|
155
201
|
}
|
|
156
202
|
|
|
157
203
|
let nextItem = CPListItem(
|
|
@@ -185,7 +231,7 @@ class NowPlayingTemplate: AutoPlayTemplate {
|
|
|
185
231
|
sectionIndexTitle: nil
|
|
186
232
|
)
|
|
187
233
|
|
|
188
|
-
|
|
234
|
+
fallbackTemplate.updateSections([infoSection, timelineSection, controlSection])
|
|
189
235
|
}
|
|
190
236
|
|
|
191
237
|
private func formatTime(_ seconds: Double) -> String {
|
|
@@ -594,6 +640,12 @@ class NowPlayingTemplate: AutoPlayTemplate {
|
|
|
594
640
|
config.onPopped?()
|
|
595
641
|
cleanupPlayer()
|
|
596
642
|
|
|
643
|
+
if presentationStyle == .systemNowPlaying {
|
|
644
|
+
Task { @MainActor in
|
|
645
|
+
NeoSkolaNowPlayingTemplate.clearSystemButtons()
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
597
649
|
let commandCenter = MPRemoteCommandCenter.shared()
|
|
598
650
|
commandCenter.playCommand.removeTarget(nil)
|
|
599
651
|
commandCenter.pauseCommand.removeTarget(nil)
|