@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:
|
|
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)
|
|
@@ -187,7 +231,7 @@ class NowPlayingTemplate: AutoPlayTemplate {
|
|
|
187
231
|
sectionIndexTitle: nil
|
|
188
232
|
)
|
|
189
233
|
|
|
190
|
-
|
|
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)
|