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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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.
|
|
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",
|