Skip to content

Commit 30a8df3

Browse files
committed
feat(android): implement getTracksByArtistAsync
1 parent 5674cd1 commit 30a8df3

File tree

7 files changed

+334
-40
lines changed

7 files changed

+334
-40
lines changed

android/src/main/java/com/musiclibrary/MusicLibraryModule.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import com.musiclibrary.utils.ModuleUtils.throwUnlessPermissionsGranted
1010
import com.musiclibrary.utils.ModuleUtils.withModuleScope
1111
import com.musiclibrary.tracks.GetTracks
1212
import com.musiclibrary.tracks.GetTracksByAlbum
13+
import com.musiclibrary.tracks.GetTracksByArtist
1314
import com.musiclibrary.albums.GetAlbums
1415
import com.musiclibrary.albums.GetAlbumsByArtist
1516
import com.musiclibrary.artists.GetArtists
@@ -50,6 +51,15 @@ class MusicLibraryModule(reactContext: ReactApplicationContext) :
5051
}
5152
}
5253

54+
override fun getTracksByArtistAsync(artistId: String, options: ReadableMap, promise: Promise) {
55+
throwUnlessPermissionsGranted(reactApplicationContext, isWrite = false) {
56+
withModuleScope(promise) {
57+
GetTracksByArtist(reactApplicationContext, artistId, options, promise)
58+
.execute()
59+
}
60+
}
61+
}
62+
5363
override fun getAlbumsAsync(options: ReadableMap, promise: Promise) {
5464
throwUnlessPermissionsGranted(reactApplicationContext, isWrite = false) {
5565
withModuleScope(promise) {
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.musiclibrary.tracks
2+
3+
import android.content.Context
4+
import com.facebook.react.bridge.Promise
5+
import com.facebook.react.bridge.ReadableMap
6+
import com.musiclibrary.models.AssetsOptions
7+
import com.musiclibrary.utils.DataConverter
8+
import com.musiclibrary.utils.ReadableMapMapper.toAssetsOptions
9+
10+
internal class GetTracksByArtist(
11+
private val context: Context,
12+
private val artistId: String,
13+
private val options: ReadableMap,
14+
private val promise: Promise
15+
) {
16+
17+
fun execute() {
18+
try {
19+
val contentResolver = context.contentResolver
20+
val assetsOptions = options.toAssetsOptions()
21+
val result = GetTracksByArtistQuery.getTracksByArtist(contentResolver, artistId, assetsOptions)
22+
23+
// Convert result to React Native bridge format
24+
val resultMap = DataConverter.paginatedResultToWritableMap(result) { track ->
25+
DataConverter.trackToWritableMap(track)
26+
}
27+
28+
promise.resolve(resultMap)
29+
} catch (e: Exception) {
30+
promise.reject("QUERY_ERROR", "Failed to query tracks by artist: ${e.message}", e)
31+
}
32+
}
33+
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
package com.musiclibrary.tracks
2+
3+
import android.content.ContentResolver
4+
import android.provider.MediaStore
5+
import com.musiclibrary.models.*
6+
7+
object GetTracksByArtistQuery {
8+
fun getTracksByArtist(
9+
contentResolver: ContentResolver,
10+
artistId: String,
11+
options: AssetsOptions,
12+
): PaginatedResult<Track> {
13+
val projection = arrayOf(
14+
MediaStore.Audio.Media._ID,
15+
MediaStore.Audio.Media.TITLE,
16+
MediaStore.Audio.Media.ARTIST,
17+
MediaStore.Audio.Media.ALBUM,
18+
MediaStore.Audio.Media.DURATION,
19+
MediaStore.Audio.Media.DATA,
20+
MediaStore.Audio.Media.DATE_ADDED,
21+
MediaStore.Audio.Media.SIZE,
22+
MediaStore.Audio.Media.TRACK,
23+
)
24+
25+
val selection = "${MediaStore.Audio.Media.ARTIST_ID} = ? AND ${MediaStore.Audio.Media.IS_MUSIC} = 1 AND ${MediaStore.Audio.Media.DURATION} > 0"
26+
val selectionArgs = arrayOf(artistId)
27+
val sortOrder = "${MediaStore.Audio.Media.ALBUM} ASC, ${MediaStore.Audio.Media.TRACK} ASC, ${MediaStore.Audio.Media.TITLE} ASC"
28+
29+
val cursor = contentResolver.query(
30+
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
31+
projection,
32+
selection,
33+
selectionArgs,
34+
sortOrder
35+
) ?: throw RuntimeException("Failed to query MediaStore: cursor is null")
36+
37+
val tracks = mutableListOf<Track>()
38+
var hasNextPage: Boolean
39+
var endCursor: String? = null
40+
val totalCount = cursor.count
41+
42+
cursor.use { c ->
43+
val idColumn = c.getColumnIndexOrThrow(MediaStore.Audio.Media._ID)
44+
val titleColumn = c.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE)
45+
val artistColumn = c.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST)
46+
val albumColumn = c.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM)
47+
val durationColumn = c.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION)
48+
val dataColumn = c.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA)
49+
val dateAddedColumn = c.getColumnIndexOrThrow(MediaStore.Audio.Media.DATE_ADDED)
50+
val sizeColumn = c.getColumnIndexOrThrow(MediaStore.Audio.Media.SIZE)
51+
52+
// Jump to the specified start position
53+
val foundAfter = if (options.after == null) {
54+
cursor.moveToFirst() // Move to the first record
55+
true
56+
} else {
57+
var found = false
58+
if (cursor.moveToFirst()) {
59+
do {
60+
val id = cursor.getLong(idColumn).toString()
61+
if (id == options.after) {
62+
found = true
63+
break
64+
}
65+
} while (cursor.moveToNext())
66+
}
67+
// Move to the next record after the specified after if found
68+
found && cursor.moveToNext()
69+
}
70+
71+
var count = 0
72+
val maxItems = options.first.coerceAtMost(1000) // Limit the maximum number of queries
73+
74+
while (foundAfter && count < maxItems) {
75+
try {
76+
val id = c.getLong(idColumn)
77+
val data = c.getString(dataColumn) ?: ""
78+
79+
// Skip invalid data
80+
if (data.isEmpty()) {
81+
continue
82+
}
83+
84+
val title = c.getString(titleColumn) ?: ""
85+
val artist = c.getString(artistColumn)
86+
val album = c.getString(albumColumn)
87+
val duration = c.getLong(durationColumn) / 1000.0 // Convert to seconds
88+
val dateAdded = c.getLong(dateAddedColumn)
89+
val fileSize = c.getLong(sizeColumn)
90+
val artworkUri = "content://media/external/audio/media/${id}/albumart"
91+
92+
// Create a Track
93+
val track = Track(
94+
id = id.toString(),
95+
title = title,
96+
artist = artist,
97+
artwork = artworkUri,
98+
album = album,
99+
duration = duration,
100+
url = "file://$data",
101+
createdAt = dateAdded * 1000, // Convert to milliseconds
102+
modifiedAt = dateAdded * 1000, // Convert to milliseconds
103+
fileSize = fileSize
104+
)
105+
106+
tracks.add(track)
107+
endCursor = id.toString()
108+
count++
109+
} catch (e: Exception) {
110+
continue
111+
}
112+
113+
if (!cursor.moveToNext()) break
114+
}
115+
116+
// Check if there are more data
117+
hasNextPage = !c.isAfterLast
118+
}
119+
120+
return PaginatedResult(
121+
items = tracks,
122+
hasNextPage = hasNextPage,
123+
endCursor = endCursor,
124+
totalCount = totalCount
125+
)
126+
}
127+
}

0 commit comments

Comments
 (0)