@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,420 @@
|
|
|
1
|
+
#import "TurboSongs.h"
|
|
2
|
+
#import <MediaPlayer/MediaPlayer.h>
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@implementation TurboSongs
|
|
6
|
+
RCT_EXPORT_MODULE()
|
|
7
|
+
|
|
8
|
+
// Example method
|
|
9
|
+
// See // https://reactnative.dev/docs/native-modules-ios
|
|
10
|
+
RCT_EXPORT_METHOD(getAll:(NSDictionary *)options
|
|
11
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
12
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
13
|
+
{
|
|
14
|
+
if([MPMediaLibrary authorizationStatus] != MPMediaLibraryAuthorizationStatusAuthorized){
|
|
15
|
+
reject(@"Permission denied",@"Permission denied",0);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
NSInteger limit = [options objectForKey:@"limit"] ? [options[@"limit"] integerValue] : 20;
|
|
20
|
+
NSInteger offset = [options objectForKey:@"offset"] ? [options[@"offset"] integerValue] : 0;
|
|
21
|
+
NSInteger coverQty = [options objectForKey:@"coverQuality"] ? [options[@"coverQuality"] integerValue] : 100;
|
|
22
|
+
NSInteger minSongDuration = [options objectForKey:@"minSongDuration"] ? [options[@"minSongDuration"] integerValue] / 1000 : 100;
|
|
23
|
+
|
|
24
|
+
BOOL needCover = [options objectForKey:@"coverQuality"] != nil || [options[@"coverQuality"] integerValue] == 0;
|
|
25
|
+
|
|
26
|
+
id sortOrderValue = [options objectForKey:@"sortOrder"];
|
|
27
|
+
|
|
28
|
+
NSString *sortOrder;
|
|
29
|
+
if ([sortOrderValue isKindOfClass:[NSString class]]) {
|
|
30
|
+
sortOrder = sortOrderValue;
|
|
31
|
+
} else {
|
|
32
|
+
sortOrder = @"ASC";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
id sortByValue = [options objectForKey:@"sortBy"];
|
|
36
|
+
|
|
37
|
+
NSString *sortBy;
|
|
38
|
+
if ([sortByValue isKindOfClass:[NSString class]]) {
|
|
39
|
+
sortBy = sortByValue;
|
|
40
|
+
} else {
|
|
41
|
+
sortBy = @"TITLE";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
NSMutableArray *mutableSongsToSerialize = [NSMutableArray array];
|
|
45
|
+
|
|
46
|
+
MPMediaQuery *mediaQuery = [MPMediaQuery songsQuery]; // run a query on song media type
|
|
47
|
+
[mediaQuery addFilterPredicate:[MPMediaPropertyPredicate predicateWithValue:@(NO)
|
|
48
|
+
forProperty:MPMediaItemPropertyIsCloudItem]]; // ensure what we retrieve is on device
|
|
49
|
+
|
|
50
|
+
NSArray *allMediaItems = [mediaQuery items];
|
|
51
|
+
|
|
52
|
+
// Define the range for the limited subset
|
|
53
|
+
NSRange range = NSMakeRange(offset, MIN(limit, allMediaItems.count - offset));
|
|
54
|
+
|
|
55
|
+
// Get the subset of media items within the specified range
|
|
56
|
+
NSArray<MPMediaItem *> *limitedMediaItems = [allMediaItems subarrayWithRange:range];
|
|
57
|
+
|
|
58
|
+
// Sort items
|
|
59
|
+
NSArray<MPMediaItem *> *sortedMediaItems = [self sortMediaItems:limitedMediaItems byKey: sortBy sortOrder: sortOrder];
|
|
60
|
+
|
|
61
|
+
for (MPMediaItem *song in sortedMediaItems) {
|
|
62
|
+
NSDictionary *songDictionary = [NSMutableDictionary dictionary];
|
|
63
|
+
|
|
64
|
+
NSString *durationStr = [song valueForProperty: MPMediaItemPropertyPlaybackDuration];
|
|
65
|
+
NSInteger durationInt = [durationStr integerValue];
|
|
66
|
+
|
|
67
|
+
NSURL *assetURL = [song valueForProperty:MPMediaItemPropertyAssetURL];
|
|
68
|
+
AVAsset *asset = [AVAsset assetWithURL:assetURL];
|
|
69
|
+
|
|
70
|
+
if ([asset hasProtectedContent] == NO and durationInt >= minSongDuration) {
|
|
71
|
+
|
|
72
|
+
NSString *title = [song valueForProperty: MPMediaItemPropertyTitle];
|
|
73
|
+
NSString *albumTitle = [song valueForProperty: MPMediaItemPropertyAlbumTitle];
|
|
74
|
+
NSString *albumArtist = [song valueForProperty: MPMediaItemPropertyAlbumArtist];
|
|
75
|
+
NSString *genre = [song valueForProperty: MPMediaItemPropertyGenre]; // filterable
|
|
76
|
+
|
|
77
|
+
[songDictionary setValue:[NSString stringWithString:assetURL.absoluteString] forKey:@"url"];
|
|
78
|
+
[songDictionary setValue:[NSString stringWithString:title] forKey:@"title"];
|
|
79
|
+
[songDictionary setValue:[NSString stringWithString:albumTitle] forKey:@"album"];
|
|
80
|
+
[songDictionary setValue:[NSString stringWithString:albumArtist] forKey:@"artist"];
|
|
81
|
+
[songDictionary setValue:[NSNumber numberWithInt:durationInt * 1000] forKey:@"duration"];
|
|
82
|
+
[songDictionary setValue:[NSString stringWithString:genre] forKey:@"genre"];
|
|
83
|
+
|
|
84
|
+
if (needCover) {
|
|
85
|
+
MPMediaItemArtwork *artwork = [song valueForProperty: MPMediaItemPropertyArtwork];
|
|
86
|
+
if (artwork != nil) {
|
|
87
|
+
UIImage *image = [artwork imageWithSize:CGSizeMake(coverQty, coverQty)];
|
|
88
|
+
// http://www.12qw.ch/2014/12/tooltip-decoding-base64-images-with-chrome-data-url/
|
|
89
|
+
// http://stackoverflow.com/a/510444/185771
|
|
90
|
+
NSString *base64 = [NSString stringWithFormat:@"%@%@", @"data:image/jpeg;base64,", [self imageToNSString:image]];
|
|
91
|
+
[songDictionary setValue:base64 forKey:@"cover"];
|
|
92
|
+
} else {
|
|
93
|
+
[songDictionary setValue:@"" forKey:@"cover"];
|
|
94
|
+
}
|
|
95
|
+
} else {
|
|
96
|
+
[songDictionary setValue:@"" forKey:@"cover"];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
[mutableSongsToSerialize addObject:songDictionary];
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
resolve(mutableSongsToSerialize);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
RCT_EXPORT_METHOD(getAlbums:(NSDictionary *)options
|
|
107
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
108
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
109
|
+
{
|
|
110
|
+
|
|
111
|
+
if([MPMediaLibrary authorizationStatus] != MPMediaLibraryAuthorizationStatusAuthorized){
|
|
112
|
+
reject(@"Permission denied",@"Permission denied",0);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
NSInteger limit = [options objectForKey:@"limit"] ? [options[@"limit"] integerValue] : 20;
|
|
117
|
+
NSInteger offset = [options objectForKey:@"offset"] ? [options[@"offset"] integerValue] : 0;
|
|
118
|
+
NSInteger coverQty = [options objectForKey:@"coverQuality"] ? [options[@"coverQuality"] integerValue] : 100;
|
|
119
|
+
NSString *artist = options[@"artist"];
|
|
120
|
+
|
|
121
|
+
BOOL needCover = [options objectForKey:@"coverQuality"] != nil || [options[@"coverQuality"] integerValue] == 0;
|
|
122
|
+
|
|
123
|
+
id sortOrderValue = [options objectForKey:@"sortOrder"];
|
|
124
|
+
|
|
125
|
+
NSString *sortOrder;
|
|
126
|
+
if ([sortOrderValue isKindOfClass:[NSString class]]) {
|
|
127
|
+
sortOrder = sortOrderValue;
|
|
128
|
+
} else {
|
|
129
|
+
sortOrder = @"ASC";
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
id sortByValue = [options objectForKey:@"sortBy"];
|
|
133
|
+
|
|
134
|
+
NSString *sortBy;
|
|
135
|
+
if ([sortByValue isKindOfClass:[NSString class]]) {
|
|
136
|
+
sortBy = sortByValue;
|
|
137
|
+
} else {
|
|
138
|
+
sortBy = @"TITLE";
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if(artist.length == 0){
|
|
142
|
+
reject(@"Artist name must not be empty",@"Artist name must not be empty",0);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
NSMutableArray *mutableSongsToSerialize = [NSMutableArray array];
|
|
147
|
+
|
|
148
|
+
MPMediaQuery *mediaQuery = [MPMediaQuery albumsQuery]; // run a query on song media type
|
|
149
|
+
[mediaQuery addFilterPredicate:[MPMediaPropertyPredicate predicateWithValue:@(NO)
|
|
150
|
+
forProperty:MPMediaItemPropertyIsCloudItem]]; // ensure what we retrieve is on device
|
|
151
|
+
// this is returning all songs no matter what
|
|
152
|
+
[mediaQuery addFilterPredicate:[
|
|
153
|
+
MPMediaPropertyPredicate
|
|
154
|
+
predicateWithValue:artist
|
|
155
|
+
forProperty:MPMediaItemPropertyArtist
|
|
156
|
+
comparisonType: MPMediaPredicateComparisonContains
|
|
157
|
+
]
|
|
158
|
+
];
|
|
159
|
+
|
|
160
|
+
NSArray *allMediaItems = [mediaQuery items];
|
|
161
|
+
|
|
162
|
+
// Define the range for the limited subset
|
|
163
|
+
NSRange range = NSMakeRange(offset, MIN(limit, allMediaItems.count - offset));
|
|
164
|
+
|
|
165
|
+
// Get the subset of media items within the specified range
|
|
166
|
+
NSArray<MPMediaItem *> *limitedMediaItems = [allMediaItems subarrayWithRange:range];
|
|
167
|
+
|
|
168
|
+
// Sort items
|
|
169
|
+
NSArray<MPMediaItem *> *sortedMediaItems = [self sortMediaItems:limitedMediaItems byKey: sortBy sortOrder: sortOrder];
|
|
170
|
+
|
|
171
|
+
for (MPMediaItem *album in sortedMediaItems) {
|
|
172
|
+
NSDictionary *songDictionary = [NSMutableDictionary dictionary];
|
|
173
|
+
|
|
174
|
+
NSURL *assetURL = [album valueForProperty:MPMediaItemPropertyAssetURL];
|
|
175
|
+
AVAsset *asset = [AVAsset assetWithURL:assetURL];
|
|
176
|
+
|
|
177
|
+
if ([asset hasProtectedContent] == NO) {
|
|
178
|
+
|
|
179
|
+
NSString *albumTitle = [album valueForProperty: MPMediaItemPropertyAlbumTitle]; // filterable
|
|
180
|
+
NSString *albumArtist = [album valueForProperty: MPMediaItemPropertyAlbumArtist]; //
|
|
181
|
+
NSString *numberOfSongs = [album valueForProperty: MPMediaItemPropertyAlbumTrackCount]; //
|
|
182
|
+
|
|
183
|
+
[songDictionary setValue:[NSString stringWithString:assetURL.absoluteString] forKey:@"url"];
|
|
184
|
+
[songDictionary setValue:[NSString stringWithString:albumTitle] forKey:@"album"];
|
|
185
|
+
[songDictionary setValue:[NSString stringWithString:albumArtist] forKey:@"artist"];
|
|
186
|
+
[songDictionary setValue:[NSString stringWithString:numberOfSongs] forKey:@"numberOfSongs"];
|
|
187
|
+
|
|
188
|
+
if (needCover) {
|
|
189
|
+
MPMediaItemArtwork *artwork = [album valueForProperty: MPMediaItemPropertyArtwork];
|
|
190
|
+
if (artwork != nil) {
|
|
191
|
+
UIImage *image = [artwork imageWithSize:CGSizeMake(coverQty, coverQty)];
|
|
192
|
+
// http://www.12qw.ch/2014/12/tooltip-decoding-base64-images-with-chrome-data-url/
|
|
193
|
+
// http://stackoverflow.com/a/510444/185771
|
|
194
|
+
NSString *base64 = [NSString stringWithFormat:@"%@%@", @"data:image/jpeg;base64,", [self imageToNSString:image]];
|
|
195
|
+
[songDictionary setValue:base64 forKey:@"cover"];
|
|
196
|
+
} else {
|
|
197
|
+
[songDictionary setValue:@"" forKey:@"cover"];
|
|
198
|
+
}
|
|
199
|
+
} else {
|
|
200
|
+
[songDictionary setValue:@"" forKey:@"cover"];
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
[mutableSongsToSerialize addObject:songDictionary];
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
resolve(mutableSongsToSerialize);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
RCT_EXPORT_METHOD(search:(NSDictionary *)options
|
|
211
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
212
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
213
|
+
{
|
|
214
|
+
if([MPMediaLibrary authorizationStatus] != MPMediaLibraryAuthorizationStatusAuthorized){
|
|
215
|
+
reject(@"Permission denied",@"Permission denied",0);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
NSInteger limit = [options objectForKey:@"limit"] ? [options[@"limit"] integerValue] : 20;
|
|
220
|
+
NSInteger offset = [options objectForKey:@"offset"] ? [options[@"offset"] integerValue] : 0;
|
|
221
|
+
NSInteger coverQty = [options objectForKey:@"coverQuality"] ? [options[@"coverQuality"] integerValue] : 100;
|
|
222
|
+
NSString *searchBy = options[@"searchBy"];
|
|
223
|
+
|
|
224
|
+
BOOL needCover = [options objectForKey:@"coverQuality"] != nil || [options[@"coverQuality"] integerValue] == 0;
|
|
225
|
+
|
|
226
|
+
id sortOrderValue = [options objectForKey:@"sortOrder"];
|
|
227
|
+
|
|
228
|
+
NSString *sortOrder;
|
|
229
|
+
if ([sortOrderValue isKindOfClass:[NSString class]]) {
|
|
230
|
+
sortOrder = sortOrderValue;
|
|
231
|
+
} else {
|
|
232
|
+
sortOrder = @"ASC";
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
id sortByValue = [options objectForKey:@"sortBy"];
|
|
236
|
+
|
|
237
|
+
NSString *sortBy;
|
|
238
|
+
if ([sortByValue isKindOfClass:[NSString class]]) {
|
|
239
|
+
sortBy = sortByValue;
|
|
240
|
+
} else {
|
|
241
|
+
sortBy = @"TITLE";
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if(searchBy.length == 0){
|
|
245
|
+
reject(@"Search param must not be empty",@"Search param must not be empty",0);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
NSMutableArray *mutableSongsToSerialize = [NSMutableArray array];
|
|
250
|
+
|
|
251
|
+
MPMediaQuery *mediaQuery = [MPMediaQuery songsQuery]; // run a query on song media type
|
|
252
|
+
[mediaQuery addFilterPredicate:[MPMediaPropertyPredicate predicateWithValue:@(NO)
|
|
253
|
+
forProperty:MPMediaItemPropertyIsCloudItem]]; // ensure what we retrieve is on device
|
|
254
|
+
|
|
255
|
+
NSPredicate *filters = [NSPredicate predicateWithFormat:@"title contains[cd] %@ OR albumTitle contains[cd] %@ OR artist contains[cd] %@", searchBy, searchBy, searchBy];
|
|
256
|
+
|
|
257
|
+
NSArray *allMediaItems = [[mediaQuery items] filteredArrayUsingPredicate:filters];
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
// Define the range for the limited subset
|
|
261
|
+
NSRange range = NSMakeRange(offset, MIN(limit, allMediaItems.count - offset));
|
|
262
|
+
|
|
263
|
+
// Get the subset of media items within the specified range
|
|
264
|
+
NSArray<MPMediaItem *> *limitedMediaItems = [allMediaItems subarrayWithRange:range];
|
|
265
|
+
|
|
266
|
+
// Sort items
|
|
267
|
+
NSArray<MPMediaItem *> *sortedMediaItems = [self sortMediaItems:limitedMediaItems byKey: sortBy sortOrder: sortOrder];
|
|
268
|
+
|
|
269
|
+
for (MPMediaItem *song in sortedMediaItems) {
|
|
270
|
+
NSDictionary *songDictionary = [NSMutableDictionary dictionary];
|
|
271
|
+
|
|
272
|
+
NSURL *assetURL = [song valueForProperty:MPMediaItemPropertyAssetURL];
|
|
273
|
+
AVAsset *asset = [AVAsset assetWithURL:assetURL];
|
|
274
|
+
|
|
275
|
+
if ([asset hasProtectedContent] == NO) {
|
|
276
|
+
|
|
277
|
+
NSString *durationStr = [song valueForProperty: MPMediaItemPropertyPlaybackDuration];
|
|
278
|
+
NSInteger durationInt = [durationStr integerValue];
|
|
279
|
+
|
|
280
|
+
NSString *title = [song valueForProperty: MPMediaItemPropertyTitle];
|
|
281
|
+
NSString *albumTitle = [song valueForProperty: MPMediaItemPropertyAlbumTitle];
|
|
282
|
+
NSString *albumArtist = [song valueForProperty: MPMediaItemPropertyAlbumArtist];
|
|
283
|
+
NSString *genre = [song valueForProperty: MPMediaItemPropertyGenre]; // filterable
|
|
284
|
+
|
|
285
|
+
[songDictionary setValue:[NSString stringWithString:assetURL.absoluteString] forKey:@"url"];
|
|
286
|
+
[songDictionary setValue:[NSString stringWithString:title] forKey:@"title"];
|
|
287
|
+
[songDictionary setValue:[NSString stringWithString:albumTitle] forKey:@"album"];
|
|
288
|
+
[songDictionary setValue:[NSString stringWithString:albumArtist] forKey:@"artist"];
|
|
289
|
+
[songDictionary setValue:[NSNumber numberWithInt:durationInt * 1000] forKey:@"duration"];
|
|
290
|
+
[songDictionary setValue:[NSString stringWithString:genre] forKey:@"genre"];
|
|
291
|
+
|
|
292
|
+
if (needCover) {
|
|
293
|
+
MPMediaItemArtwork *artwork = [song valueForProperty: MPMediaItemPropertyArtwork];
|
|
294
|
+
if (artwork != nil) {
|
|
295
|
+
UIImage *image = [artwork imageWithSize:CGSizeMake(coverQty, coverQty)];
|
|
296
|
+
// http://www.12qw.ch/2014/12/tooltip-decoding-base64-images-with-chrome-data-url/
|
|
297
|
+
// http://stackoverflow.com/a/510444/185771
|
|
298
|
+
NSString *base64 = [NSString stringWithFormat:@"%@%@", @"data:image/jpeg;base64,", [self imageToNSString:image]];
|
|
299
|
+
[songDictionary setValue:base64 forKey:@"cover"];
|
|
300
|
+
} else {
|
|
301
|
+
[songDictionary setValue:@"" forKey:@"cover"];
|
|
302
|
+
}
|
|
303
|
+
} else {
|
|
304
|
+
[songDictionary setValue:@"" forKey:@"cover"];
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
[mutableSongsToSerialize addObject:songDictionary];
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
resolve(mutableSongsToSerialize);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// http://stackoverflow.com/questions/22243854/encode-image-to-base64-get-a-invalid-base64-string-ios-using-base64encodedstri
|
|
315
|
+
- (NSString *)imageToNSString:(UIImage *)image
|
|
316
|
+
{
|
|
317
|
+
NSData *data = UIImagePNGRepresentation(image);
|
|
318
|
+
|
|
319
|
+
return [data base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed];
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
- (NSArray<MPMediaItem *> *)sortMediaItems:(NSArray<MPMediaItem *> *)mediaItems byKey:(NSString *)sortKey sortOrder:(NSString *)sortOrder {
|
|
323
|
+
NSArray<MPMediaItem *> *sortedMediaItems;
|
|
324
|
+
|
|
325
|
+
if ([sortKey isEqualToString:@"DURATION"]) {
|
|
326
|
+
sortedMediaItems = [mediaItems sortedArrayUsingComparator:^NSComparisonResult(MPMediaItem *item1, MPMediaItem *item2) {
|
|
327
|
+
NSNumber *duration1 = [item1 valueForProperty:MPMediaItemPropertyPlaybackDuration];
|
|
328
|
+
NSNumber *duration2 = [item2 valueForProperty:MPMediaItemPropertyPlaybackDuration];
|
|
329
|
+
NSComparisonResult result = [duration1 compare:duration2];
|
|
330
|
+
|
|
331
|
+
if ([sortOrder isEqualToString:@"DESC"]) {
|
|
332
|
+
return (result == NSOrderedAscending) ? NSOrderedDescending :
|
|
333
|
+
(result == NSOrderedDescending) ? NSOrderedAscending : NSOrderedSame;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return result;
|
|
337
|
+
}];
|
|
338
|
+
} else if ([sortKey isEqualToString:@"TITLE"]) {
|
|
339
|
+
sortedMediaItems = [mediaItems sortedArrayUsingComparator:^NSComparisonResult(MPMediaItem *item1, MPMediaItem *item2) {
|
|
340
|
+
NSString *title1 = [item1 valueForProperty:MPMediaItemPropertyTitle];
|
|
341
|
+
NSString *title2 = [item2 valueForProperty:MPMediaItemPropertyTitle];
|
|
342
|
+
NSComparisonResult result = [title1 compare:title2];
|
|
343
|
+
|
|
344
|
+
if ([sortOrder isEqualToString:@"DESC"]) {
|
|
345
|
+
return (result == NSOrderedAscending) ? NSOrderedDescending :
|
|
346
|
+
(result == NSOrderedDescending) ? NSOrderedAscending : NSOrderedSame;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return result;
|
|
350
|
+
}];
|
|
351
|
+
} else if ([sortKey isEqualToString:@"ARTIST"]) {
|
|
352
|
+
sortedMediaItems = [mediaItems sortedArrayUsingComparator:^NSComparisonResult(MPMediaItem *item1, MPMediaItem *item2) {
|
|
353
|
+
NSString *artist1 = [item1 valueForProperty:MPMediaItemPropertyArtist];
|
|
354
|
+
NSString *artist2 = [item2 valueForProperty:MPMediaItemPropertyArtist];
|
|
355
|
+
NSComparisonResult result = [artist1 compare:artist2];
|
|
356
|
+
|
|
357
|
+
if ([sortOrder isEqualToString:@"DESC"]) {
|
|
358
|
+
return (result == NSOrderedAscending) ? NSOrderedDescending :
|
|
359
|
+
(result == NSOrderedDescending) ? NSOrderedAscending : NSOrderedSame;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return result;
|
|
363
|
+
}];
|
|
364
|
+
} else if ([sortKey isEqualToString:@"ALBUM"]) {
|
|
365
|
+
sortedMediaItems = [mediaItems sortedArrayUsingComparator:^NSComparisonResult(MPMediaItem *item1, MPMediaItem *item2) {
|
|
366
|
+
NSString *album1 = [item1 valueForProperty:MPMediaItemPropertyAlbumTitle];
|
|
367
|
+
NSString *album2 = [item2 valueForProperty:MPMediaItemPropertyAlbumTitle];
|
|
368
|
+
NSComparisonResult result = [album1 compare:album2];
|
|
369
|
+
|
|
370
|
+
if ([sortOrder isEqualToString:@"DESC"]) {
|
|
371
|
+
return (result == NSOrderedAscending) ? NSOrderedDescending :
|
|
372
|
+
(result == NSOrderedDescending) ? NSOrderedAscending : NSOrderedSame;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return result;
|
|
376
|
+
}];
|
|
377
|
+
} else if ([sortKey isEqualToString:@"GENDER"]) {
|
|
378
|
+
sortedMediaItems = [mediaItems sortedArrayUsingComparator:^NSComparisonResult(MPMediaItem *item1, MPMediaItem *item2) {
|
|
379
|
+
NSString *gender1 = [item1 valueForProperty:MPMediaItemPropertyGenre];
|
|
380
|
+
NSString *gender2 = [item2 valueForProperty:MPMediaItemPropertyGenre];
|
|
381
|
+
NSComparisonResult result = [gender1 compare:gender2];
|
|
382
|
+
|
|
383
|
+
if ([sortOrder isEqualToString:@"DESC"]) {
|
|
384
|
+
return (result == NSOrderedAscending) ? NSOrderedDescending :
|
|
385
|
+
(result == NSOrderedDescending) ? NSOrderedAscending : NSOrderedSame;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return result;
|
|
389
|
+
}];
|
|
390
|
+
} else if ([sortKey isEqualToString:@"DATE_ADDED"]) {
|
|
391
|
+
sortedMediaItems = [mediaItems sortedArrayUsingComparator:^NSComparisonResult(MPMediaItem *item1, MPMediaItem *item2) {
|
|
392
|
+
NSString *dateAdded1 = [item1 valueForProperty:MPMediaItemPropertyDateAdded];
|
|
393
|
+
NSString *dateAdded2 = [item2 valueForProperty:MPMediaItemPropertyDateAdded];
|
|
394
|
+
NSComparisonResult result = [dateAdded1 compare:dateAdded2];
|
|
395
|
+
|
|
396
|
+
if ([sortOrder isEqualToString:@"DESC"]) {
|
|
397
|
+
return (result == NSOrderedAscending) ? NSOrderedDescending :
|
|
398
|
+
(result == NSOrderedDescending) ? NSOrderedAscending : NSOrderedSame;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return result;
|
|
402
|
+
}];
|
|
403
|
+
} else {
|
|
404
|
+
// Default sorting (by title)
|
|
405
|
+
sortedMediaItems = mediaItems;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return sortedMediaItems;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Don't compile this code when we build for the old architecture.
|
|
412
|
+
#ifdef RCT_NEW_ARCH_ENABLED
|
|
413
|
+
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
|
|
414
|
+
(const facebook::react::ObjCTurboModule::InitParams &)params
|
|
415
|
+
{
|
|
416
|
+
return std::make_shared<facebook::react::NativeTurboSongsSpecJSI>(params);
|
|
417
|
+
}
|
|
418
|
+
#endif
|
|
419
|
+
|
|
420
|
+
@end
|
package/package.json
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@andycui/react-native-get-music-files",
|
|
3
|
+
"version": "3.0.1",
|
|
4
|
+
"description": "React Native package to get music files from local and sd for iOS and Android",
|
|
5
|
+
"main": "lib/commonjs/index",
|
|
6
|
+
"module": "lib/module/index",
|
|
7
|
+
"types": "lib/typescript/src/index.d.ts",
|
|
8
|
+
"react-native": "src/index",
|
|
9
|
+
"source": "src/index",
|
|
10
|
+
"files": [
|
|
11
|
+
"src",
|
|
12
|
+
"lib",
|
|
13
|
+
"android",
|
|
14
|
+
"ios",
|
|
15
|
+
"cpp",
|
|
16
|
+
"*.podspec",
|
|
17
|
+
"!ios/build",
|
|
18
|
+
"!android/build",
|
|
19
|
+
"!android/gradle",
|
|
20
|
+
"!android/gradlew",
|
|
21
|
+
"!android/gradlew.bat",
|
|
22
|
+
"!android/local.properties",
|
|
23
|
+
"!**/__tests__",
|
|
24
|
+
"!**/__fixtures__",
|
|
25
|
+
"!**/__mocks__",
|
|
26
|
+
"!**/.*"
|
|
27
|
+
],
|
|
28
|
+
"scripts": {
|
|
29
|
+
"example": "yarn workspace react-native-turbo-songs-example",
|
|
30
|
+
"test": "jest",
|
|
31
|
+
"typecheck": "tsc --noEmit",
|
|
32
|
+
"lint": "eslint \"**/*.{js,ts,tsx}\"",
|
|
33
|
+
"clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib",
|
|
34
|
+
"release": "release-it"
|
|
35
|
+
},
|
|
36
|
+
"keywords": [
|
|
37
|
+
"react-native",
|
|
38
|
+
"music",
|
|
39
|
+
"files",
|
|
40
|
+
"metadata",
|
|
41
|
+
"ios",
|
|
42
|
+
"android",
|
|
43
|
+
"react",
|
|
44
|
+
"native"
|
|
45
|
+
],
|
|
46
|
+
"repository": "https://github.com/andy380743909/react-native-get-music-files",
|
|
47
|
+
"author": "Andy Cui <andy380743909@gmail.com>",
|
|
48
|
+
"license": "MIT",
|
|
49
|
+
"bugs": {
|
|
50
|
+
"url": "https://github.com/andy380743909/react-native-get-music-files/issues"
|
|
51
|
+
},
|
|
52
|
+
"homepage": "https://github.com/andy380743909/react-native-get-music-files#readme",
|
|
53
|
+
"publishConfig": {
|
|
54
|
+
"registry": "https://registry.npmjs.org/"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@commitlint/config-conventional": "^17.0.2",
|
|
58
|
+
"@evilmartians/lefthook": "^1.5.0",
|
|
59
|
+
"@react-native/eslint-config": "^0.72.2",
|
|
60
|
+
"@release-it/conventional-changelog": "^5.0.0",
|
|
61
|
+
"@types/jest": "^28.1.2",
|
|
62
|
+
"@types/react": "~17.0.21",
|
|
63
|
+
"@types/react-native": "0.70.0",
|
|
64
|
+
"commitlint": "^17.0.2",
|
|
65
|
+
"del-cli": "^5.0.0",
|
|
66
|
+
"eslint": "^8.4.1",
|
|
67
|
+
"eslint-config-prettier": "^8.5.0",
|
|
68
|
+
"eslint-plugin-prettier": "^4.0.0",
|
|
69
|
+
"jest": "^28.1.1",
|
|
70
|
+
"pod-install": "^0.1.0",
|
|
71
|
+
"prettier": "^2.0.5",
|
|
72
|
+
"react": "18.2.0",
|
|
73
|
+
"react-native": "0.72.5",
|
|
74
|
+
"react-native-builder-bob": "^0.40.17",
|
|
75
|
+
"release-it": "^15.0.0",
|
|
76
|
+
"turbo": "^1.10.7",
|
|
77
|
+
"typescript": "^5.0.2"
|
|
78
|
+
},
|
|
79
|
+
"resolutions": {
|
|
80
|
+
"@types/react": "17.0.21"
|
|
81
|
+
},
|
|
82
|
+
"peerDependencies": {
|
|
83
|
+
"react": "*",
|
|
84
|
+
"react-native": "*"
|
|
85
|
+
},
|
|
86
|
+
"workspaces": [
|
|
87
|
+
"example"
|
|
88
|
+
],
|
|
89
|
+
"packageManager": "yarn@3.6.1",
|
|
90
|
+
"engines": {
|
|
91
|
+
"node": ">= 18.0.0"
|
|
92
|
+
},
|
|
93
|
+
"jest": {
|
|
94
|
+
"preset": "react-native",
|
|
95
|
+
"modulePathIgnorePatterns": [
|
|
96
|
+
"<rootDir>/example/node_modules",
|
|
97
|
+
"<rootDir>/lib/"
|
|
98
|
+
]
|
|
99
|
+
},
|
|
100
|
+
"commitlint": {
|
|
101
|
+
"extends": [
|
|
102
|
+
"@commitlint/config-conventional"
|
|
103
|
+
]
|
|
104
|
+
},
|
|
105
|
+
"release-it": {
|
|
106
|
+
"git": {
|
|
107
|
+
"commitMessage": "chore: release ${version}",
|
|
108
|
+
"tagName": "v${version}"
|
|
109
|
+
},
|
|
110
|
+
"npm": {
|
|
111
|
+
"publish": true
|
|
112
|
+
},
|
|
113
|
+
"github": {
|
|
114
|
+
"release": true
|
|
115
|
+
},
|
|
116
|
+
"plugins": {
|
|
117
|
+
"@release-it/conventional-changelog": {
|
|
118
|
+
"preset": "angular"
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
"eslintConfig": {
|
|
123
|
+
"root": true,
|
|
124
|
+
"extends": [
|
|
125
|
+
"@react-native",
|
|
126
|
+
"prettier"
|
|
127
|
+
],
|
|
128
|
+
"rules": {
|
|
129
|
+
"prettier/prettier": [
|
|
130
|
+
"error",
|
|
131
|
+
{
|
|
132
|
+
"quoteProps": "consistent",
|
|
133
|
+
"singleQuote": true,
|
|
134
|
+
"tabWidth": 2,
|
|
135
|
+
"trailingComma": "es5",
|
|
136
|
+
"useTabs": false
|
|
137
|
+
}
|
|
138
|
+
]
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
"eslintIgnore": [
|
|
142
|
+
"node_modules/",
|
|
143
|
+
"lib/"
|
|
144
|
+
],
|
|
145
|
+
"prettier": {
|
|
146
|
+
"quoteProps": "consistent",
|
|
147
|
+
"singleQuote": true,
|
|
148
|
+
"tabWidth": 2,
|
|
149
|
+
"trailingComma": "es5",
|
|
150
|
+
"useTabs": false
|
|
151
|
+
},
|
|
152
|
+
"react-native-builder-bob": {
|
|
153
|
+
"source": "src",
|
|
154
|
+
"output": "lib",
|
|
155
|
+
"targets": [
|
|
156
|
+
"commonjs",
|
|
157
|
+
"module",
|
|
158
|
+
[
|
|
159
|
+
"typescript",
|
|
160
|
+
{
|
|
161
|
+
"project": "tsconfig.build.json"
|
|
162
|
+
}
|
|
163
|
+
]
|
|
164
|
+
]
|
|
165
|
+
},
|
|
166
|
+
"codegenConfig": {
|
|
167
|
+
"name": "RNTurboSongsSpec",
|
|
168
|
+
"type": "modules",
|
|
169
|
+
"jsSrcsDir": "src"
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32'
|
|
5
|
+
|
|
6
|
+
Pod::Spec.new do |s|
|
|
7
|
+
s.name = "react-native-turbo-songs"
|
|
8
|
+
s.version = package["version"]
|
|
9
|
+
s.summary = package["description"]
|
|
10
|
+
s.homepage = package["homepage"]
|
|
11
|
+
s.license = package["license"]
|
|
12
|
+
s.authors = package["author"]
|
|
13
|
+
|
|
14
|
+
s.platforms = { :ios => "11.0" }
|
|
15
|
+
s.source = { :git => "https://.git", :tag => "#{s.version}" }
|
|
16
|
+
|
|
17
|
+
s.source_files = "ios/**/*.{h,m,mm}"
|
|
18
|
+
|
|
19
|
+
# Use install_modules_dependencies helper to install the dependencies if React Native version >=0.71.0.
|
|
20
|
+
# See https://github.com/facebook/react-native/blob/febf6b7f33fdb4904669f99d795eba4c0f95d7bf/scripts/cocoapods/new_architecture.rb#L79.
|
|
21
|
+
if respond_to?(:install_modules_dependencies, true)
|
|
22
|
+
install_modules_dependencies(s)
|
|
23
|
+
else
|
|
24
|
+
s.dependency "React-Core"
|
|
25
|
+
|
|
26
|
+
# Don't install the dependencies when we run `pod install` in the old architecture.
|
|
27
|
+
if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then
|
|
28
|
+
s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1"
|
|
29
|
+
s.pod_target_xcconfig = {
|
|
30
|
+
"HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"",
|
|
31
|
+
"OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1",
|
|
32
|
+
"CLANG_CXX_LANGUAGE_STANDARD" => "c++17"
|
|
33
|
+
}
|
|
34
|
+
s.dependency "React-Codegen"
|
|
35
|
+
s.dependency "RCT-Folly"
|
|
36
|
+
s.dependency "RCTRequired"
|
|
37
|
+
s.dependency "RCTTypeSafety"
|
|
38
|
+
s.dependency "ReactCommon/turbomodule/core"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { TurboModule } from 'react-native';
|
|
2
|
+
import { TurboModuleRegistry } from 'react-native';
|
|
3
|
+
|
|
4
|
+
export interface Song {
|
|
5
|
+
url: string;
|
|
6
|
+
title: string;
|
|
7
|
+
album: string;
|
|
8
|
+
artist: string;
|
|
9
|
+
duration: number;
|
|
10
|
+
genre: string;
|
|
11
|
+
cover: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
enum SortSongOrder {
|
|
15
|
+
ASC = 'ASC',
|
|
16
|
+
DESC = 'DESC',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
enum SortSongFields {
|
|
20
|
+
TITLE = 'TITLE',
|
|
21
|
+
DURATION = 'DURATION',
|
|
22
|
+
ARTIST = 'ARTIST',
|
|
23
|
+
GENRE = 'GENRE',
|
|
24
|
+
ALBUM = 'ALBUM',
|
|
25
|
+
DATE_ADDED = 'DATE_ADDED',
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface SongOptions {
|
|
29
|
+
limit?: number;
|
|
30
|
+
offset?: number;
|
|
31
|
+
coverQuality?: number;
|
|
32
|
+
minSongDuration?: number;
|
|
33
|
+
searchBy?: string;
|
|
34
|
+
sortOrder?: SortSongOrder;
|
|
35
|
+
sortBy?: SortSongFields;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface Album {
|
|
39
|
+
url: string;
|
|
40
|
+
album: string;
|
|
41
|
+
artist: string;
|
|
42
|
+
numberOfSongs: string;
|
|
43
|
+
cover: string;
|
|
44
|
+
}
|
|
45
|
+
export interface AlbumOptions {
|
|
46
|
+
limit?: number;
|
|
47
|
+
offset?: number;
|
|
48
|
+
coverQuality?: number;
|
|
49
|
+
artist: string;
|
|
50
|
+
sortOrder?: SortSongOrder;
|
|
51
|
+
sortBy?: SortSongOrder;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface Spec extends TurboModule {
|
|
55
|
+
getAll(options?: SongOptions): Promise<Song[] | string>;
|
|
56
|
+
getAlbums(options?: AlbumOptions): Promise<Album[] | string>;
|
|
57
|
+
searchSongs(options?: SongOptions): Promise<Song[] | string>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export default TurboModuleRegistry.getEnforcing<Spec>('TurboSongs');
|