@neoskola/auto-play 0.3.3 → 0.3.4

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.
@@ -19,6 +19,9 @@ class NowPlayingTemplate: AutoPlayTemplate {
19
19
  private var didFinishObserver: NSObjectProtocol?
20
20
  private var statusObservation: NSKeyValueObservation?
21
21
  private var completionFired = false
22
+ private var downloadTask: URLSessionDownloadTask?
23
+ private var localAudioFileURL: URL?
24
+ private var pendingStartFrom: Double = 0
22
25
 
23
26
  var autoDismissMs: Double? {
24
27
  return config.autoDismissMs
@@ -60,6 +63,7 @@ class NowPlayingTemplate: AutoPlayTemplate {
60
63
  cleanupPlayer()
61
64
  completionFired = false
62
65
  lastReportedSecond = Int(startFrom)
66
+ pendingStartFrom = startFrom
63
67
 
64
68
  guard let audioURL = URL(string: url) else {
65
69
  print("[NowPlayingTemplate] Invalid audio URL: \(url)")
@@ -71,8 +75,50 @@ class NowPlayingTemplate: AutoPlayTemplate {
71
75
  updateNowPlayingInfo()
72
76
  MPNowPlayingInfoCenter.default().playbackState = .playing
73
77
 
74
- // Direct AVPlayer streaming — URL now has correct headers from backend
75
- let asset = AVURLAsset(url: audioURL)
78
+ print("[NowPlayingTemplate] Downloading audio: \(url)")
79
+
80
+ // Download file first, then play from local — R2/Cloudflare CDN streaming
81
+ // causes FigFilePlayer errors with AVPlayer, local playback is reliable
82
+ downloadTask = URLSession.shared.downloadTask(with: audioURL) { [weak self] tempURL, response, error in
83
+ guard let self = self else { return }
84
+
85
+ if let error = error {
86
+ print("[NowPlayingTemplate] Download failed: \(error.localizedDescription)")
87
+ return
88
+ }
89
+
90
+ guard let tempURL = tempURL else {
91
+ print("[NowPlayingTemplate] Download returned no file")
92
+ return
93
+ }
94
+
95
+ // Move to a persistent temp location (URLSession deletes the file after this block)
96
+ let localURL = FileManager.default.temporaryDirectory
97
+ .appendingPathComponent("carplay_audio_\(UUID().uuidString).mp3")
98
+
99
+ do {
100
+ if let oldFile = self.localAudioFileURL {
101
+ try? FileManager.default.removeItem(at: oldFile)
102
+ }
103
+ try FileManager.default.moveItem(at: tempURL, to: localURL)
104
+ self.localAudioFileURL = localURL
105
+ print("[NowPlayingTemplate] Audio downloaded to: \(localURL.lastPathComponent)")
106
+ } catch {
107
+ print("[NowPlayingTemplate] Failed to move downloaded file: \(error)")
108
+ return
109
+ }
110
+
111
+ DispatchQueue.main.async { [weak self] in
112
+ self?.startPlaybackFromLocalFile(localURL: localURL, startFrom: self?.pendingStartFrom ?? 0)
113
+ }
114
+ }
115
+ downloadTask?.resume()
116
+
117
+ return true
118
+ }
119
+
120
+ private func startPlaybackFromLocalFile(localURL: URL, startFrom: Double) {
121
+ let asset = AVURLAsset(url: localURL)
76
122
  playerItem = AVPlayerItem(asset: asset)
77
123
 
78
124
  // KVO: detect duration as soon as asset loads (critical for progress bar)
@@ -96,14 +142,11 @@ class NowPlayingTemplate: AutoPlayTemplate {
96
142
 
97
143
  player = AVPlayer(playerItem: playerItem)
98
144
 
99
- // Seek to start position
100
145
  if startFrom > 0 {
101
146
  let time = CMTime(seconds: startFrom, preferredTimescale: 600)
102
147
  player?.seek(to: time)
103
148
  }
104
149
 
105
- player?.play()
106
-
107
150
  // Periodic time observer (every 1 second) for MPNowPlayingInfoCenter updates
108
151
  let interval = CMTime(seconds: 1.0, preferredTimescale: 600)
109
152
  timeObserver = player?.addPeriodicTimeObserver(
@@ -113,14 +156,6 @@ class NowPlayingTemplate: AutoPlayTemplate {
113
156
  self?.handleTimeUpdate(time: time)
114
157
  }
115
158
 
116
- // Progress report timer (every 30 seconds) — calls JS callback for backend reporting
117
- progressReportTimer = Timer.scheduledTimer(
118
- withTimeInterval: 30.0,
119
- repeats: true
120
- ) { [weak self] _ in
121
- self?.reportProgress()
122
- }
123
-
124
159
  // Playback finished notification
125
160
  didFinishObserver = NotificationCenter.default.addObserver(
126
161
  forName: .AVPlayerItemDidPlayToEndTime,
@@ -130,8 +165,18 @@ class NowPlayingTemplate: AutoPlayTemplate {
130
165
  self?.handlePlaybackFinished()
131
166
  }
132
167
 
133
- print("[NowPlayingTemplate] Streaming playback started: \(url)")
134
- return true
168
+ // Progress report timer (every 30 seconds) — calls JS callback for backend reporting
169
+ progressReportTimer = Timer.scheduledTimer(
170
+ withTimeInterval: 30.0,
171
+ repeats: true
172
+ ) { [weak self] _ in
173
+ self?.reportProgress()
174
+ }
175
+
176
+ player?.play()
177
+ MPNowPlayingInfoCenter.default().playbackState = .playing
178
+
179
+ print("[NowPlayingTemplate] Playback started from local file")
135
180
  }
136
181
 
137
182
  private func handleTimeUpdate(time: CMTime) {
@@ -230,6 +275,8 @@ class NowPlayingTemplate: AutoPlayTemplate {
230
275
  }
231
276
 
232
277
  private func cleanupPlayer() {
278
+ downloadTask?.cancel()
279
+ downloadTask = nil
233
280
  statusObservation?.invalidate()
234
281
  statusObservation = nil
235
282
  if let timeObserver = timeObserver {
@@ -245,6 +292,10 @@ class NowPlayingTemplate: AutoPlayTemplate {
245
292
  player?.pause()
246
293
  player = nil
247
294
  playerItem = nil
295
+ if let localFile = localAudioFileURL {
296
+ try? FileManager.default.removeItem(at: localFile)
297
+ localAudioFileURL = nil
298
+ }
248
299
  }
249
300
 
250
301
  // MARK: - CarPlay UI
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neoskola/auto-play",
3
- "version": "0.3.3",
3
+ "version": "0.3.4",
4
4
  "description": "Android Auto and Apple CarPlay for react-native",
5
5
  "main": "lib/index",
6
6
  "module": "lib/index",