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

added optional request headers for remote assests (android & ios) #805

Merged
merged 15 commits into from
Jun 25, 2018
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions Video.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,39 @@ 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;
}

stringsOnlyObject(obj) {
const strObj = {};

Object.keys(obj).forEach(x => {
strObj[x] = obj[x].toString();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will set objects to [object Object] - do we want a check here for typeof obj[x] and to use JSON.stringify in those cases?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes you're correct - I was kinda of unsure whether to leave it or do a "smarter" string conversion. After your review, I choose the latter. 👍

Conversion is as follows:

  • Booleans, numbers, strings => prototype.toString
  • Objects and nulls => JSON.stringify'
  • Date => prototype.toISOString
  • undefined => returns empty string

});

return strObj;
}

seek = (time) => {
this.setNativeProps({ seek: time });
Expand Down Expand Up @@ -190,6 +223,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) : {}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the key here should be headers and not requestHeaders. That or VideoViewManager.PROP_SRC_HEADERS needs to be changed to requestHeaders. Without these changes, this line in VideoViewManager:

src.getMap(PROP_SRC_HEADERS)

throws a runtime error due to the fact that 'headers' is not a key on 'src'.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch! 🙏 I've just pushed updates. I can't test it out on Android right now - it would be nice if you could verify it works. 🙌

Thanks for the reviews btw! 👍

},
onVideoLoadStart: this._onLoadStart,
onVideoLoad: this._onLoad,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import okhttp3.Cookie;
import okhttp3.JavaNetCookieJar;
import okhttp3.OkHttpClient;
import java.util.Map;


public class DataSourceUtil {

Expand Down Expand Up @@ -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<String, String> requestHeaders) {
if (defaultDataSourceFactory == null || (requestHeaders != null && !requestHeaders.isEmpty())) {
defaultDataSourceFactory = buildDataSourceFactory(context, bandwidthMeter, requestHeaders);
}
return defaultDataSourceFactory;
}
Expand All @@ -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<String, String> 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<String, String> 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import com.brentvatne.receiver.BecomingNoisyListener;
import com.facebook.react.bridge.Dynamic;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.uimanager.ThemedReactContext;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.DefaultLoadControl;
Expand Down Expand Up @@ -58,6 +59,7 @@
import java.net.CookieManager;
import java.net.CookiePolicy;
import java.lang.Math;
import java.util.Map;
import java.lang.Object;

@SuppressLint("ViewConstructor")
Expand Down Expand Up @@ -107,6 +109,7 @@ class ReactExoplayerView extends FrameLayout implements
private float mProgressUpdateInterval = 250.0f;
private boolean playInBackground = false;
private boolean useTextureView = false;
private Map<String, String> requestHeaders;
// \ End props

// React
Expand Down Expand Up @@ -368,7 +371,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
Expand Down Expand Up @@ -589,14 +592,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<String, String> 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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,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;
Expand All @@ -23,6 +24,7 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
private static final String PROP_SRC = "src";
private static final String PROP_SRC_URI = "uri";
private static final String PROP_SRC_TYPE = "type";
private static final String PROP_SRC_HEADERS = "requestHeaders";
private static final String PROP_RESIZE_MODE = "resizeMode";
private static final String PROP_REPEAT = "repeat";
private static final String PROP_SELECTED_TEXT_TRACK = "selectedTextTrack";
Expand Down Expand Up @@ -78,6 +80,8 @@ public void setSrc(final ReactExoplayerView videoView, @Nullable ReadableMap src
Context context = videoView.getContext().getApplicationContext();
String uriString = src.hasKey(PROP_SRC_URI) ? src.getString(PROP_SRC_URI) : null;
String extension = src.hasKey(PROP_SRC_TYPE) ? src.getString(PROP_SRC_TYPE) : null;
Map<String, String> headers = src.hasKey(PROP_SRC_HEADERS) ? toStringMap(src.getMap(PROP_SRC_HEADERS)) : null;


if (TextUtils.isEmpty(uriString)) {
return;
Expand All @@ -87,7 +91,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(
Expand Down Expand Up @@ -196,4 +200,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<String, String> toStringMap(@Nullable ReadableMap readableMap) {
if (readableMap == null)
return null;

com.facebook.react.bridge.ReadableMapKeySetIterator iterator = readableMap.keySetIterator();
if (!iterator.hasNextKey())
return null;

Map<String, String> result = new HashMap<>();
while (iterator.hasNextKey()) {
String key = iterator.nextKey();
result.put(key, readableMap.getString(key));
}

return result;
}
}
50 changes: 44 additions & 6 deletions android/src/main/java/com/brentvatne/react/ReactVideoView.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -207,16 +211,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;

Expand Down Expand Up @@ -245,7 +250,11 @@ 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));
}

setDataSource(mThemedReactContext, parsedUrl, headers);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In master (of the main repo) no headers are being passed to setDataSource. Even though the headers HashMap is being created (lines 247-251) and the cookies are added to it.

Is it me or was the headers never used?

Luckily setDataSource had an overload that accepted headers which I could pass them to.

} else if (isAsset) {
if (uriString.startsWith("content://")) {
Uri parsedUrl = Uri.parse(uriString);
Expand Down Expand Up @@ -291,8 +300,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);
Expand Down Expand Up @@ -579,10 +593,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);
}

}
Expand Down Expand Up @@ -614,4 +628,28 @@ 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<String, String> toStringMap(@Nullable ReadableMap readableMap) {
if (readableMap == null)
return null;

com.facebook.react.bridge.ReadableMapKeySetIterator iterator = readableMap.keySetIterator();
if (!iterator.hasNextKey())
return null;

Map<String, String> result = new HashMap<>();
while (iterator.hasNextKey()) {
String key = iterator.nextKey();
result.put(key, readableMap.getString(key));
}

return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public class ReactVideoViewManager extends SimpleViewManager<ReactVideoView> {
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";
Expand Down Expand Up @@ -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
);
Expand All @@ -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)
);
}
}

Expand Down
11 changes: 9 additions & 2 deletions ios/RCTVideo.m
Original file line number Diff line number Diff line change
Expand Up @@ -321,14 +321,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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will ignore cookies if you opt to use headers. You should build up the options object so both are supported.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh, good point! 🙌 I see it now 👏

[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) {
Expand Down