@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.
@@ -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,7 @@
1
+ package com.turbosongs
2
+
3
+ import com.facebook.react.bridge.ReactApplicationContext
4
+
5
+ abstract class TurboSongsSpec internal constructor(context: ReactApplicationContext) :
6
+ NativeTurboSongsSpec(context) {
7
+ }
@@ -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
+ }
@@ -0,0 +1,12 @@
1
+
2
+ #ifdef RCT_NEW_ARCH_ENABLED
3
+ #import "RNTurboSongsSpec.h"
4
+
5
+ @interface TurboSongs : NSObject <NativeTurboSongsSpec>
6
+ #else
7
+ #import <React/RCTBridgeModule.h>
8
+
9
+ @interface TurboSongs : NSObject <RCTBridgeModule>
10
+ #endif
11
+
12
+ @end