@neoskola/auto-play 0.3.19 → 0.3.20

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
- // Custom player screen using CPListTemplate instead of CPNowPlayingTemplate.shared
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 infoItem = CPListItem(
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 statusItem = CPListItem(
50
- text: "Yükleniyor...",
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: [section],
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 ? "Playing" : "Paused"
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 statusText: String
98
- let statusIcon: String
99
- if config.isPlaying {
100
- let elapsed = formatTime(currentElapsedTime)
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
- statusText = "Paused"
106
- statusIcon = "pause.circle.fill"
110
+ progressPercent = 0
111
+ }
112
+ let progressItem = CPListItem(
113
+ text: "Progress \(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: "Previous Lesson",
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 ? "Pause" : "Play",
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 statusItem = CPListItem(
110
- text: statusText,
157
+ let nextItem = CPListItem(
158
+ text: "Next Lesson",
111
159
  detailText: nil,
112
- image: UIImage(systemName: statusIcon),
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 section = CPListSection(
118
- items: [infoItem, statusItem],
170
+ let infoSection = CPListSection(
171
+ items: [infoItem],
119
172
  header: nil,
120
173
  sectionIndexTitle: nil
121
174
  )
122
175
 
123
- template.updateSections([section])
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neoskola/auto-play",
3
- "version": "0.3.19",
3
+ "version": "0.3.20",
4
4
  "description": "Android Auto and Apple CarPlay for react-native",
5
5
  "main": "lib/index",
6
6
  "module": "lib/index",