@@ -6,7 +6,19 @@ import android.os.Build
6
6
import android.provider.MediaStore
7
7
import android.util.Base64
8
8
import com.musiclibrary.models.*
9
-
9
+ import java.util.concurrent.Executors
10
+ import java.util.concurrent.Future
11
+ import java.util.concurrent.ThreadPoolExecutor
12
+ import java.util.concurrent.TimeUnit
13
+
14
+ /* *
15
+ * GetTracksQuery
16
+ *
17
+ * 1. Split the query and metadata extraction of audio files
18
+ * 2. Use multi-threaded parallel processing of metadata extraction to significantly improve performance
19
+ * 3. Reasonably control the size of the thread pool to avoid resource waste
20
+ * 4. Add timeout mechanism to avoid a single file blocking the entire process
21
+ */
10
22
object GetTracksQuery {
11
23
12
24
fun getTracks (
@@ -37,7 +49,7 @@ object GetTracksQuery {
37
49
sortOrder
38
50
) ? : throw RuntimeException (" Failed to query MediaStore: cursor is null" )
39
51
40
- val tracks = mutableListOf<Track >()
52
+ val basicTracks = mutableListOf<Track >()
41
53
var hasNextPage = false
42
54
var endCursor: String? = null
43
55
val totalCount = cursor.count
@@ -86,23 +98,21 @@ object GetTracksQuery {
86
98
continue
87
99
}
88
100
89
- val meta = getTrackMeta(data)
90
-
91
- val track = Track (
101
+ val basicTrack = Track (
92
102
id = id.toString(),
93
103
title = title,
94
- cover = meta.cover ,
104
+ cover = " " ,
95
105
artist = artist,
96
106
album = album,
97
- genre = meta.genre ,
107
+ genre = " " ,
98
108
duration = duration,
99
109
uri = " file://$data " ,
100
110
createdAt = dateAdded * 1000 , // Convert to milliseconds
101
111
modifiedAt = dateAdded * 1000 , // Convert to milliseconds
102
112
fileSize = fileSize
103
113
)
104
114
105
- tracks .add(track )
115
+ basicTracks .add(basicTrack )
106
116
endCursor = id.toString()
107
117
count++
108
118
} catch (e: Exception ) {
@@ -115,6 +125,9 @@ object GetTracksQuery {
115
125
hasNextPage = c.moveToNext()
116
126
}
117
127
128
+ // Use multi-threaded parallel processing of metadata extraction
129
+ val tracks = processTracksMetadata(basicTracks)
130
+
118
131
return PaginatedResult (
119
132
items = tracks,
120
133
hasNextPage = hasNextPage,
@@ -140,6 +153,93 @@ object GetTracksQuery {
140
153
return conditions.joinToString(" AND " )
141
154
}
142
155
156
+ /* *
157
+ * Use multi-threaded parallel processing of metadata extraction
158
+ *
159
+ * @param basicTracks Track list
160
+ * @return Track list with complete metadata
161
+ */
162
+ private fun processTracksMetadata (basicTracks : List <Track >): List <Track > {
163
+ if (basicTracks.isEmpty()) {
164
+ return emptyList()
165
+ }
166
+
167
+ // Create thread pool, optimized for I/O intensive tasks
168
+ val threadCount = minOf(16 , maxOf(4 , Runtime .getRuntime().availableProcessors() * 4 ))
169
+ val executor = Executors .newFixedThreadPool(threadCount) as ThreadPoolExecutor
170
+
171
+ // Pre-warm thread pool
172
+ executor.prestartAllCoreThreads()
173
+
174
+ try {
175
+ // Create Future task list
176
+ val futures = mutableListOf<Future <Track >>()
177
+
178
+ // Create asynchronous task for each track
179
+ for (basicTrack in basicTracks) {
180
+ val future = executor.submit<Track > {
181
+ try {
182
+ val meta = getTrackMeta(basicTrack.uri.replace(" file://" , " " ))
183
+ Track (
184
+ id = basicTrack.id,
185
+ title = basicTrack.title,
186
+ cover = meta.cover,
187
+ artist = basicTrack.artist,
188
+ album = basicTrack.album,
189
+ genre = meta.genre,
190
+ duration = basicTrack.duration,
191
+ uri = basicTrack.uri,
192
+ createdAt = basicTrack.createdAt,
193
+ modifiedAt = basicTrack.modifiedAt,
194
+ fileSize = basicTrack.fileSize
195
+ )
196
+ } catch (e: Exception ) {
197
+ // If metadata extraction fails, return track without metadata
198
+ Track (
199
+ id = basicTrack.id,
200
+ title = basicTrack.title,
201
+ cover = " " ,
202
+ artist = basicTrack.artist,
203
+ album = basicTrack.album,
204
+ genre = " " ,
205
+ duration = basicTrack.duration,
206
+ uri = basicTrack.uri,
207
+ createdAt = basicTrack.createdAt,
208
+ modifiedAt = basicTrack.modifiedAt,
209
+ fileSize = basicTrack.fileSize
210
+ )
211
+ }
212
+ }
213
+ futures.add(future)
214
+ }
215
+
216
+ // Collect all results
217
+ val tracks = mutableListOf<Track >()
218
+ for (future in futures) {
219
+ try {
220
+ // Set shorter timeout (maximum 2 seconds per file)
221
+ val track = future.get(2 , TimeUnit .SECONDS )
222
+ tracks.add(track)
223
+ } catch (e: Exception ) {
224
+ // If the task times out or fails, skip this track
225
+ continue
226
+ }
227
+ }
228
+
229
+ return tracks
230
+ } finally {
231
+ // Ensure the thread pool is closed correctly
232
+ executor.shutdown()
233
+ try {
234
+ if (! executor.awaitTermination(30 , TimeUnit .SECONDS )) {
235
+ executor.shutdownNow()
236
+ }
237
+ } catch (e: InterruptedException ) {
238
+ executor.shutdownNow()
239
+ }
240
+ }
241
+ }
242
+
143
243
private fun buildSelectionArgs (options : AssetsOptions ): Array <String >? {
144
244
val args = mutableListOf<String >()
145
245
@@ -189,7 +289,7 @@ object GetTracksQuery {
189
289
190
290
private fun getTrackMeta (data : String ): TrackMeta {
191
291
val retriever = MediaMetadataRetriever ()
192
- try {
292
+ return try {
193
293
retriever.setDataSource(data)
194
294
195
295
val genre = retriever.extractMetadata(MediaMetadataRetriever .METADATA_KEY_GENRE ) ? : " "
@@ -201,9 +301,9 @@ object GetTracksQuery {
201
301
" "
202
302
}
203
303
204
- return TrackMeta (cover, genre)
304
+ TrackMeta (cover, genre)
205
305
} catch (e: Exception ) {
206
- return TrackMeta (" " , " " )
306
+ TrackMeta (" " , " " )
207
307
} finally {
208
308
retriever.release()
209
309
}
0 commit comments