Skip to content

Commit 6a258f1

Browse files
authored
base64 data url support (react-native-audio-toolkit#220)
1 parent 96197e3 commit 6a258f1

File tree

6 files changed

+144
-22
lines changed

6 files changed

+144
-22
lines changed

android/src/main/java/com/reactnativecommunity/rctaudiotoolkit/AudioPlayerModule.java

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -251,8 +251,6 @@ public void prepare(Integer playerId, String path, ReadableMap options, final Ca
251251
destroy(playerId);
252252
this.lastPlayerId = playerId;
253253

254-
Uri uri = uriFromPath(path);
255-
256254
//MediaPlayer player = MediaPlayer.create(this.context, uri, null, attributes);
257255
MediaPlayer player = new MediaPlayer();
258256

@@ -264,13 +262,23 @@ public void prepare(Integer playerId, String path, ReadableMap options, final Ca
264262
265263
player.setAudioAttributes(attributes);
266264
*/
267-
268-
try {
269-
Log.d(LOG_TAG, uri.getPath());
270-
player.setDataSource(this.context, uri);
271-
} catch (IOException e) {
272-
callback.invoke(errObj("invalidpath", e.toString()));
273-
return;
265+
if(path.startsWith("data:audio/")) {
266+
try {
267+
player.setDataSource(path);
268+
}
269+
catch (IOException e) {
270+
callback.invoke(errObj("invalid base64 data url", e.toString()));
271+
return;
272+
}
273+
} else {
274+
try {
275+
Uri uri = uriFromPath(path);
276+
Log.d(LOG_TAG, uri.getPath());
277+
player.setDataSource(this.context, uri);
278+
} catch (IOException e) {
279+
callback.invoke(errObj("invalidpath", e.toString()));
280+
return;
281+
}
274282
}
275283

276284
player.setOnErrorListener(this);

ios/ReactNativeAudioToolkit/ReactNativeAudioToolkit/AudioPlayer.m

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
#import "ReactPlayerItem.h"
1717
#import <AVFoundation/AVPlayer.h>
1818
#import <AVFoundation/AVPlayerItem.h>
19-
#import <AVFoundation/AVAsset.h>
2019

2120

2221
@interface AudioPlayer ()
@@ -102,18 +101,31 @@ - (NSURL *)findUrlForPath:(NSString *)path {
102101
callback(@[dict]);
103102
return;
104103
}
105-
106-
// Try to find the correct file
107-
NSURL *url = [self findUrlForPath:path];
108-
if (!url) {
109-
NSDictionary* dict = [Helpers errObjWithCode:@"invalidpath" withMessage:@"No file found at path"];
104+
ReactPlayerItem *item;
105+
if ([path hasPrefix:@"data:audio/"]) {
106+
// inline data
107+
NSData *data = [Helpers decodeBase64DataUrl:path];
108+
if (!data) {
109+
NSDictionary* dict = [Helpers errObjWithCode:@"invalidpath" withMessage:@"invalid data:audio URL"];
110+
callback(@[dict]);
111+
return;
112+
}
113+
item = (ReactPlayerItem *)[ReactPlayerItem playerItemWithData: data];
114+
} else {
115+
// Try to find the correct file
116+
NSURL *url = [self findUrlForPath:path];
117+
if (!url) {
118+
NSDictionary* dict = [Helpers errObjWithCode:@"invalidpath" withMessage:@"No file found at path"];
119+
callback(@[dict]);
120+
return;
121+
}
122+
item = (ReactPlayerItem *)[ReactPlayerItem playerItemWithURL: url];
123+
}
124+
if (!item) {
125+
NSDictionary* dict = [Helpers errObjWithCode:@"preparefail" withMessage:@"error initializing player item"];
110126
callback(@[dict]);
111127
return;
112128
}
113-
114-
// Load asset from the url
115-
AVURLAsset *asset = [AVURLAsset assetWithURL: url];
116-
ReactPlayerItem *item = (ReactPlayerItem *)[ReactPlayerItem playerItemWithAsset: asset];
117129
item.reactPlayerId = playerId;
118130

119131
// Add notification to know when file has stopped playing

ios/ReactNativeAudioToolkit/ReactNativeAudioToolkit/Helpers.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,6 @@
1717

1818
+(NSDictionary *)recorderSettingsFromOptions:(NSDictionary *)options;
1919

20+
+(NSData *)decodeBase64DataUrl:(NSString*)url;
21+
2022
@end

ios/ReactNativeAudioToolkit/ReactNativeAudioToolkit/Helpers.m

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,21 @@ + (NSDictionary *)recorderSettingsFromOptions:(NSDictionary *)options {
7777
return recordSettings;
7878
}
7979

80+
+(NSData *)decodeBase64DataUrl:(NSString*)url {
81+
NSRange b64r = [url rangeOfString:@";base64,"];
82+
if(b64r.location == NSNotFound) {
83+
NSLog(@"decodeBase64DataUrl - base64 not found in data: url");
84+
return nil;
85+
}
86+
NSInteger idx = b64r.location + @";base64,".length;
87+
NSString *b64string = [url substringFromIndex:idx];
88+
NSData *b64decoded = [[NSData alloc] initWithBase64EncodedString:b64string options:NSASCIIStringEncoding];
89+
if(b64decoded == nil) {
90+
NSLog(@"decodeBase64DataUrl - error decoding base64 data");
91+
return nil;
92+
}
93+
94+
return b64decoded;
95+
}
96+
8097
@end

ios/ReactNativeAudioToolkit/ReactNativeAudioToolkit/ReactPlayerItem.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010

1111
#import <AVFoundation/AVFoundation.h>
1212
@class ReactPlayer;
13-
@interface ReactPlayerItem : AVPlayerItem
13+
@interface ReactPlayerItem : AVPlayerItem <AVAssetResourceLoaderDelegate>
14+
15+
+ (instancetype)playerItemWithData:(NSData *)data;
1416

1517
@property (nonatomic, strong) NSNumber *reactPlayerId;
1618

ios/ReactNativeAudioToolkit/ReactNativeAudioToolkit/ReactPlayerItem.m

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,95 @@
1010

1111
#import "ReactPlayerItem.h"
1212

13-
@implementation ReactPlayerItem
13+
@implementation ReactPlayerItem {
14+
NSData *_data;
15+
}
1416

1517
- (void)dealloc {
1618
self.reactPlayerId = nil;
19+
_data = nil;
1720
}
1821

19-
+ (instancetype)playerItemWithAsset:(AVAsset *)asset {
22+
+ (instancetype)playerItemWithURL:(NSURL *)url {
23+
AVURLAsset *asset = [AVURLAsset assetWithURL: url];
2024
return [[self alloc] initWithAsset:asset];
2125
}
2226

27+
+ (instancetype)playerItemWithData:(NSData *)data {
28+
AVURLAsset *asset = [AVURLAsset assetWithURL:[NSURL URLWithString:@"data:"]];
29+
ReactPlayerItem *rpi = [[self alloc] initWithAsset:asset];
30+
dispatch_queue_t queue = dispatch_queue_create("assetQueue", nil);
31+
[asset.resourceLoader setDelegate:rpi queue:queue];
32+
rpi->_data = data;
33+
return rpi;
34+
}
35+
36+
#pragma mark - AVAssetResourceLoaderDelegate
37+
38+
- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForRenewalOfRequestedResource:(AVAssetResourceRenewalRequest *)renewalRequest {
39+
return [self loadingRequestHandling:renewalRequest];
40+
}
41+
42+
- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest {
43+
return [self loadingRequestHandling:loadingRequest];;
44+
}
45+
46+
- (void)resourceLoader:(AVAssetResourceLoader *)resourceLoader
47+
didCancelLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest {
48+
NSLog(@"didCancelLoadingRequest");
49+
}
50+
51+
- (BOOL)loadingRequestHandling:(AVAssetResourceLoadingRequest *)loadingRequest {
52+
NSLog(@"loadingRequestHandling");
53+
54+
if(loadingRequest.contentInformationRequest != nil) {
55+
// fill up contentInformationRequest end return
56+
loadingRequest.contentInformationRequest.contentType = @"public.mp3";
57+
loadingRequest.contentInformationRequest.contentLength = _data.length;
58+
[loadingRequest.contentInformationRequest setByteRangeAccessSupported:YES];
59+
[loadingRequest finishLoading];
60+
return YES;
61+
}
62+
63+
// slice data as requested
64+
AVAssetResourceLoadingDataRequest *dataRequest = loadingRequest.dataRequest;
65+
66+
long long startOffset = dataRequest.requestedOffset;
67+
if (dataRequest.currentOffset != 0){
68+
startOffset = dataRequest.currentOffset;
69+
}
70+
NSUInteger unreadBytes = _data.length - startOffset;
71+
NSUInteger numberOfBytesToRespondWith = dataRequest.requestedLength >= unreadBytes ? unreadBytes : dataRequest.requestedLength;
72+
NSRange r = {startOffset, numberOfBytesToRespondWith};
73+
NSData *data = [_data subdataWithRange:r];
74+
75+
// provide sliced data
76+
if(data){
77+
[dataRequest respondWithData:data];
78+
[loadingRequest finishLoading];
79+
return YES;
80+
}
81+
82+
NSError *error = [NSError errorWithDomain: @"ReactPlayerItem"
83+
code: -1
84+
userInfo: @{NSLocalizedDescriptionKey: @"loadingRequestHandling - error providing data"}
85+
];
86+
87+
[loadingRequest finishLoadingWithError:error];
88+
89+
return NO;
90+
}
91+
92+
#pragma mark - utilities
93+
94+
+ (NSData *)base64DataFromBase64String: (NSString *)base64String {
95+
if (base64String != nil) {
96+
// NSData from the Base64 encoded str
97+
NSData *base64Data = [[NSData alloc] initWithBase64EncodedString:base64String options:NSASCIIStringEncoding];
98+
return base64Data;
99+
}
100+
return nil;
101+
}
102+
103+
23104
@end

0 commit comments

Comments
 (0)