@neoskola/auto-play 0.3.19 → 0.3.21
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.
|
@@ -34,35 +34,28 @@ class NowPlayingTemplate: AutoPlayTemplate {
|
|
|
34
34
|
init(config: NowPlayingTemplateConfig) {
|
|
35
35
|
self.config = config
|
|
36
36
|
|
|
37
|
-
//
|
|
37
|
+
// CarPlay-safe custom player screen using CPListTemplate.
|
|
38
38
|
let titleText = Parser.parseText(text: config.title) ?? "Now Playing"
|
|
39
39
|
let subtitleText = config.subtitle.flatMap { Parser.parseText(text: $0) } ?? ""
|
|
40
40
|
|
|
41
|
-
let
|
|
41
|
+
let initialInfoItem = CPListItem(
|
|
42
42
|
text: titleText,
|
|
43
43
|
detailText: subtitleText,
|
|
44
44
|
image: UIImage(systemName: "music.note"),
|
|
45
45
|
accessoryImage: nil,
|
|
46
46
|
accessoryType: .none
|
|
47
47
|
)
|
|
48
|
+
initialInfoItem.isEnabled = false
|
|
48
49
|
|
|
49
|
-
let
|
|
50
|
-
|
|
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],
|
|
50
|
+
let initialSection = CPListSection(
|
|
51
|
+
items: [initialInfoItem],
|
|
59
52
|
header: nil,
|
|
60
53
|
sectionIndexTitle: nil
|
|
61
54
|
)
|
|
62
55
|
|
|
63
56
|
template = CPListTemplate(
|
|
64
57
|
title: "Now Playing",
|
|
65
|
-
sections: [
|
|
58
|
+
sections: [initialSection],
|
|
66
59
|
assistantCellConfiguration: nil,
|
|
67
60
|
id: config.id
|
|
68
61
|
)
|
|
@@ -71,6 +64,7 @@ class NowPlayingTemplate: AutoPlayTemplate {
|
|
|
71
64
|
guard let self = self else { return }
|
|
72
65
|
NowPlayingSessionManager.shared.ensureSessionActive()
|
|
73
66
|
self.setupRemoteCommandCenter()
|
|
67
|
+
self.updatePlayerUI()
|
|
74
68
|
self.updateNowPlayingInfo()
|
|
75
69
|
self.isSetupComplete = true
|
|
76
70
|
|
|
@@ -85,6 +79,10 @@ class NowPlayingTemplate: AutoPlayTemplate {
|
|
|
85
79
|
private func updatePlayerUI() {
|
|
86
80
|
let titleText = Parser.parseText(text: config.title) ?? "Now Playing"
|
|
87
81
|
let subtitleText = config.subtitle.flatMap { Parser.parseText(text: $0) } ?? ""
|
|
82
|
+
let elapsedText = formatTime(currentElapsedTime)
|
|
83
|
+
let totalText = currentDuration > 0 ? formatTime(currentDuration) : "--:--"
|
|
84
|
+
let stateText = config.isPlaying ? "Oynatiliyor" : "Duraklatildi"
|
|
85
|
+
let stateIcon = config.isPlaying ? "play.circle.fill" : "pause.circle.fill"
|
|
88
86
|
|
|
89
87
|
let infoItem = CPListItem(
|
|
90
88
|
text: titleText,
|
|
@@ -93,34 +91,101 @@ class NowPlayingTemplate: AutoPlayTemplate {
|
|
|
93
91
|
accessoryImage: nil,
|
|
94
92
|
accessoryType: .none
|
|
95
93
|
)
|
|
94
|
+
infoItem.isEnabled = false
|
|
95
|
+
|
|
96
|
+
let timingItem = CPListItem(
|
|
97
|
+
text: "\(elapsedText) / \(totalText)",
|
|
98
|
+
detailText: stateText,
|
|
99
|
+
image: UIImage(systemName: stateIcon),
|
|
100
|
+
accessoryImage: nil,
|
|
101
|
+
accessoryType: .none
|
|
102
|
+
)
|
|
103
|
+
timingItem.isEnabled = false
|
|
96
104
|
|
|
97
|
-
let
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
let total = currentDuration > 0 ? formatTime(currentDuration) : "--:--"
|
|
102
|
-
statusText = "Playing \(elapsed) / \(total)"
|
|
103
|
-
statusIcon = "play.circle.fill"
|
|
105
|
+
let progressPercent: Int
|
|
106
|
+
if currentDuration > 0 {
|
|
107
|
+
let ratio = max(0.0, min(currentElapsedTime / currentDuration, 1.0))
|
|
108
|
+
progressPercent = Int(ratio * 100.0)
|
|
104
109
|
} else {
|
|
105
|
-
|
|
106
|
-
|
|
110
|
+
progressPercent = 0
|
|
111
|
+
}
|
|
112
|
+
let progressItem = CPListItem(
|
|
113
|
+
text: "Ilerleme \(progressPercent)%",
|
|
114
|
+
detailText: progressBarText(elapsed: currentElapsedTime, duration: currentDuration),
|
|
115
|
+
image: UIImage(systemName: "waveform.path.ecg"),
|
|
116
|
+
accessoryImage: nil,
|
|
117
|
+
accessoryType: .none
|
|
118
|
+
)
|
|
119
|
+
progressItem.isEnabled = false
|
|
120
|
+
|
|
121
|
+
let previousItem = CPListItem(
|
|
122
|
+
text: "Onceki Bolum",
|
|
123
|
+
detailText: nil,
|
|
124
|
+
image: UIImage(systemName: "backward.fill"),
|
|
125
|
+
accessoryImage: nil,
|
|
126
|
+
accessoryType: .none
|
|
127
|
+
)
|
|
128
|
+
previousItem.isEnabled = config.onPreviousTrack != nil
|
|
129
|
+
previousItem.handler = { [weak self] _, completion in
|
|
130
|
+
self?.config.onPreviousTrack?()
|
|
131
|
+
completion()
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
let playPauseItem = CPListItem(
|
|
135
|
+
text: config.isPlaying ? "Duraklat" : "Oynat",
|
|
136
|
+
detailText: nil,
|
|
137
|
+
image: UIImage(systemName: config.isPlaying ? "pause.fill" : "play.fill"),
|
|
138
|
+
accessoryImage: nil,
|
|
139
|
+
accessoryType: .none
|
|
140
|
+
)
|
|
141
|
+
playPauseItem.handler = { [weak self] _, completion in
|
|
142
|
+
guard let self = self else {
|
|
143
|
+
completion()
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if self.config.isPlaying {
|
|
148
|
+
self.pauseAudio()
|
|
149
|
+
self.config.onPause?()
|
|
150
|
+
} else {
|
|
151
|
+
self.resumeAudio()
|
|
152
|
+
self.config.onPlay?()
|
|
153
|
+
}
|
|
154
|
+
completion()
|
|
107
155
|
}
|
|
108
156
|
|
|
109
|
-
let
|
|
110
|
-
text:
|
|
157
|
+
let nextItem = CPListItem(
|
|
158
|
+
text: "Sonraki Bolum",
|
|
111
159
|
detailText: nil,
|
|
112
|
-
image: UIImage(systemName:
|
|
160
|
+
image: UIImage(systemName: "forward.fill"),
|
|
113
161
|
accessoryImage: nil,
|
|
114
162
|
accessoryType: .none
|
|
115
163
|
)
|
|
164
|
+
nextItem.isEnabled = config.onNextTrack != nil
|
|
165
|
+
nextItem.handler = { [weak self] _, completion in
|
|
166
|
+
self?.config.onNextTrack?()
|
|
167
|
+
completion()
|
|
168
|
+
}
|
|
116
169
|
|
|
117
|
-
let
|
|
118
|
-
items: [infoItem
|
|
170
|
+
let infoSection = CPListSection(
|
|
171
|
+
items: [infoItem],
|
|
119
172
|
header: nil,
|
|
120
173
|
sectionIndexTitle: nil
|
|
121
174
|
)
|
|
122
175
|
|
|
123
|
-
|
|
176
|
+
let timelineSection = CPListSection(
|
|
177
|
+
items: [timingItem, progressItem],
|
|
178
|
+
header: nil,
|
|
179
|
+
sectionIndexTitle: nil
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
let controlSection = CPListSection(
|
|
183
|
+
items: [previousItem, playPauseItem, nextItem],
|
|
184
|
+
header: nil,
|
|
185
|
+
sectionIndexTitle: nil
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
template.updateSections([infoSection, timelineSection, controlSection])
|
|
124
189
|
}
|
|
125
190
|
|
|
126
191
|
private func formatTime(_ seconds: Double) -> String {
|
|
@@ -130,6 +195,19 @@ class NowPlayingTemplate: AutoPlayTemplate {
|
|
|
130
195
|
return String(format: "%d:%02d", mins, secs)
|
|
131
196
|
}
|
|
132
197
|
|
|
198
|
+
private func progressBarText(elapsed: Double, duration: Double) -> String {
|
|
199
|
+
let totalBars = 16
|
|
200
|
+
guard duration > 0, !duration.isNaN, !duration.isInfinite else {
|
|
201
|
+
return "[----------------]"
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
let clamped = max(0.0, min(elapsed / duration, 1.0))
|
|
205
|
+
let filledCount = Int((clamped * Double(totalBars)).rounded(.towardZero))
|
|
206
|
+
let filled = String(repeating: "#", count: filledCount)
|
|
207
|
+
let empty = String(repeating: "-", count: max(totalBars - filledCount, 0))
|
|
208
|
+
return "[\(filled)\(empty)]"
|
|
209
|
+
}
|
|
210
|
+
|
|
133
211
|
// MARK: - Native Audio Playback
|
|
134
212
|
|
|
135
213
|
@MainActor
|