react-native-nitro-player 0.5.1 → 0.5.2

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.
@@ -57,7 +57,8 @@ final class DownloadFileManager {
57
57
  func saveDownloadedFile(
58
58
  from temporaryLocation: URL, trackId: String, storageLocation: StorageLocation,
59
59
  originalURL: String? = nil,
60
- suggestedFilename: String? = nil
60
+ suggestedFilename: String? = nil,
61
+ httpResponse: HTTPURLResponse? = nil
61
62
  ) -> String? {
62
63
  print("🎯 DownloadFileManager: saveDownloadedFile called for trackId=\(trackId)")
63
64
  print(" From: \(temporaryLocation.path)")
@@ -68,21 +69,12 @@ final class DownloadFileManager {
68
69
  storageLocation == .private ? privateDownloadsDirectory : publicDownloadsDirectory
69
70
  print(" Destination directory: \(destinationDirectory.path)")
70
71
 
71
- // Determine file extension
72
- var fileExtension = "mp3" // Default fallback
73
-
74
- if let suggestedFilename = suggestedFilename, !suggestedFilename.isEmpty {
75
- let url = URL(fileURLWithPath: suggestedFilename)
76
- let pathExtension = url.pathExtension.lowercased()
77
- if !pathExtension.isEmpty {
78
- fileExtension = pathExtension
79
- }
80
- } else if let originalURL = originalURL, let url = URL(string: originalURL) {
81
- let pathExtension = url.pathExtension.lowercased()
82
- if !pathExtension.isEmpty {
83
- fileExtension = pathExtension
84
- }
85
- }
72
+ // Determine file extension using headers first, then URL path, then default
73
+ let fileExtension = Self.resolveFileExtension(
74
+ httpResponse: httpResponse,
75
+ suggestedFilename: suggestedFilename,
76
+ originalURL: originalURL
77
+ )
86
78
  print(" File extension: \(fileExtension)")
87
79
 
88
80
  let fileName = "\(trackId).\(fileExtension)"
@@ -113,6 +105,95 @@ final class DownloadFileManager {
113
105
  }
114
106
  }
115
107
 
108
+ // MARK: - Extension Resolution
109
+
110
+ private static let mimeTypeToExtension: [String: String] = [
111
+ "audio/mpeg": "mp3",
112
+ "audio/mp3": "mp3",
113
+ "audio/mp4": "m4a",
114
+ "audio/m4a": "m4a",
115
+ "audio/x-m4a": "m4a",
116
+ "audio/aac": "aac",
117
+ "audio/ogg": "ogg",
118
+ "audio/flac": "flac",
119
+ "audio/x-flac": "flac",
120
+ "audio/wav": "wav",
121
+ "audio/x-wav": "wav",
122
+ "audio/webm": "webm",
123
+ "audio/opus": "opus",
124
+ ]
125
+
126
+ /// Resolves the audio file extension from HTTP response headers, suggested filename, or URL.
127
+ /// Priority: Content-Disposition filename → Content-Type MIME → suggestedFilename → URL path ext → "mp3"
128
+ private static func resolveFileExtension(
129
+ httpResponse: HTTPURLResponse?,
130
+ suggestedFilename: String?,
131
+ originalURL: String?
132
+ ) -> String {
133
+ // 1. Content-Disposition: attachment; filename="track.mp3"
134
+ if let disposition = httpResponse?.value(forHTTPHeaderField: "Content-Disposition") {
135
+ if let ext = extensionFromContentDisposition(disposition), !ext.isEmpty {
136
+ print(" [ExtResolve] Content-Disposition → .\(ext)")
137
+ return ext
138
+ }
139
+ }
140
+
141
+ // 2. Content-Type MIME type
142
+ if let contentType = httpResponse?.value(forHTTPHeaderField: "Content-Type") {
143
+ let mime = contentType.split(separator: ";").first.map(String.init)?.trimmingCharacters(in: .whitespaces) ?? contentType
144
+ if let ext = mimeTypeToExtension[mime.lowercased()] {
145
+ print(" [ExtResolve] Content-Type '\(mime)' → .\(ext)")
146
+ return ext
147
+ }
148
+ }
149
+
150
+ // 3. Suggested filename from URLSession
151
+ if let name = suggestedFilename, !name.isEmpty {
152
+ let ext = URL(fileURLWithPath: name).pathExtension.lowercased()
153
+ if !ext.isEmpty && isAudioExtension(ext) {
154
+ print(" [ExtResolve] suggestedFilename → .\(ext)")
155
+ return ext
156
+ }
157
+ }
158
+
159
+ // 4. URL path extension (only if it looks like an audio format, not e.g. ".view")
160
+ if let urlString = originalURL, let url = URL(string: urlString) {
161
+ let ext = url.pathExtension.lowercased()
162
+ if !ext.isEmpty && isAudioExtension(ext) {
163
+ print(" [ExtResolve] URL path ext → .\(ext)")
164
+ return ext
165
+ }
166
+ }
167
+
168
+ print(" [ExtResolve] fallback → .mp3")
169
+ return "mp3"
170
+ }
171
+
172
+ private static func extensionFromContentDisposition(_ disposition: String) -> String? {
173
+ // Match: filename="foo.mp3" or filename=foo.mp3
174
+ let patterns = [
175
+ #"filename\*?=(?:UTF-8'')?\"?([^\";\r\n]+)\"?"#,
176
+ ]
177
+ for pattern in patterns {
178
+ if let regex = try? NSRegularExpression(pattern: pattern, options: .caseInsensitive) {
179
+ let range = NSRange(disposition.startIndex..., in: disposition)
180
+ if let match = regex.firstMatch(in: disposition, range: range),
181
+ let filenameRange = Range(match.range(at: 1), in: disposition)
182
+ {
183
+ let filename = String(disposition[filenameRange]).trimmingCharacters(in: .whitespaces)
184
+ let ext = URL(fileURLWithPath: filename).pathExtension.lowercased()
185
+ if !ext.isEmpty { return ext }
186
+ }
187
+ }
188
+ }
189
+ return nil
190
+ }
191
+
192
+ private static func isAudioExtension(_ ext: String) -> Bool {
193
+ let audioExtensions: Set<String> = ["mp3", "m4a", "aac", "ogg", "flac", "wav", "webm", "opus", "mp4"]
194
+ return audioExtensions.contains(ext)
195
+ }
196
+
116
197
  func deleteFile(at path: String) {
117
198
  do {
118
199
  if fileManager.fileExists(atPath: path) {
@@ -682,15 +682,17 @@ extension DownloadManagerCore: URLSessionDownloadDelegate {
682
682
  (self.config.storageLocation ?? .private, self.trackMetadata[trackId]?.url)
683
683
  }
684
684
 
685
- // Get suggested filename from response
685
+ // Get suggested filename and HTTP headers from response
686
686
  let suggestedFilename = downloadTask.response?.suggestedFilename
687
+ let httpResponse = downloadTask.response as? HTTPURLResponse
687
688
 
688
689
  let destinationPath = DownloadFileManager.shared.saveDownloadedFile(
689
690
  from: location,
690
691
  trackId: trackId,
691
692
  storageLocation: storageLocation,
692
693
  originalURL: originalURL,
693
- suggestedFilename: suggestedFilename
694
+ suggestedFilename: suggestedFilename,
695
+ httpResponse: httpResponse
694
696
  )
695
697
 
696
698
  // Now handle the rest asynchronously
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-nitro-player",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "description": "A powerful audio player library for React Native with playlist management, playback controls, and support for Android Auto and CarPlay",
5
5
  "main": "lib/index",
6
6
  "module": "lib/index",