Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Manual cache #9

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
6 changes: 6 additions & 0 deletions Video.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ export default class Video extends Component {
};
}

static export = async (url) => {
return await NativeModules.VideoManager.export(
url,
);
};

setNativeProps(nativeProps) {
this._root.setNativeProps(nativeProps);
}
Expand Down
2 changes: 1 addition & 1 deletion android-exoplayer/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ android {

dependencies {
implementation "com.facebook.react:react-native:${safeExtGet('reactNativeVersion', '+')}"
implementation('com.google.android.exoplayer:exoplayer:2.10.5') {
api('com.google.android.exoplayer:exoplayer:2.10.5') {
exclude group: 'com.android.support'
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,39 +1,49 @@
package com.brentvatne.exoplayer;

import android.content.Context;
import android.net.Uri;
import android.util.Log;
import android.util.Pair;

import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.upstream.cache.NoOpCacheEvictor;
import com.google.android.exoplayer2.upstream.cache.Cache;
import com.google.android.exoplayer2.upstream.cache.SimpleCache;
import com.google.android.exoplayer2.upstream.cache.CacheUtil;
import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory;
import com.google.android.exoplayer2.upstream.cache.CacheKeyFactory;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSourceInputStream;

import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSourceInputStream;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.cache.Cache;
import com.google.android.exoplayer2.upstream.cache.CacheDataSource;
import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory;
import com.google.android.exoplayer2.upstream.cache.CacheKeyFactory;
import com.google.android.exoplayer2.upstream.cache.CacheUtil;
import com.google.android.exoplayer2.upstream.cache.SimpleCache;

import java.io.IOException;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URL;

public class ExoPlayerCache extends ReactContextBaseJavaModule {

private static SimpleCache instance = null;
private static final String CACHE_KEY_PREFIX = "exoPlayerCacheKeyPrefix";
private String TMP_EXPORT_PATH;

public ExoPlayerCache(ReactApplicationContext reactContext) {
super(reactContext);

TMP_EXPORT_PATH = getReactApplicationContext().getCacheDir().toString() + "/video-tmp";
File exportPath = new File(TMP_EXPORT_PATH);

// Clear the temporary export files on launch to make sure this doesn't grow infinitely.
if (exportPath.exists()) {
for (File child: exportPath.listFiles()) {
child.delete();
}
}
}

@Override
Expand All @@ -50,42 +60,43 @@ public void exportVideo(final String url, final Promise promise) {
public void run() {
Log.d(getName(), "Exporting...");
Log.d(getName(), url);

final Uri uri = Uri.parse(url);

final SimpleCache downloadCache = VideoCache.getInstance().getSimpleCache();
final DataSource dataSource = createDataSource(downloadCache);
final DataSpec dataSpec = new DataSpec(uri, 0, C.LENGTH_UNSET, null);
final SimpleCache downloadCache = ExoPlayerCache.getInstance(getReactApplicationContext());
CacheKeyFactory cacheKeyFactory = ds -> CACHE_KEY_PREFIX + "." + CacheUtil.generateKey(ds.uri);;

File targetFile = new File(TMP_EXPORT_PATH, uri.getLastPathSegment());

// Create export dir if not exists.
targetFile.getParentFile().mkdirs();


// https://github.com/google/ExoPlayer/issues/5569
try {
CacheUtil.getCached(
dataSpec,
CacheUtil.getCached(
dataSpec,
downloadCache,
cacheKeyFactory
new ExoplayerCacheKeyFactory()
);

DataSourceInputStream inputStream = new DataSourceInputStream(createDataSource(downloadCache), dataSpec);
BufferedOutputStream outStream = new BufferedOutputStream(new FileOutputStream(targetFile), 64 * 1024);

File targetFile = new File(ExoPlayerCache.getCacheDir(getReactApplicationContext()) + "/" + uri.getLastPathSegment());
OutputStream outStream = new FileOutputStream(targetFile);

byte[] buffer = new byte[8 * 1024];
int bytesRead;
try {
while ((bytesRead = inputStream.read(buffer)) != -1) {
outStream.write(buffer, 0, bytesRead);
// TODO Add onProgress() callback here
byte[] data = new byte[64 * 1024];
int bytesRead;
while ((bytesRead = inputStream.read(data)) != C.RESULT_END_OF_INPUT) {
outStream.write(data, 0, bytesRead);
}
} catch (IOException e) {
// TODO this exception should not be thrown
Log.d(getName(), "Read error");
Log.d(getName(), "Write error");
e.printStackTrace();
} finally {
dataSource.close();
outStream.close();
}

CacheUtil.getCached(
dataSpec,
downloadCache,
cacheKeyFactory
);

Log.d(getName(), "Export succeeded");
Log.d(getName(), targetFile.getPath());

Expand All @@ -100,23 +111,11 @@ public void run() {
exportThread.start();
}

public static SimpleCache getInstance(Context context) {
if(instance == null) {
instance = new SimpleCache(new File(ExoPlayerCache.getCacheDir(context)), new NoOpCacheEvictor());
}
return instance;
}

private static String getCacheDir(Context context) {
return context.getCacheDir().toString() + "/video";
}

private DataSource createDataSource(Cache cache) {
private CacheDataSource createDataSource(Cache cache) {
return new CacheDataSourceFactory(cache, DataSourceUtil.getDefaultDataSourceFactory(
getReactApplicationContext(),
null,
null
)).createDataSource();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.brentvatne_exp.exoplayer;

import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.cache.CacheKeyFactory;

public class ExoplayerCacheKeyFactory implements CacheKeyFactory {
@Override
public String buildCacheKey(DataSpec dataSpec) {
String uri = dataSpec.uri.toString();

// Strip query parameters for cache key since this breaks lookup.
int queryIndex = uri.indexOf("?");
if (queryIndex != -1) {
uri = uri.substring(0, queryIndex);
}

return uri;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
import com.google.android.exoplayer2.source.ExtractorMediaSource;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MergingMediaSource;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.source.SingleSampleMediaSource;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
Expand All @@ -62,7 +61,9 @@
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultAllocator;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.cache.Cache;
import com.google.android.exoplayer2.upstream.FileDataSourceFactory;
import com.google.android.exoplayer2.upstream.cache.CacheDataSink;
import com.google.android.exoplayer2.upstream.cache.CacheDataSinkFactory;
import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory;
import com.google.android.exoplayer2.upstream.cache.SimpleCache;
import com.google.android.exoplayer2.util.Util;
Expand Down Expand Up @@ -103,6 +104,8 @@ class ReactExoplayerView extends FrameLayout implements
private ExoPlayerView exoPlayerView;

private SimpleCache downloadCache;
private ExoplayerCacheKeyFactory cacheKeyFactory;
private CacheDataSourceFactory cacheDataSourceFactory;
private DataSource.Factory mediaDataSourceFactory;
private SimpleExoPlayer player;
private DefaultTrackSelector trackSelector;
Expand Down Expand Up @@ -197,7 +200,13 @@ public void setId(int id) {
private void createViews() {
clearResumePosition();
mediaDataSourceFactory = buildDataSourceFactory(true);
downloadCache = ExoPlayerCache.getInstance(getContext());
downloadCache = VideoCache.getInstance().getSimpleCache();
cacheKeyFactory = new ExoplayerCacheKeyFactory();
cacheDataSourceFactory = new CacheDataSourceFactory(
downloadCache, mediaDataSourceFactory, new FileDataSourceFactory(),
new CacheDataSinkFactory(downloadCache, CacheDataSink.DEFAULT_FRAGMENT_SIZE),
0, null, cacheKeyFactory);


if (CookieHandler.getDefault() != DEFAULT_COOKIE_MANAGER) {
CookieHandler.setDefault(DEFAULT_COOKIE_MANAGER);
Expand Down Expand Up @@ -434,7 +443,7 @@ private MediaSource buildMediaSource(Uri uri, String overrideExtension) {
config.buildLoadErrorHandlingPolicy(minLoadRetryCount)
).createMediaSource(uri);
case C.TYPE_OTHER:
return new ExtractorMediaSource.Factory(new CacheDataSourceFactory(downloadCache, mediaDataSourceFactory))
return new ExtractorMediaSource.Factory(cacheDataSourceFactory)
.createMediaSource(uri);
default: {
throw new IllegalStateException("Unsupported type: " + type);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.brentvatne.exoplayer;

import com.google.android.exoplayer2.upstream.cache.SimpleCache;

public class VideoCache {

private static volatile VideoCache instance;
private SimpleCache cache;

private VideoCache() {
if (instance != null) {
throw new RuntimeException("Use getInstance()");
}
}

public void setSimpleCache(SimpleCache cache) {
this.cache = cache;
}

public SimpleCache getSimpleCache() {
if (this.cache == null) {
throw new RuntimeException("Tried to access video cache but no cache is set");
}

return this.cache;
}

public boolean hasSimpleCache() {
return this.cache != null;
}

public void resetSimpleCache() {
if (hasSimpleCache()) {
this.cache.release();
this.cache = null;
} else {
throw new RuntimeException("Resetting video cache but no cache is set!");
}

return;
}

public static VideoCache getInstance() {
if (instance == null) {
synchronized (VideoCache.class) {
if (instance == null) instance = new VideoCache();
}
}

return instance;
}
}
1 change: 1 addition & 0 deletions ios/RCTVideo.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,6 @@
- (AVPlayerViewController*)createPlayerViewController:(AVPlayer*)player withPlayerItem:(AVPlayerItem*)playerItem;

- (void)save:(NSDictionary *)options resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject;
+ (void)export:(NSString *)url resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject;

@end
1 change: 1 addition & 0 deletions ios/RCTVideo.m
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,7 @@ - (void)dvAssetLoaderDelegate:(DVAssetLoaderDelegate *)loaderDelegate
didLoadData:(NSData *)data
forURL:(NSURL *)url {
DebugLog(@"dvAssetLoaderDelegate: url '%@'", [url absoluteString]);

[_videoCache storeItem:data forUri:[url absoluteString] withCallback:^(BOOL success) {
DebugLog(@"Cache data stored successfully 🎉");
}];
Expand Down
7 changes: 7 additions & 0 deletions ios/RCTVideo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
objects = {

/* Begin PBXBuildFile section */
C7522D2C2452D73B00027383 /* RCTVideoExport.m in Sources */ = {isa = PBXBuildFile; fileRef = C7522D2A2452D73B00027383 /* RCTVideoExport.m */; };
C7EF13182180C23D00E42B96 /* RCTVideoManager.m in Sources */ = {isa = PBXBuildFile; fileRef = C7EF130D2180C23D00E42B96 /* RCTVideoManager.m */; };
C7EF13192180C23D00E42B96 /* RCTVideoPlayerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C7EF130E2180C23D00E42B96 /* RCTVideoPlayerViewController.m */; };
C7EF131A2180C23D00E42B96 /* RCTVideoCache.m in Sources */ = {isa = PBXBuildFile; fileRef = C7EF13122180C23D00E42B96 /* RCTVideoCache.m */; };
Expand Down Expand Up @@ -38,6 +39,8 @@
/* Begin PBXFileReference section */
134814201AA4EA6300B7C361 /* libRCTVideo.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTVideo.a; sourceTree = BUILT_PRODUCTS_DIR; };
641E28441F0EEC8500443AF6 /* libRCTVideo.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTVideo.a; sourceTree = BUILT_PRODUCTS_DIR; };
C7522D2A2452D73B00027383 /* RCTVideoExport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTVideoExport.m; sourceTree = "<group>"; };
C7522D2B2452D73B00027383 /* RCTVideoExport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTVideoExport.h; sourceTree = "<group>"; };
C7EF130D2180C23D00E42B96 /* RCTVideoManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTVideoManager.m; sourceTree = "<group>"; };
C7EF130E2180C23D00E42B96 /* RCTVideoPlayerViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTVideoPlayerViewController.m; sourceTree = "<group>"; };
C7EF130F2180C23D00E42B96 /* RCTVideo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTVideo.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -87,6 +90,8 @@
58B511D21A9E6C8500147676 = {
isa = PBXGroup;
children = (
C7522D2B2452D73B00027383 /* RCTVideoExport.h */,
C7522D2A2452D73B00027383 /* RCTVideoExport.m */,
C7EF130F2180C23D00E42B96 /* RCTVideo.h */,
C7EF13152180C23D00E42B96 /* RCTVideo.m */,
C7EF13102180C23D00E42B96 /* RCTVideoCache.h */,
Expand Down Expand Up @@ -164,6 +169,7 @@
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
English,
en,
);
mainGroup = 58B511D21A9E6C8500147676;
Expand All @@ -187,6 +193,7 @@
C7EF13192180C23D00E42B96 /* RCTVideoPlayerViewController.m in Sources */,
C7EF131B2180C23D00E42B96 /* UIView+FindUIViewController.m in Sources */,
C7EF131A2180C23D00E42B96 /* RCTVideoCache.m in Sources */,
C7522D2C2452D73B00027383 /* RCTVideoExport.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
3 changes: 1 addition & 2 deletions ios/RCTVideoCache.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ typedef NS_ENUM(NSUInteger, RCTVideoCacheStatus) {
}

@property(nonatomic, strong) SPTPersistentCache * _Nullable videoCache;
@property(nonatomic, strong) NSString * cachePath;
@property(nonatomic, strong) NSString * cacheIdentifier;
@property(nonatomic, strong) NSString * temporaryCachePath;

+ (RCTVideoCache *)sharedInstance;
Expand All @@ -34,5 +32,6 @@ typedef NS_ENUM(NSUInteger, RCTVideoCacheStatus) {
- (AVURLAsset *)getItemFromTemporaryStorage:(NSString *)key;
- (BOOL)saveDataToTemporaryStorage:(NSData *)data key:(NSString *)key;
- (void) createTemporaryPath;
- (void) setVideoCache:(SPTPersistentCache *)videoCache;

@end
Loading