@andycui/react-native-get-music-files 3.0.1
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.
- package/LICENSE +20 -0
- package/README.md +226 -0
- package/android/build.gradle +127 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/AndroidManifestNew.xml +2 -0
- package/android/src/main/java/com/turbosongs/BundlePair.kt +95 -0
- package/android/src/main/java/com/turbosongs/TurboSongsModule.kt +498 -0
- package/android/src/main/java/com/turbosongs/TurboSongsPackage.kt +35 -0
- package/android/src/newarch/TurboSongsSpec.kt +7 -0
- package/android/src/oldarch/TurboSongsSpec.kt +14 -0
- package/ios/TurboSongs.h +12 -0
- package/ios/TurboSongs.mm +420 -0
- package/package.json +171 -0
- package/react-native-turbo-songs.podspec +41 -0
- package/src/NativeTurboSongs.ts +60 -0
- package/src/index.tsx +70 -0
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
package com.turbosongs
|
|
2
|
+
|
|
3
|
+
import android.content.ContentResolver
|
|
4
|
+
import android.content.pm.PackageManager
|
|
5
|
+
import android.database.Cursor
|
|
6
|
+
import android.graphics.Bitmap
|
|
7
|
+
import android.graphics.BitmapFactory
|
|
8
|
+
import android.media.MediaMetadataRetriever
|
|
9
|
+
import android.os.Build
|
|
10
|
+
import android.os.Bundle
|
|
11
|
+
import android.provider.MediaStore
|
|
12
|
+
import android.provider.MediaStore.Audio.Media
|
|
13
|
+
import android.util.Base64
|
|
14
|
+
import androidx.core.content.ContextCompat
|
|
15
|
+
import com.facebook.react.bridge.Arguments
|
|
16
|
+
import com.facebook.react.bridge.Promise
|
|
17
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
18
|
+
import com.facebook.react.bridge.ReactMethod
|
|
19
|
+
import com.facebook.react.bridge.ReadableMap
|
|
20
|
+
import com.facebook.react.bridge.WritableArray
|
|
21
|
+
import com.facebook.react.bridge.WritableMap
|
|
22
|
+
import com.facebook.react.bridge.WritableNativeArray
|
|
23
|
+
import com.facebook.react.bridge.WritableNativeMap
|
|
24
|
+
import java.io.ByteArrayOutputStream
|
|
25
|
+
import java.io.IOException
|
|
26
|
+
import java.util.ArrayList
|
|
27
|
+
import java.util.Collections.addAll
|
|
28
|
+
|
|
29
|
+
class TurboSongsModule internal constructor(context: ReactApplicationContext) :
|
|
30
|
+
TurboSongsSpec(context) {
|
|
31
|
+
|
|
32
|
+
override fun getName(): String {
|
|
33
|
+
return NAME
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private fun hasPermissions(): Boolean {
|
|
37
|
+
val readPermission = ContextCompat.checkSelfPermission(reactApplicationContext.applicationContext, android.Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
|
|
38
|
+
val audioPermission = ContextCompat.checkSelfPermission(reactApplicationContext.applicationContext, android.Manifest.permission.READ_MEDIA_AUDIO) == PackageManager.PERMISSION_GRANTED
|
|
39
|
+
|
|
40
|
+
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU){
|
|
41
|
+
return audioPermission
|
|
42
|
+
}
|
|
43
|
+
return readPermission
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Example method
|
|
47
|
+
// See https://reactnative.dev/docs/native-modules-android
|
|
48
|
+
@ReactMethod
|
|
49
|
+
override fun getAll(options: ReadableMap, promise: Promise) {
|
|
50
|
+
if(!hasPermissions()){
|
|
51
|
+
promise.reject("Permissions denied","Permissions denied")
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
val projection = arrayOf<String>(
|
|
56
|
+
MediaStore.Audio.AudioColumns.TITLE,
|
|
57
|
+
MediaStore.Audio.AudioColumns.ALBUM,
|
|
58
|
+
MediaStore.Audio.AudioColumns.ARTIST,
|
|
59
|
+
MediaStore.Audio.AudioColumns.DURATION,
|
|
60
|
+
MediaStore.Audio.AudioColumns.GENRE,
|
|
61
|
+
MediaStore.Audio.Media.DATA
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
val limit = when (options.hasKey("limit")) {
|
|
65
|
+
true -> options.getInt("limit")
|
|
66
|
+
else -> 20
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
val offset = when (options.hasKey("offset")) {
|
|
70
|
+
true -> options.getInt("offset")
|
|
71
|
+
else -> 0
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
val minSongDuration = when (options.hasKey("minSongDuration")) {
|
|
75
|
+
true -> options.getInt("minSongDuration")
|
|
76
|
+
else -> 1000
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
val sortOrder = when {
|
|
80
|
+
options.hasKey("sortOrder") -> options.getString("sortOrder")
|
|
81
|
+
else -> "ASC"
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
val sortColumn = when {
|
|
85
|
+
options.hasKey("sortBy") -> options.getString("sortBy")
|
|
86
|
+
else -> "TITLE"
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Bellow android 0
|
|
90
|
+
val selection = MediaStore.Audio.Media.IS_MUSIC + " != 0" + " AND " + MediaStore.Audio.Media.DURATION + " >= " + minSongDuration
|
|
91
|
+
// Android 0 afterwards
|
|
92
|
+
val bundleSelection = bundleOf(
|
|
93
|
+
ContentResolver.QUERY_ARG_SQL_SELECTION bundleTo selection,
|
|
94
|
+
ContentResolver.QUERY_ARG_LIMIT bundleTo limit,
|
|
95
|
+
ContentResolver.QUERY_ARG_OFFSET bundleTo offset,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
// ORDER BY $orderBy
|
|
99
|
+
val resultSet = parseCursor(projection,
|
|
100
|
+
"$selection LIMIT $limit OFFSET $offset", bundleSelection, null
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
val songList: ArrayList<Any> = createSongCursor(resultSet, options).toArrayList()
|
|
104
|
+
|
|
105
|
+
// manual sort
|
|
106
|
+
songList.sortWith(compareBy {
|
|
107
|
+
when (sortColumn) {
|
|
108
|
+
"DURATION" -> "duration"
|
|
109
|
+
"TITLE" -> "title"
|
|
110
|
+
"ARTIST" -> "artist"
|
|
111
|
+
"ALBUM" -> "album"
|
|
112
|
+
"GENRE" -> "genre"
|
|
113
|
+
"DATE_ADDED" -> "date_added"
|
|
114
|
+
// Add more cases for other columns if needed
|
|
115
|
+
else -> "title"
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
// If the sortOrder is descending, reverse the sorted list
|
|
120
|
+
if (sortOrder == "DESC") {
|
|
121
|
+
songList.reverse()
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Convert ArrayList to WritableArray
|
|
125
|
+
val writableArray: WritableArray = Arguments.createArray()
|
|
126
|
+
|
|
127
|
+
for (item in songList) {
|
|
128
|
+
val writableMap: WritableMap = Arguments.createMap()
|
|
129
|
+
|
|
130
|
+
when (item) {
|
|
131
|
+
is Map<*, *> -> {
|
|
132
|
+
// If the item is a Map, assume it's a HashMap<String, Any>
|
|
133
|
+
for ((key, value) in item.entries) {
|
|
134
|
+
when (value) {
|
|
135
|
+
is String -> writableMap.putString(key.toString(), value)
|
|
136
|
+
is Int -> writableMap.putInt(key.toString(), value)
|
|
137
|
+
is Double -> writableMap.putDouble(key.toString(), value)
|
|
138
|
+
is Boolean -> writableMap.putBoolean(key.toString(), value)
|
|
139
|
+
// Handle other types as needed
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// Add more cases if there are other types in your ArrayList
|
|
144
|
+
else -> {
|
|
145
|
+
// Handle other types or provide a default behavior
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
writableArray.pushMap(writableMap)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
promise.resolve(writableArray)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
@ReactMethod
|
|
156
|
+
override fun getAlbums(options: ReadableMap, promise : Promise) {
|
|
157
|
+
|
|
158
|
+
if(!hasPermissions()){
|
|
159
|
+
promise.reject("Permissions denied","Permissions denied")
|
|
160
|
+
return
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if(!options.hasKey("artist")){
|
|
164
|
+
promise.reject("Artist name must not be empty", "Artist name must not be empty")
|
|
165
|
+
return
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
var projection = arrayOf<String>(
|
|
169
|
+
MediaStore.Audio.Albums.ALBUM_ID,
|
|
170
|
+
MediaStore.Audio.Albums.ALBUM,
|
|
171
|
+
MediaStore.Audio.Albums.ARTIST,
|
|
172
|
+
MediaStore.Audio.AudioColumns.NUM_TRACKS,
|
|
173
|
+
MediaStore.Audio.Media.DATA
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
val artist = options?.getString("artist")
|
|
177
|
+
|
|
178
|
+
val limit = when (options.hasKey("limit")) {
|
|
179
|
+
true -> options.getInt("limit")
|
|
180
|
+
else -> 20
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
val offset = when (options.hasKey("offset")) {
|
|
184
|
+
true -> options.getInt("offset")
|
|
185
|
+
else -> 0
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
val sortOrder = when {
|
|
189
|
+
options.hasKey("sortOrder") -> options.getString("sortOrder")
|
|
190
|
+
else -> "ASC"
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
val sortColumn = when {
|
|
194
|
+
options.hasKey("sortBy") -> options.getString("sortBy")
|
|
195
|
+
else -> "TITLE"
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Bellow android 0
|
|
199
|
+
val selection = MediaStore.Audio.Media.IS_MUSIC + " != 0" + " AND " + MediaStore.Audio.Albums.ARTIST + " LIKE ?"
|
|
200
|
+
val whereArgs = arrayOf("%$artist%")
|
|
201
|
+
// Android 0 afterwards
|
|
202
|
+
val bundleSelection = bundleOf(
|
|
203
|
+
ContentResolver.QUERY_ARG_SQL_SELECTION bundleTo selection,
|
|
204
|
+
ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS bundleTo whereArgs,
|
|
205
|
+
ContentResolver.QUERY_ARG_LIMIT bundleTo limit,
|
|
206
|
+
ContentResolver.QUERY_ARG_OFFSET bundleTo offset,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
val resultSet = parseCursor(projection,
|
|
210
|
+
"$selection LIMIT $limit OFFSET $offset", bundleSelection, whereArgs
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
val songList: ArrayList<Any> = createAlbumCursor(resultSet, options).toArrayList()
|
|
214
|
+
|
|
215
|
+
// manual sort
|
|
216
|
+
songList.sortWith(compareBy {
|
|
217
|
+
when (sortColumn) {
|
|
218
|
+
"DURATION" -> "duration"
|
|
219
|
+
"TITLE" -> "title"
|
|
220
|
+
"ARTIST" -> "artist"
|
|
221
|
+
"ALBUM" -> "album"
|
|
222
|
+
"GENRE" -> "genre"
|
|
223
|
+
"DATE_ADDED" -> "date_added"
|
|
224
|
+
// Add more cases for other columns if needed
|
|
225
|
+
else -> "title"
|
|
226
|
+
}
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
// If the sortOrder is descending, reverse the sorted list
|
|
230
|
+
if (sortOrder == "DESC") {
|
|
231
|
+
songList.reverse()
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Convert ArrayList to WritableArray
|
|
235
|
+
val writableArray: WritableArray = Arguments.createArray()
|
|
236
|
+
|
|
237
|
+
for (item in songList) {
|
|
238
|
+
val writableMap: WritableMap = Arguments.createMap()
|
|
239
|
+
|
|
240
|
+
when (item) {
|
|
241
|
+
is Map<*, *> -> {
|
|
242
|
+
// If the item is a Map, assume it's a HashMap<String, Any>
|
|
243
|
+
for ((key, value) in item.entries) {
|
|
244
|
+
when (value) {
|
|
245
|
+
is String -> writableMap.putString(key.toString(), value)
|
|
246
|
+
is Int -> writableMap.putInt(key.toString(), value)
|
|
247
|
+
is Double -> writableMap.putDouble(key.toString(), value)
|
|
248
|
+
is Boolean -> writableMap.putBoolean(key.toString(), value)
|
|
249
|
+
// Handle other types as needed
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
// Add more cases if there are other types in your ArrayList
|
|
254
|
+
else -> {
|
|
255
|
+
// Handle other types or provide a default behavior
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
writableArray.pushMap(writableMap)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
promise.resolve(writableArray)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
@ReactMethod
|
|
266
|
+
override fun search(options: ReadableMap, promise : Promise) {
|
|
267
|
+
|
|
268
|
+
if(!hasPermissions()){
|
|
269
|
+
promise.reject("Permissions denied","Permissions denied")
|
|
270
|
+
return
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if(!options.hasKey("searchBy")){
|
|
274
|
+
promise.reject("Search param must not be empty", "Search param must not be empty")
|
|
275
|
+
return
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
var projection = arrayOf<String>(
|
|
279
|
+
MediaStore.Audio.AudioColumns.TITLE,
|
|
280
|
+
MediaStore.Audio.AudioColumns.ALBUM,
|
|
281
|
+
MediaStore.Audio.AudioColumns.ARTIST,
|
|
282
|
+
MediaStore.Audio.AudioColumns.DURATION,
|
|
283
|
+
MediaStore.Audio.AudioColumns.GENRE,
|
|
284
|
+
MediaStore.Audio.Media.DATA
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
val searchBy = options.getString("searchBy")
|
|
288
|
+
|
|
289
|
+
val limit = when (options.hasKey("limit")) {
|
|
290
|
+
true -> options.getInt("limit")
|
|
291
|
+
else -> 20
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
val offset = when (options.hasKey("offset")) {
|
|
295
|
+
true -> options.getInt("offset")
|
|
296
|
+
else -> 0
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
val sortOrder = when {
|
|
300
|
+
options.hasKey("sortOrder") -> options.getString("sortOrder")
|
|
301
|
+
else -> "ASC"
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
val sortColumn = when {
|
|
305
|
+
options.hasKey("sortBy") -> options.getString("sortBy")
|
|
306
|
+
else -> "TITLE"
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
val query = "AND ("+ MediaStore.Audio.Albums.ARTIST + " LIKE ? OR " + MediaStore.Audio.Albums.ALBUM + " LIKE ? OR " + MediaStore.Audio.Media.TITLE + " LIKE ?)"
|
|
310
|
+
|
|
311
|
+
val whereArgs = arrayOf("%$searchBy%","%$searchBy%","%$searchBy%")
|
|
312
|
+
|
|
313
|
+
// Bellow android 0
|
|
314
|
+
val selection = MediaStore.Audio.Media.IS_MUSIC + " != 0"
|
|
315
|
+
// Android 0 afterwards
|
|
316
|
+
val bundleSelection = bundleOf(
|
|
317
|
+
ContentResolver.QUERY_ARG_SQL_SELECTION bundleTo "$selection $query",
|
|
318
|
+
ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS bundleTo whereArgs,
|
|
319
|
+
ContentResolver.QUERY_ARG_LIMIT bundleTo limit,
|
|
320
|
+
ContentResolver.QUERY_ARG_OFFSET bundleTo offset,
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
val resultSet = parseCursor(projection,
|
|
324
|
+
"$selection $query LIMIT $limit OFFSET $offset", bundleSelection, whereArgs
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
val songList: ArrayList<Any> = createSongCursor(resultSet, options).toArrayList()
|
|
328
|
+
|
|
329
|
+
// manual sort
|
|
330
|
+
songList.sortWith(compareBy {
|
|
331
|
+
when (sortColumn) {
|
|
332
|
+
"DURATION" -> "duration"
|
|
333
|
+
"TITLE" -> "title"
|
|
334
|
+
"ARTIST" -> "artist"
|
|
335
|
+
"ALBUM" -> "album"
|
|
336
|
+
"GENRE" -> "genre"
|
|
337
|
+
"DATE_ADDED" -> "date_added"
|
|
338
|
+
// Add more cases for other columns if needed
|
|
339
|
+
else -> "title"
|
|
340
|
+
}
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
// If the sortOrder is descending, reverse the sorted list
|
|
344
|
+
if (sortOrder == "DESC") {
|
|
345
|
+
songList.reverse()
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Convert ArrayList to WritableArray
|
|
349
|
+
val writableArray: WritableArray = Arguments.createArray()
|
|
350
|
+
|
|
351
|
+
for (item in songList) {
|
|
352
|
+
val writableMap: WritableMap = Arguments.createMap()
|
|
353
|
+
|
|
354
|
+
when (item) {
|
|
355
|
+
is Map<*, *> -> {
|
|
356
|
+
// If the item is a Map, assume it's a HashMap<String, Any>
|
|
357
|
+
for ((key, value) in item.entries) {
|
|
358
|
+
when (value) {
|
|
359
|
+
is String -> writableMap.putString(key.toString(), value)
|
|
360
|
+
is Int -> writableMap.putInt(key.toString(), value)
|
|
361
|
+
is Double -> writableMap.putDouble(key.toString(), value)
|
|
362
|
+
is Boolean -> writableMap.putBoolean(key.toString(), value)
|
|
363
|
+
// Handle other types as needed
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
// Add more cases if there are other types in your ArrayList
|
|
368
|
+
else -> {
|
|
369
|
+
// Handle other types or provide a default behavior
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
writableArray.pushMap(writableMap)
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
promise.resolve(writableArray)
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
private fun parseCursor(projection: Array<String>, selection: String, bundleSelection: Bundle, searchParams: Array<String>?): Cursor? {
|
|
380
|
+
val uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
|
|
381
|
+
val resultSet : Cursor? = when {
|
|
382
|
+
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O -> {
|
|
383
|
+
reactApplicationContext.contentResolver.query(uri, projection, bundleSelection, null)
|
|
384
|
+
} else -> {
|
|
385
|
+
reactApplicationContext.contentResolver.query(uri, projection, selection, searchParams, null)
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return resultSet
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
private fun createAlbumCursor(resultSet: Cursor?, options: ReadableMap?): WritableArray {
|
|
392
|
+
val items: WritableArray = WritableNativeArray()
|
|
393
|
+
var coverQuality = 100
|
|
394
|
+
var needCover = false
|
|
395
|
+
if (resultSet != null) {
|
|
396
|
+
if(resultSet.moveToFirst()) {
|
|
397
|
+
if(options != null && options.hasKey("coverQuality")){
|
|
398
|
+
coverQuality = options.getInt("coverQuality")
|
|
399
|
+
needCover = coverQuality > 0
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
do {
|
|
403
|
+
val id = resultSet.getString(0)
|
|
404
|
+
val album = resultSet.getString(1)
|
|
405
|
+
val artist = resultSet.getString(2)
|
|
406
|
+
val numberOfSongs = resultSet.getString(3)
|
|
407
|
+
val path = resultSet.getString(4)
|
|
408
|
+
|
|
409
|
+
var song: WritableMap = WritableNativeMap();
|
|
410
|
+
song.putString("id", id)
|
|
411
|
+
song.putString("album", album)
|
|
412
|
+
song.putString("artist", artist)
|
|
413
|
+
song.putString("numberOfSongs", numberOfSongs)
|
|
414
|
+
|
|
415
|
+
if (needCover) {
|
|
416
|
+
song.putString("cover", getCover(path, coverQuality))
|
|
417
|
+
} else {
|
|
418
|
+
song.putString("cover", "")
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
items.pushMap(song)
|
|
422
|
+
} while (resultSet.moveToNext())
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
resultSet?.close()
|
|
427
|
+
return items
|
|
428
|
+
}
|
|
429
|
+
private fun createSongCursor(resultSet: Cursor?, options: ReadableMap?) : WritableArray {
|
|
430
|
+
val items: WritableArray = WritableNativeArray()
|
|
431
|
+
var coverQuality = 100
|
|
432
|
+
var needCover = false
|
|
433
|
+
if (resultSet != null) {
|
|
434
|
+
if(resultSet.moveToFirst()) {
|
|
435
|
+
|
|
436
|
+
if(options != null && options.hasKey("coverQuality")){
|
|
437
|
+
coverQuality = options.getInt("coverQuality")
|
|
438
|
+
needCover = coverQuality > 0
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
do {
|
|
442
|
+
val title = resultSet.getString(0)
|
|
443
|
+
val album = resultSet.getString(1)
|
|
444
|
+
val artist = resultSet.getString(2)
|
|
445
|
+
val duration = resultSet.getString(3)
|
|
446
|
+
val genre = resultSet.getString(4)
|
|
447
|
+
val path = resultSet.getString(5)
|
|
448
|
+
|
|
449
|
+
var song: WritableMap = WritableNativeMap();
|
|
450
|
+
song.putString("url", path)
|
|
451
|
+
song.putString("title", title)
|
|
452
|
+
song.putString("album", album)
|
|
453
|
+
song.putString("artist", artist)
|
|
454
|
+
song.putInt("duration", Integer.parseInt(duration))
|
|
455
|
+
song.putString("genre", genre)
|
|
456
|
+
|
|
457
|
+
if (needCover) {
|
|
458
|
+
var thumbnail = getCover(path, coverQuality)
|
|
459
|
+
song.putString("cover", thumbnail)
|
|
460
|
+
} else {
|
|
461
|
+
song.putString("cover", "")
|
|
462
|
+
}
|
|
463
|
+
items.pushMap(song)
|
|
464
|
+
} while (resultSet.moveToNext())
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
resultSet?.close()
|
|
469
|
+
return items
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
private fun getCover(path: String?, quality: Int) : String {
|
|
473
|
+
if(path == null) return ""
|
|
474
|
+
|
|
475
|
+
val mmr = MediaMetadataRetriever()
|
|
476
|
+
mmr.setDataSource(path)
|
|
477
|
+
|
|
478
|
+
return try {
|
|
479
|
+
val cover = mmr.embeddedPicture ?: return ""
|
|
480
|
+
if(cover.isEmpty()) return ""
|
|
481
|
+
|
|
482
|
+
var byteArrayOutputStream = ByteArrayOutputStream()
|
|
483
|
+
var bitmap = BitmapFactory.decodeByteArray(cover, 0, cover.size)
|
|
484
|
+
|
|
485
|
+
if(bitmap != null){
|
|
486
|
+
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, byteArrayOutputStream)
|
|
487
|
+
return "data:image/jpeg;base64," + Base64.encodeToString(byteArrayOutputStream.toByteArray(), Base64.DEFAULT)
|
|
488
|
+
}
|
|
489
|
+
return ""
|
|
490
|
+
} catch (e: IOException){
|
|
491
|
+
""
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
companion object {
|
|
496
|
+
const val NAME = "TurboSongs"
|
|
497
|
+
}
|
|
498
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
package com.turbosongs
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.TurboReactPackage
|
|
4
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
5
|
+
import com.facebook.react.bridge.NativeModule
|
|
6
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
7
|
+
import com.facebook.react.module.model.ReactModuleInfo
|
|
8
|
+
import java.util.HashMap
|
|
9
|
+
|
|
10
|
+
class TurboSongsPackage : TurboReactPackage() {
|
|
11
|
+
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
|
|
12
|
+
return if (name == TurboSongsModule.NAME) {
|
|
13
|
+
TurboSongsModule(reactContext)
|
|
14
|
+
} else {
|
|
15
|
+
null
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
|
|
20
|
+
return ReactModuleInfoProvider {
|
|
21
|
+
val moduleInfos: MutableMap<String, ReactModuleInfo> = HashMap()
|
|
22
|
+
val isTurboModule: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
|
|
23
|
+
moduleInfos[TurboSongsModule.NAME] = ReactModuleInfo(
|
|
24
|
+
TurboSongsModule.NAME,
|
|
25
|
+
TurboSongsModule.NAME,
|
|
26
|
+
false, // canOverrideExistingModule
|
|
27
|
+
false, // needsEagerInit
|
|
28
|
+
true, // hasConstants
|
|
29
|
+
false, // isCxxModule
|
|
30
|
+
isTurboModule // isTurboModule
|
|
31
|
+
)
|
|
32
|
+
moduleInfos
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
package com.turbosongs
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
4
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
5
|
+
import com.facebook.react.bridge.Promise
|
|
6
|
+
import com.facebook.react.bridge.ReadableMap
|
|
7
|
+
|
|
8
|
+
abstract class TurboSongsSpec internal constructor(context: ReactApplicationContext) :
|
|
9
|
+
ReactContextBaseJavaModule(context) {
|
|
10
|
+
|
|
11
|
+
abstract fun getAll(options: ReadableMap, promise: Promise)
|
|
12
|
+
abstract fun getAlbums(options: ReadableMap, promise: Promise)
|
|
13
|
+
abstract fun search(options: ReadableMap, promise: Promise)
|
|
14
|
+
}
|
package/ios/TurboSongs.h
ADDED