diff --git a/Video.js b/Video.js index 979eabb332..7a0d7a3996 100644 --- a/Video.js +++ b/Video.js @@ -26,6 +26,29 @@ export default class Video extends Component { setNativeProps(nativeProps) { this._root.setNativeProps(nativeProps); } + + toTypeString(x) { + switch (typeof x) { + case "object": + return x instanceof Date + ? x.toISOString() + : JSON.stringify(x); // object, null + case "undefined": + return ""; + default: // boolean, number, string + return x.toString(); + } + } + + stringsOnlyObject(obj) { + const strObj = {}; + + Object.keys(obj).forEach(x => { + strObj[x] = this.toTypeString(obj[x]); + }); + + return strObj; + } seek = (time, tolerance = 100) => { if (Platform.OS === 'ios') { @@ -202,6 +225,7 @@ export default class Video extends Component { type: source.type || '', mainVer: source.mainVer || 0, patchVer: source.patchVer || 0, + requestHeaders: source.headers ? this.stringsOnlyObject(source.headers) : {} }, onVideoLoadStart: this._onLoadStart, onVideoLoad: this._onLoad, diff --git a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/DataSourceUtil.java b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/DataSourceUtil.java index 6691cd3795..4140fa100d 100644 --- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/DataSourceUtil.java +++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/DataSourceUtil.java @@ -17,6 +17,8 @@ import okhttp3.Cookie; import okhttp3.JavaNetCookieJar; import okhttp3.OkHttpClient; +import java.util.Map; + public class DataSourceUtil { @@ -49,9 +51,10 @@ public static void setRawDataSourceFactory(DataSource.Factory factory) { DataSourceUtil.rawDataSourceFactory = factory; } - public static DataSource.Factory getDefaultDataSourceFactory(ReactContext context, DefaultBandwidthMeter bandwidthMeter) { - if (defaultDataSourceFactory == null) { - defaultDataSourceFactory = buildDataSourceFactory(context, bandwidthMeter); + + public static DataSource.Factory getDefaultDataSourceFactory(ReactContext context, DefaultBandwidthMeter bandwidthMeter, Map requestHeaders) { + if (defaultDataSourceFactory == null || (requestHeaders != null && !requestHeaders.isEmpty())) { + defaultDataSourceFactory = buildDataSourceFactory(context, bandwidthMeter, requestHeaders); } return defaultDataSourceFactory; } @@ -64,17 +67,21 @@ private static DataSource.Factory buildRawDataSourceFactory(ReactContext context return new RawResourceDataSourceFactory(context.getApplicationContext()); } - private static DataSource.Factory buildDataSourceFactory(ReactContext context, DefaultBandwidthMeter bandwidthMeter) { + private static DataSource.Factory buildDataSourceFactory(ReactContext context, DefaultBandwidthMeter bandwidthMeter, Map requestHeaders) { return new DefaultDataSourceFactory(context, bandwidthMeter, - buildHttpDataSourceFactory(context, bandwidthMeter)); + buildHttpDataSourceFactory(context, bandwidthMeter, requestHeaders)); } - private static HttpDataSource.Factory buildHttpDataSourceFactory(ReactContext context, DefaultBandwidthMeter bandwidthMeter) { + private static HttpDataSource.Factory buildHttpDataSourceFactory(ReactContext context, DefaultBandwidthMeter bandwidthMeter, Map requestHeaders) { OkHttpClient client = OkHttpClientProvider.getOkHttpClient(); CookieJarContainer container = (CookieJarContainer) client.cookieJar(); ForwardingCookieHandler handler = new ForwardingCookieHandler(context); container.setCookieJar(new JavaNetCookieJar(handler)); - return new OkHttpDataSourceFactory(client, getUserAgent(context), bandwidthMeter); - } + OkHttpDataSourceFactory okHttpDataSourceFactory = new OkHttpDataSourceFactory(client, getUserAgent(context), bandwidthMeter); + if (requestHeaders != null) + okHttpDataSourceFactory.getDefaultRequestProperties().set(requestHeaders); + + return okHttpDataSourceFactory; + } } diff --git a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index 9b57293f6f..88832de20a 100644 --- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -65,6 +65,7 @@ import java.net.CookieManager; import java.net.CookiePolicy; import java.lang.Math; +import java.util.Map; import java.lang.Object; import java.util.ArrayList; @@ -117,6 +118,7 @@ class ReactExoplayerView extends FrameLayout implements private float mProgressUpdateInterval = 250.0f; private boolean playInBackground = false; private boolean useTextureView = false; + private Map requestHeaders; // \ End props // React @@ -416,7 +418,7 @@ private void clearResumePosition() { * @return A new DataSource factory. */ private DataSource.Factory buildDataSourceFactory(boolean useBandwidthMeter) { - return DataSourceUtil.getDefaultDataSourceFactory(this.themedReactContext, useBandwidthMeter ? BANDWIDTH_METER : null); + return DataSourceUtil.getDefaultDataSourceFactory(this.themedReactContext, useBandwidthMeter ? BANDWIDTH_METER : null, requestHeaders); } // AudioManager.OnAudioFocusChangeListener implementation @@ -660,14 +662,15 @@ public void onMetadata(Metadata metadata) { // ReactExoplayerViewManager public api - public void setSrc(final Uri uri, final String extension) { + public void setSrc(final Uri uri, final String extension, Map headers) { if (uri != null) { boolean isOriginalSourceNull = srcUri == null; boolean isSourceEqual = uri.equals(srcUri); this.srcUri = uri; this.extension = extension; - this.mediaDataSourceFactory = DataSourceUtil.getDefaultDataSourceFactory(this.themedReactContext, BANDWIDTH_METER); + this.requestHeaders = headers; + this.mediaDataSourceFactory = DataSourceUtil.getDefaultDataSourceFactory(this.themedReactContext, BANDWIDTH_METER, this.requestHeaders); if (!isOriginalSourceNull && !isSourceEqual) { reloadSource(); diff --git a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java index 7c41db58af..e3775a6ee5 100644 --- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java +++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java @@ -13,6 +13,7 @@ import com.facebook.react.uimanager.annotations.ReactProp; import com.google.android.exoplayer2.upstream.RawResourceDataSource; +import java.util.HashMap; import java.util.Map; import javax.annotation.Nullable; @@ -24,6 +25,7 @@ public class ReactExoplayerViewManager extends ViewGroupManager headers = src.hasKey(PROP_SRC_HEADERS) ? toStringMap(src.getMap(PROP_SRC_HEADERS)) : null; + if (TextUtils.isEmpty(uriString)) { return; @@ -89,7 +93,7 @@ public void setSrc(final ReactExoplayerView videoView, @Nullable ReadableMap src Uri srcUri = Uri.parse(uriString); if (srcUri != null) { - videoView.setSrc(srcUri, extension); + videoView.setSrc(srcUri, extension, headers); } } else { int identifier = context.getResources().getIdentifier( @@ -208,4 +212,28 @@ private boolean startsWithValidScheme(String uriString) { } return ResizeMode.RESIZE_MODE_FIT; } + + /** + * toStringMap converts a {@link ReadableMap} into a HashMap. + * + * @param readableMap The ReadableMap to be conveted. + * @return A HashMap containing the data that was in the ReadableMap. + * @see 'Adapted from https://github.com/artemyarulin/react-native-eval/blob/master/android/src/main/java/com/evaluator/react/ConversionUtil.java' + */ + public static Map toStringMap(@Nullable ReadableMap readableMap) { + if (readableMap == null) + return null; + + com.facebook.react.bridge.ReadableMapKeySetIterator iterator = readableMap.keySetIterator(); + if (!iterator.hasNextKey()) + return null; + + Map result = new HashMap<>(); + while (iterator.hasNextKey()) { + String key = iterator.nextKey(); + result.put(key, readableMap.getString(key)); + } + + return result; + } } diff --git a/android/src/main/java/com/brentvatne/react/ReactVideoView.java b/android/src/main/java/com/brentvatne/react/ReactVideoView.java index 487433339b..954130a155 100644 --- a/android/src/main/java/com/brentvatne/react/ReactVideoView.java +++ b/android/src/main/java/com/brentvatne/react/ReactVideoView.java @@ -16,6 +16,7 @@ import com.android.vending.expansion.zipfile.ZipResourceFile; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.LifecycleEventListener; +import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.WritableMap; import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.events.RCTEventEmitter; @@ -30,6 +31,8 @@ import java.lang.Math; import java.math.BigDecimal; +import javax.annotation.Nullable; + @SuppressLint("ViewConstructor") public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnPreparedListener, MediaPlayer .OnErrorListener, MediaPlayer.OnBufferingUpdateListener, MediaPlayer.OnCompletionListener, MediaPlayer.OnInfoListener, LifecycleEventListener, MediaController.MediaPlayerControl { @@ -89,6 +92,7 @@ public String toString() { private String mSrcUriString = null; private String mSrcType = "mp4"; + private ReadableMap mRequestHeaders = null; private boolean mSrcIsNetwork = false; private boolean mSrcIsAsset = false; private ScalableType mResizeMode = ScalableType.LEFT_TOP; @@ -206,16 +210,17 @@ public void cleanupMediaPlayerResources() { } } - public void setSrc(final String uriString, final String type, final boolean isNetwork, final boolean isAsset) { - setSrc(uriString,type,isNetwork,isAsset,0,0); + public void setSrc(final String uriString, final String type, final boolean isNetwork, final boolean isAsset, final ReadableMap requestHeaders) { + setSrc(uriString, type, isNetwork, isAsset, requestHeaders, 0, 0); } - public void setSrc(final String uriString, final String type, final boolean isNetwork, final boolean isAsset, final int expansionMainVersion, final int expansionPatchVersion) { + public void setSrc(final String uriString, final String type, final boolean isNetwork, final boolean isAsset, final ReadableMap requestHeaders, final int expansionMainVersion, final int expansionPatchVersion) { mSrcUriString = uriString; mSrcType = type; mSrcIsNetwork = isNetwork; mSrcIsAsset = isAsset; + mRequestHeaders = requestHeaders; mMainVer = expansionMainVersion; mPatchVer = expansionPatchVersion; @@ -244,7 +249,15 @@ public void setSrc(final String uriString, final String type, final boolean isNe headers.put("Cookie", cookie); } - setDataSource(uriString); + if (mRequestHeaders != null) { + headers.putAll(toStringMap(mRequestHeaders)); + } + + /* According to https://github.com/react-native-community/react-native-video/pull/537 + * there is an issue with this where it can cause a IOException. + * TODO: diagnose this exception and fix it + */ + setDataSource(mThemedReactContext, parsedUrl, headers); } else if (isAsset) { if (uriString.startsWith("content://")) { Uri parsedUrl = Uri.parse(uriString); @@ -290,8 +303,13 @@ public void setSrc(final String uriString, final String type, final boolean isNe } WritableMap src = Arguments.createMap(); + + WritableMap wRequestHeaders = Arguments.createMap(); + wRequestHeaders.merge(mRequestHeaders); + src.putString(ReactVideoViewManager.PROP_SRC_URI, uriString); src.putString(ReactVideoViewManager.PROP_SRC_TYPE, type); + src.putMap(ReactVideoViewManager.PROP_SRC_HEADERS, wRequestHeaders); src.putBoolean(ReactVideoViewManager.PROP_SRC_IS_NETWORK, isNetwork); if(mMainVer>0) { src.putInt(ReactVideoViewManager.PROP_SRC_MAINVER, mMainVer); @@ -581,10 +599,10 @@ protected void onAttachedToWindow() { super.onAttachedToWindow(); if(mMainVer>0) { - setSrc(mSrcUriString, mSrcType, mSrcIsNetwork,mSrcIsAsset,mMainVer,mPatchVer); + setSrc(mSrcUriString, mSrcType, mSrcIsNetwork, mSrcIsAsset, mRequestHeaders, mMainVer, mPatchVer); } else { - setSrc(mSrcUriString, mSrcType, mSrcIsNetwork,mSrcIsAsset); + setSrc(mSrcUriString, mSrcType, mSrcIsNetwork, mSrcIsAsset, mRequestHeaders); } } @@ -618,4 +636,25 @@ public void run() { @Override public void onHostDestroy() { } + + /** + * toStringMap converts a {@link ReadableMap} into a HashMap. + * + * @param readableMap The ReadableMap to be conveted. + * @return A HashMap containing the data that was in the ReadableMap. + * @see 'Adapted from https://github.com/artemyarulin/react-native-eval/blob/master/android/src/main/java/com/evaluator/react/ConversionUtil.java' + */ + public static Map toStringMap(@Nullable ReadableMap readableMap) { + Map result = new HashMap<>(); + if (readableMap == null) + return result; + + com.facebook.react.bridge.ReadableMapKeySetIterator iterator = readableMap.keySetIterator(); + while (iterator.hasNextKey()) { + String key = iterator.nextKey(); + result.put(key, readableMap.getString(key)); + } + + return result; + } } diff --git a/android/src/main/java/com/brentvatne/react/ReactVideoViewManager.java b/android/src/main/java/com/brentvatne/react/ReactVideoViewManager.java index eff426a248..7973121a57 100644 --- a/android/src/main/java/com/brentvatne/react/ReactVideoViewManager.java +++ b/android/src/main/java/com/brentvatne/react/ReactVideoViewManager.java @@ -21,6 +21,7 @@ public class ReactVideoViewManager extends SimpleViewManager { public static final String PROP_SRC = "src"; public static final String PROP_SRC_URI = "uri"; public static final String PROP_SRC_TYPE = "type"; + public static final String PROP_SRC_HEADERS = "requestHeaders"; public static final String PROP_SRC_IS_NETWORK = "isNetwork"; public static final String PROP_SRC_MAINVER = "mainVer"; public static final String PROP_SRC_PATCHVER = "patchVer"; @@ -86,6 +87,7 @@ public void setSrc(final ReactVideoView videoView, @Nullable ReadableMap src) { src.getString(PROP_SRC_TYPE), src.getBoolean(PROP_SRC_IS_NETWORK), src.getBoolean(PROP_SRC_IS_ASSET), + src.getMap(PROP_SRC_HEADERS), mainVer, patchVer ); @@ -95,8 +97,9 @@ public void setSrc(final ReactVideoView videoView, @Nullable ReadableMap src) { src.getString(PROP_SRC_URI), src.getString(PROP_SRC_TYPE), src.getBoolean(PROP_SRC_IS_NETWORK), - src.getBoolean(PROP_SRC_IS_ASSET) - ); + src.getBoolean(PROP_SRC_IS_ASSET), + src.getMap(PROP_SRC_HEADERS) + ); } } diff --git a/ios/RCTVideo.m b/ios/RCTVideo.m index 97b40c1ed9..8950a1743a 100644 --- a/ios/RCTVideo.m +++ b/ios/RCTVideo.m @@ -324,14 +324,21 @@ - (AVPlayerItem*)playerItemForSource:(NSDictionary *)source bool isAsset = [RCTConvert BOOL:[source objectForKey:@"isAsset"]]; NSString *uri = [source objectForKey:@"uri"]; NSString *type = [source objectForKey:@"type"]; - + NSDictionary *headers = [source objectForKey:@"requestHeaders"]; + NSURL *url = (isNetwork || isAsset) ? [NSURL URLWithString:uri] : [[NSURL alloc] initFileURLWithPath:[[NSBundle mainBundle] pathForResource:uri ofType:type]]; if (isNetwork) { + NSMutableDictionary *assetOptions = [[NSMutableDictionary alloc]init]; + if ([headers count] > 0) { + [assetOptions setObject:headers forKey:@"AVURLAssetHTTPHeaderFieldsKey"]; + } NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]; - AVURLAsset *asset = [AVURLAsset URLAssetWithURL:url options:@{AVURLAssetHTTPCookiesKey : cookies}]; + [assetOptions setObject:cookies forKey:AVURLAssetHTTPCookiesKey]; + + AVURLAsset *asset = [AVURLAsset URLAssetWithURL:url options:assetOptions]; return [AVPlayerItem playerItemWithAsset:asset]; } else if (isAsset) {