Merge remote-tracking branch 'upstream/master'
# Conflicts: # ios/RCTVideo.m # package.json
This commit is contained in:
commit
05d4be2d9c
11
CHANGELOG.md
11
CHANGELOG.md
@ -1,5 +1,16 @@
|
|||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
|
### Version 3.0
|
||||||
|
* Inherit Android buildtools and SDK version from the root project [#1081](https://github.com/react-native-community/react-native-video/pull/1081)
|
||||||
|
* Automatically play on ExoPlayer when the paused prop is not set [#1083](https://github.com/react-native-community/react-native-video/pull/1083)
|
||||||
|
* Preserve Android MediaPlayer paused prop when backgrounding [#1082](https://github.com/react-native-community/react-native-video/pull/1082)
|
||||||
|
* Support specifying headers on ExoPlayer as part of the source [#805](https://github.com/react-native-community/react-native-video/pull/805)
|
||||||
|
* Prevent iOS onLoad event during seeking [#1088](https://github.com/react-native-community/react-native-video/pull/1088)
|
||||||
|
* ExoPlayer playableDuration incorrect [#1089](https://github.com/react-native-community/react-native-video/pull/1089)
|
||||||
|
|
||||||
|
### Version 2.3.1
|
||||||
|
* Revert PR to inherit Android SDK versions from root project. Re-add in 3.0 [#1080](https://github.com/react-native-community/react-native-video/pull/1080)
|
||||||
|
|
||||||
### Version 2.3.0
|
### Version 2.3.0
|
||||||
* Support allowsExternalPlayback on iOS [#1057](https://github.com/react-native-community/react-native-video/pull/1057)
|
* Support allowsExternalPlayback on iOS [#1057](https://github.com/react-native-community/react-native-video/pull/1057)
|
||||||
* Inherit Android buildtools and SDK version from the root project [#999](https://github.com/react-native-community/react-native-video/pull/999)
|
* Inherit Android buildtools and SDK version from the root project [#999](https://github.com/react-native-community/react-native-video/pull/999)
|
||||||
|
117
README.md
117
README.md
@ -5,10 +5,14 @@ A `<Video>` component for react-native, as seen in
|
|||||||
|
|
||||||
Requires react-native >= 0.40.0, for RN support of 0.19.0 - 0.39.0 please use a pre 1.0 version.
|
Requires react-native >= 0.40.0, for RN support of 0.19.0 - 0.39.0 please use a pre 1.0 version.
|
||||||
|
|
||||||
|
### Version 3.0 breaking changes
|
||||||
|
Version 3.0 features a number of changes to existing behavior. See [Updating](#updating) for changes.
|
||||||
|
|
||||||
## TOC
|
## TOC
|
||||||
|
|
||||||
* [Installation](#installation)
|
* [Installation](#installation)
|
||||||
* [Usage](#usage)
|
* [Usage](#usage)
|
||||||
|
* [Updating](#updating)
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@ -51,7 +55,7 @@ Note: you can also use the `ignoreSilentSwitch` prop, shown below.
|
|||||||
|
|
||||||
Run `react-native link` to link the react-native-video library.
|
Run `react-native link` to link the react-native-video library.
|
||||||
|
|
||||||
`react-native link` don’t works properly with the tvOS target so we need to add the library manually.
|
`react-native link` doesn’t work properly with the tvOS target so we need to add the library manually.
|
||||||
|
|
||||||
First select your project in Xcode.
|
First select your project in Xcode.
|
||||||
|
|
||||||
@ -191,8 +195,6 @@ using System.Collections.Generic;
|
|||||||
onFullscreenPlayerDidPresent={this.fullScreenPlayerDidPresent} // Callback after fullscreen started
|
onFullscreenPlayerDidPresent={this.fullScreenPlayerDidPresent} // Callback after fullscreen started
|
||||||
onFullscreenPlayerWillDismiss={this.fullScreenPlayerWillDismiss} // Callback before fullscreen stops
|
onFullscreenPlayerWillDismiss={this.fullScreenPlayerWillDismiss} // Callback before fullscreen stops
|
||||||
onFullscreenPlayerDidDismiss={this.fullScreenPlayerDidDismiss} // Callback after fullscreen stopped
|
onFullscreenPlayerDidDismiss={this.fullScreenPlayerDidDismiss} // Callback after fullscreen stopped
|
||||||
onProgress={this.setTime} // Callback every ~250ms with currentTime
|
|
||||||
onTimedMetadata={this.onTimedMetadata} // Callback when the stream receive some metadata
|
|
||||||
style={styles.backgroundVideo} />
|
style={styles.backgroundVideo} />
|
||||||
|
|
||||||
// Later to trigger fullscreen
|
// Later to trigger fullscreen
|
||||||
@ -239,6 +241,8 @@ var styles = StyleSheet.create({
|
|||||||
### Event props
|
### Event props
|
||||||
* [onLoad](#onload)
|
* [onLoad](#onload)
|
||||||
* [onLoadStart](#onloadstart)
|
* [onLoadStart](#onloadstart)
|
||||||
|
* [onProgress](#onprogress)
|
||||||
|
* [onTimedMetadata](#ontimedmetadata)
|
||||||
|
|
||||||
### Methods
|
### Methods
|
||||||
* [seek](#seek)
|
* [seek](#seek)
|
||||||
@ -492,9 +496,9 @@ Payload:
|
|||||||
|
|
||||||
Property | Description
|
Property | Description
|
||||||
--- | ---
|
--- | ---
|
||||||
isNetwork | Boolean indicating if the media is being loaded from the network
|
isNetwork | boolean | Boolean indicating if the media is being loaded from the network
|
||||||
type | Type of the media. Not available on Windows
|
type | string | Type of the media. Not available on Windows
|
||||||
uri | URI for the media source. Not available on Windows
|
uri | string | URI for the media source. Not available on Windows
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
```
|
```
|
||||||
@ -507,6 +511,46 @@ Example:
|
|||||||
|
|
||||||
Platforms: all
|
Platforms: all
|
||||||
|
|
||||||
|
#### onProgress
|
||||||
|
Callback function that is called every progressInterval seconds with info about which position the media is currently playing.
|
||||||
|
|
||||||
|
Property | Description
|
||||||
|
--- | ---
|
||||||
|
currentTime | number | Current position in seconds
|
||||||
|
playableDuration | number | Position to where the media can be played to using just the buffer in seconds
|
||||||
|
seekableDuration | number | Position to where the media can be seeked to in seconds. Typically, the total length of the media
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```
|
||||||
|
{
|
||||||
|
currentTime: 5.2,
|
||||||
|
playableDuration: 34.6,
|
||||||
|
seekableDuration: 888
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### onTimedMetadata
|
||||||
|
Callback function that is called when timed metadata becomes available
|
||||||
|
|
||||||
|
Payload:
|
||||||
|
|
||||||
|
Property | Type | Description
|
||||||
|
--- | --- | ---
|
||||||
|
metadata | array | Array of metadata objects
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```
|
||||||
|
{
|
||||||
|
metadata: [
|
||||||
|
{ value: 'Streaming Encoder', identifier: 'TRSN' },
|
||||||
|
{ value: 'Internet Stream', identifier: 'TRSO' },
|
||||||
|
{ value: 'Any Time You Like', identifier: 'TIT2' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Platforms: Android ExoPlayer, iOS
|
||||||
|
|
||||||
### Methods
|
### Methods
|
||||||
Methods operate on a ref to the Video element. You can create a ref using code like:
|
Methods operate on a ref to the Video element. You can create a ref using code like:
|
||||||
```
|
```
|
||||||
@ -558,14 +602,19 @@ For more detailed info check this [article](https://cocoacasts.com/how-to-add-ap
|
|||||||
</details>
|
</details>
|
||||||
|
|
||||||
### Android Expansion File Usage
|
### Android Expansion File Usage
|
||||||
Within your render function, assuming you have a file called
|
Expansions files allow you to ship assets that exceed the 100MB apk size limit and don't need to be updated each time you push an app update.
|
||||||
"background.mp4" in your expansion file. Just add your main and (if applicable) patch version
|
|
||||||
|
This only supports mp4 files and they must not be compressed. Example command line for preventing compression:
|
||||||
|
```bash
|
||||||
|
zip -r -n .mp4 *.mp4 player.video.example.com
|
||||||
```
|
```
|
||||||
<Video
|
|
||||||
source={{uri: "background", mainVer: 1, patchVer: 0}}
|
```javascript
|
||||||
/>
|
// Within your render function, assuming you have a file called
|
||||||
```
|
// "background.mp4" in your expansion file. Just add your main and (if applicable) patch version
|
||||||
This will look for an .mp4 file (background.mp4) in the given expansion version.
|
<Video source={{uri: "background", mainVer: 1, patchVer: 0}} // Looks for .mp4 file (background.mp4) in the given expansion version.
|
||||||
|
resizeMode="cover" // Fill the whole screen at aspect ratio.
|
||||||
|
style={styles.backgroundVideo} />
|
||||||
|
|
||||||
### Load files with the RN Asset System
|
### Load files with the RN Asset System
|
||||||
|
|
||||||
@ -598,9 +647,49 @@ To enable audio to play in background on iOS the audio session needs to be set t
|
|||||||
|
|
||||||
- [Lumpen Radio](https://github.com/jhabdas/lumpen-radio) contains another example integration using local files and full screen background video.
|
- [Lumpen Radio](https://github.com/jhabdas/lumpen-radio) contains another example integration using local files and full screen background video.
|
||||||
|
|
||||||
|
## Updating
|
||||||
|
|
||||||
|
### Version 3.0
|
||||||
|
|
||||||
|
#### All platforms now auto-play
|
||||||
|
Previously, on Android ExoPlayer if the paused prop was not set, the media would not automatically start playing. The only way it would work was if you set `paused={false}`. This has been changed to automatically play if paused is not set so that the behavior is consistent across platforms.
|
||||||
|
|
||||||
|
#### All platforms now keep their paused state when returning from the background
|
||||||
|
Previously, on Android MediaPlayer if you setup an AppState event when the app went into the background and set a paused prop so that when you returned to the app the video would be paused it would be ignored.
|
||||||
|
|
||||||
|
Note, Windows does not have a concept of an app going into the background, so this doesn't apply there.
|
||||||
|
|
||||||
|
#### Use Android SDK 27 by default
|
||||||
|
Version 3.0 updates the Android build tools and SDK to version 27. React Native is in the process of [switchting over](https://github.com/facebook/react-native/issues/18095#issuecomment-395596130) to SDK 27 in preparation for Google's requirement that new Android apps [use SDK 26](https://android-developers.googleblog.com/2017/12/improving-app-security-and-performance.html) by August 2018.
|
||||||
|
|
||||||
|
You will either need to install the version 27 SDK and version 27.0.3 buildtools or modify your build.gradle file to configure react-native-video to use the same build settings as the rest of your app as described below.
|
||||||
|
|
||||||
|
##### Using app build settings
|
||||||
|
You will need to create a `project.ext` section in the top-level build.gradle file (not app/build.gradle). Fill in the values from the example below using the values found in your app/build.gradle file.
|
||||||
|
```
|
||||||
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
|
||||||
|
buildscript {
|
||||||
|
... // Various other settings go here
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
... // Various other settings go here
|
||||||
|
|
||||||
|
project.ext {
|
||||||
|
compileSdkVersion = 23
|
||||||
|
buildToolsVersion = "23.0.1"
|
||||||
|
|
||||||
|
minSdkVersion = 16
|
||||||
|
targetSdkVersion = 22
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If you encounter an error `Could not find com.android.support:support-annotations:27.0.0.` reinstall your Android Support Repository.
|
||||||
|
|
||||||
## TODOS
|
## TODOS
|
||||||
|
|
||||||
- [ ] Add support for captions
|
|
||||||
- [ ] Add support for playing multiple videos in a sequence (will interfere with current `repeat` implementation)
|
- [ ] Add support for playing multiple videos in a sequence (will interfere with current `repeat` implementation)
|
||||||
- [x] Callback to get buffering progress for remote videos
|
- [x] Callback to get buffering progress for remote videos
|
||||||
- [ ] Bring API closer to HTML5 `<Video>` [reference](http://devdocs.io/html/element/video)
|
- [ ] Bring API closer to HTML5 `<Video>` [reference](http://devdocs.io/html/element/video)
|
||||||
|
24
Video.js
24
Video.js
@ -26,6 +26,29 @@ export default class Video extends Component {
|
|||||||
setNativeProps(nativeProps) {
|
setNativeProps(nativeProps) {
|
||||||
this._root.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) => {
|
seek = (time, tolerance = 100) => {
|
||||||
if (Platform.OS === 'ios') {
|
if (Platform.OS === 'ios') {
|
||||||
@ -202,6 +225,7 @@ export default class Video extends Component {
|
|||||||
type: source.type || '',
|
type: source.type || '',
|
||||||
mainVer: source.mainVer || 0,
|
mainVer: source.mainVer || 0,
|
||||||
patchVer: source.patchVer || 0,
|
patchVer: source.patchVer || 0,
|
||||||
|
requestHeaders: source.headers ? this.stringsOnlyObject(source.headers) : {}
|
||||||
},
|
},
|
||||||
onVideoLoadStart: this._onLoadStart,
|
onVideoLoadStart: this._onLoadStart,
|
||||||
onVideoLoad: this._onLoad,
|
onVideoLoad: this._onLoad,
|
||||||
|
@ -17,6 +17,8 @@ import com.google.android.exoplayer2.util.Util;
|
|||||||
import okhttp3.Cookie;
|
import okhttp3.Cookie;
|
||||||
import okhttp3.JavaNetCookieJar;
|
import okhttp3.JavaNetCookieJar;
|
||||||
import okhttp3.OkHttpClient;
|
import okhttp3.OkHttpClient;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
|
||||||
public class DataSourceUtil {
|
public class DataSourceUtil {
|
||||||
|
|
||||||
@ -49,9 +51,10 @@ public class DataSourceUtil {
|
|||||||
DataSourceUtil.rawDataSourceFactory = factory;
|
DataSourceUtil.rawDataSourceFactory = factory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static DataSource.Factory getDefaultDataSourceFactory(ReactContext context, DefaultBandwidthMeter bandwidthMeter) {
|
|
||||||
if (defaultDataSourceFactory == null) {
|
public static DataSource.Factory getDefaultDataSourceFactory(ReactContext context, DefaultBandwidthMeter bandwidthMeter, Map<String, String> requestHeaders) {
|
||||||
defaultDataSourceFactory = buildDataSourceFactory(context, bandwidthMeter);
|
if (defaultDataSourceFactory == null || (requestHeaders != null && !requestHeaders.isEmpty())) {
|
||||||
|
defaultDataSourceFactory = buildDataSourceFactory(context, bandwidthMeter, requestHeaders);
|
||||||
}
|
}
|
||||||
return defaultDataSourceFactory;
|
return defaultDataSourceFactory;
|
||||||
}
|
}
|
||||||
@ -64,17 +67,21 @@ public class DataSourceUtil {
|
|||||||
return new RawResourceDataSourceFactory(context.getApplicationContext());
|
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,
|
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();
|
OkHttpClient client = OkHttpClientProvider.getOkHttpClient();
|
||||||
CookieJarContainer container = (CookieJarContainer) client.cookieJar();
|
CookieJarContainer container = (CookieJarContainer) client.cookieJar();
|
||||||
ForwardingCookieHandler handler = new ForwardingCookieHandler(context);
|
ForwardingCookieHandler handler = new ForwardingCookieHandler(context);
|
||||||
container.setCookieJar(new JavaNetCookieJar(handler));
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,6 +66,7 @@ import java.net.CookieHandler;
|
|||||||
import java.net.CookieManager;
|
import java.net.CookieManager;
|
||||||
import java.net.CookiePolicy;
|
import java.net.CookiePolicy;
|
||||||
import java.lang.Math;
|
import java.lang.Math;
|
||||||
|
import java.util.Map;
|
||||||
import java.lang.Object;
|
import java.lang.Object;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
@ -103,7 +104,7 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
private boolean loadVideoStarted;
|
private boolean loadVideoStarted;
|
||||||
private boolean isFullscreen;
|
private boolean isFullscreen;
|
||||||
private boolean isInBackground;
|
private boolean isInBackground;
|
||||||
private boolean isPaused = true;
|
private boolean isPaused;
|
||||||
private boolean isBuffering;
|
private boolean isBuffering;
|
||||||
private float rate = 1f;
|
private float rate = 1f;
|
||||||
|
|
||||||
@ -118,6 +119,7 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
private float mProgressUpdateInterval = 250.0f;
|
private float mProgressUpdateInterval = 250.0f;
|
||||||
private boolean playInBackground = false;
|
private boolean playInBackground = false;
|
||||||
private boolean useTextureView = false;
|
private boolean useTextureView = false;
|
||||||
|
private Map<String, String> requestHeaders;
|
||||||
// \ End props
|
// \ End props
|
||||||
|
|
||||||
// React
|
// React
|
||||||
@ -135,7 +137,7 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
&& player.getPlayWhenReady()
|
&& player.getPlayWhenReady()
|
||||||
) {
|
) {
|
||||||
long pos = player.getCurrentPosition();
|
long pos = player.getCurrentPosition();
|
||||||
long bufferedDuration = player.getBufferedPercentage() * player.getDuration();
|
long bufferedDuration = player.getBufferedPercentage() * player.getDuration() / 100;
|
||||||
eventEmitter.progressChanged(pos, bufferedDuration, player.getDuration());
|
eventEmitter.progressChanged(pos, bufferedDuration, player.getDuration());
|
||||||
msg = obtainMessage(SHOW_PROGRESS);
|
msg = obtainMessage(SHOW_PROGRESS);
|
||||||
sendMessageDelayed(msg, Math.round(mProgressUpdateInterval));
|
sendMessageDelayed(msg, Math.round(mProgressUpdateInterval));
|
||||||
@ -417,7 +419,7 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
* @return A new DataSource factory.
|
* @return A new DataSource factory.
|
||||||
*/
|
*/
|
||||||
private DataSource.Factory buildDataSourceFactory(boolean useBandwidthMeter) {
|
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
|
// AudioManager.OnAudioFocusChangeListener implementation
|
||||||
@ -661,14 +663,15 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
|
|
||||||
// ReactExoplayerViewManager public api
|
// 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) {
|
if (uri != null) {
|
||||||
boolean isOriginalSourceNull = srcUri == null;
|
boolean isOriginalSourceNull = srcUri == null;
|
||||||
boolean isSourceEqual = uri.equals(srcUri);
|
boolean isSourceEqual = uri.equals(srcUri);
|
||||||
|
|
||||||
this.srcUri = uri;
|
this.srcUri = uri;
|
||||||
this.extension = extension;
|
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) {
|
if (!isOriginalSourceNull && !isSourceEqual) {
|
||||||
reloadSource();
|
reloadSource();
|
||||||
|
@ -13,6 +13,7 @@ import com.facebook.react.uimanager.ViewGroupManager;
|
|||||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||||
import com.google.android.exoplayer2.upstream.RawResourceDataSource;
|
import com.google.android.exoplayer2.upstream.RawResourceDataSource;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
@ -24,6 +25,7 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
|
|||||||
private static final String PROP_SRC = "src";
|
private static final String PROP_SRC = "src";
|
||||||
private static final String PROP_SRC_URI = "uri";
|
private static final String PROP_SRC_URI = "uri";
|
||||||
private static final String PROP_SRC_TYPE = "type";
|
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_RESIZE_MODE = "resizeMode";
|
||||||
private static final String PROP_REPEAT = "repeat";
|
private static final String PROP_REPEAT = "repeat";
|
||||||
private static final String PROP_SELECTED_TEXT_TRACK = "selectedTextTrack";
|
private static final String PROP_SELECTED_TEXT_TRACK = "selectedTextTrack";
|
||||||
@ -80,6 +82,8 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
|
|||||||
Context context = videoView.getContext().getApplicationContext();
|
Context context = videoView.getContext().getApplicationContext();
|
||||||
String uriString = src.hasKey(PROP_SRC_URI) ? src.getString(PROP_SRC_URI) : null;
|
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;
|
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)) {
|
if (TextUtils.isEmpty(uriString)) {
|
||||||
return;
|
return;
|
||||||
@ -89,7 +93,7 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
|
|||||||
Uri srcUri = Uri.parse(uriString);
|
Uri srcUri = Uri.parse(uriString);
|
||||||
|
|
||||||
if (srcUri != null) {
|
if (srcUri != null) {
|
||||||
videoView.setSrc(srcUri, extension);
|
videoView.setSrc(srcUri, extension, headers);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
int identifier = context.getResources().getIdentifier(
|
int identifier = context.getResources().getIdentifier(
|
||||||
@ -208,4 +212,28 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
|
|||||||
}
|
}
|
||||||
return ResizeMode.RESIZE_MODE_FIT;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ import com.android.vending.expansion.zipfile.APKExpansionSupport;
|
|||||||
import com.android.vending.expansion.zipfile.ZipResourceFile;
|
import com.android.vending.expansion.zipfile.ZipResourceFile;
|
||||||
import com.facebook.react.bridge.Arguments;
|
import com.facebook.react.bridge.Arguments;
|
||||||
import com.facebook.react.bridge.LifecycleEventListener;
|
import com.facebook.react.bridge.LifecycleEventListener;
|
||||||
|
import com.facebook.react.bridge.ReadableMap;
|
||||||
import com.facebook.react.bridge.WritableMap;
|
import com.facebook.react.bridge.WritableMap;
|
||||||
import com.facebook.react.uimanager.ThemedReactContext;
|
import com.facebook.react.uimanager.ThemedReactContext;
|
||||||
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||||
@ -30,6 +31,8 @@ import java.util.Map;
|
|||||||
import java.lang.Math;
|
import java.lang.Math;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
@SuppressLint("ViewConstructor")
|
@SuppressLint("ViewConstructor")
|
||||||
public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnPreparedListener, MediaPlayer
|
public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnPreparedListener, MediaPlayer
|
||||||
.OnErrorListener, MediaPlayer.OnBufferingUpdateListener, MediaPlayer.OnCompletionListener, MediaPlayer.OnInfoListener, LifecycleEventListener, MediaController.MediaPlayerControl {
|
.OnErrorListener, MediaPlayer.OnBufferingUpdateListener, MediaPlayer.OnCompletionListener, MediaPlayer.OnInfoListener, LifecycleEventListener, MediaController.MediaPlayerControl {
|
||||||
@ -89,6 +92,7 @@ public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnP
|
|||||||
|
|
||||||
private String mSrcUriString = null;
|
private String mSrcUriString = null;
|
||||||
private String mSrcType = "mp4";
|
private String mSrcType = "mp4";
|
||||||
|
private ReadableMap mRequestHeaders = null;
|
||||||
private boolean mSrcIsNetwork = false;
|
private boolean mSrcIsNetwork = false;
|
||||||
private boolean mSrcIsAsset = false;
|
private boolean mSrcIsAsset = false;
|
||||||
private ScalableType mResizeMode = ScalableType.LEFT_TOP;
|
private ScalableType mResizeMode = ScalableType.LEFT_TOP;
|
||||||
@ -101,8 +105,7 @@ public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnP
|
|||||||
private float mRate = 1.0f;
|
private float mRate = 1.0f;
|
||||||
private float mActiveRate = 1.0f;
|
private float mActiveRate = 1.0f;
|
||||||
private boolean mPlayInBackground = false;
|
private boolean mPlayInBackground = false;
|
||||||
private boolean mActiveStatePauseStatus = false;
|
private boolean mBackgroundPaused = false;
|
||||||
private boolean mActiveStatePauseStatusInitialized = false;
|
|
||||||
|
|
||||||
private int mMainVer = 0;
|
private int mMainVer = 0;
|
||||||
private int mPatchVer = 0;
|
private int mPatchVer = 0;
|
||||||
@ -128,7 +131,7 @@ public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnP
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
|
||||||
if (mMediaPlayerValid && !isCompleted &&!mPaused) {
|
if (mMediaPlayerValid && !isCompleted && !mPaused && !mBackgroundPaused) {
|
||||||
WritableMap event = Arguments.createMap();
|
WritableMap event = Arguments.createMap();
|
||||||
event.putDouble(EVENT_PROP_CURRENT_TIME, mMediaPlayer.getCurrentPosition() / 1000.0);
|
event.putDouble(EVENT_PROP_CURRENT_TIME, mMediaPlayer.getCurrentPosition() / 1000.0);
|
||||||
event.putDouble(EVENT_PROP_PLAYABLE_DURATION, mVideoBufferedDuration / 1000.0); //TODO:mBufferUpdateRunnable
|
event.putDouble(EVENT_PROP_PLAYABLE_DURATION, mVideoBufferedDuration / 1000.0); //TODO:mBufferUpdateRunnable
|
||||||
@ -207,16 +210,17 @@ public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnP
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSrc(final String uriString, final String type, final boolean isNetwork, final boolean isAsset) {
|
public void setSrc(final String uriString, final String type, final boolean isNetwork, final boolean isAsset, final ReadableMap requestHeaders) {
|
||||||
setSrc(uriString,type,isNetwork,isAsset,0,0);
|
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;
|
mSrcUriString = uriString;
|
||||||
mSrcType = type;
|
mSrcType = type;
|
||||||
mSrcIsNetwork = isNetwork;
|
mSrcIsNetwork = isNetwork;
|
||||||
mSrcIsAsset = isAsset;
|
mSrcIsAsset = isAsset;
|
||||||
|
mRequestHeaders = requestHeaders;
|
||||||
mMainVer = expansionMainVersion;
|
mMainVer = expansionMainVersion;
|
||||||
mPatchVer = expansionPatchVersion;
|
mPatchVer = expansionPatchVersion;
|
||||||
|
|
||||||
@ -245,7 +249,15 @@ public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnP
|
|||||||
headers.put("Cookie", cookie);
|
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) {
|
} else if (isAsset) {
|
||||||
if (uriString.startsWith("content://")) {
|
if (uriString.startsWith("content://")) {
|
||||||
Uri parsedUrl = Uri.parse(uriString);
|
Uri parsedUrl = Uri.parse(uriString);
|
||||||
@ -291,8 +303,13 @@ public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnP
|
|||||||
}
|
}
|
||||||
|
|
||||||
WritableMap src = Arguments.createMap();
|
WritableMap src = Arguments.createMap();
|
||||||
|
|
||||||
|
WritableMap wRequestHeaders = Arguments.createMap();
|
||||||
|
wRequestHeaders.merge(mRequestHeaders);
|
||||||
|
|
||||||
src.putString(ReactVideoViewManager.PROP_SRC_URI, uriString);
|
src.putString(ReactVideoViewManager.PROP_SRC_URI, uriString);
|
||||||
src.putString(ReactVideoViewManager.PROP_SRC_TYPE, type);
|
src.putString(ReactVideoViewManager.PROP_SRC_TYPE, type);
|
||||||
|
src.putMap(ReactVideoViewManager.PROP_SRC_HEADERS, wRequestHeaders);
|
||||||
src.putBoolean(ReactVideoViewManager.PROP_SRC_IS_NETWORK, isNetwork);
|
src.putBoolean(ReactVideoViewManager.PROP_SRC_IS_NETWORK, isNetwork);
|
||||||
if(mMainVer>0) {
|
if(mMainVer>0) {
|
||||||
src.putInt(ReactVideoViewManager.PROP_SRC_MAINVER, mMainVer);
|
src.putInt(ReactVideoViewManager.PROP_SRC_MAINVER, mMainVer);
|
||||||
@ -334,11 +351,6 @@ public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnP
|
|||||||
|
|
||||||
mPaused = paused;
|
mPaused = paused;
|
||||||
|
|
||||||
if ( !mActiveStatePauseStatusInitialized ) {
|
|
||||||
mActiveStatePauseStatus = mPaused;
|
|
||||||
mActiveStatePauseStatusInitialized = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mMediaPlayerValid) {
|
if (!mMediaPlayerValid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -410,8 +422,16 @@ public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnP
|
|||||||
if (mMediaPlayerValid) {
|
if (mMediaPlayerValid) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
if (!mPaused) { // Applying the rate while paused will cause the video to start
|
if (!mPaused) { // Applying the rate while paused will cause the video to start
|
||||||
mMediaPlayer.setPlaybackParams(mMediaPlayer.getPlaybackParams().setSpeed(rate));
|
/* Per https://stackoverflow.com/questions/39442522/setplaybackparams-causes-illegalstateexception
|
||||||
mActiveRate = rate;
|
* Some devices throw an IllegalStateException if you set the rate without first calling reset()
|
||||||
|
* TODO: Call reset() then reinitialize the player
|
||||||
|
*/
|
||||||
|
try {
|
||||||
|
mMediaPlayer.setPlaybackParams(mMediaPlayer.getPlaybackParams().setSpeed(rate));
|
||||||
|
mActiveRate = rate;
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(ReactVideoViewManager.REACT_CLASS, "Unable to set rate, unsupported on this device");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Log.e(ReactVideoViewManager.REACT_CLASS, "Setting playback rate is not yet supported on Android versions below 6.0");
|
Log.e(ReactVideoViewManager.REACT_CLASS, "Setting playback rate is not yet supported on Android versions below 6.0");
|
||||||
@ -579,39 +599,62 @@ public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnP
|
|||||||
super.onAttachedToWindow();
|
super.onAttachedToWindow();
|
||||||
|
|
||||||
if(mMainVer>0) {
|
if(mMainVer>0) {
|
||||||
setSrc(mSrcUriString, mSrcType, mSrcIsNetwork,mSrcIsAsset,mMainVer,mPatchVer);
|
setSrc(mSrcUriString, mSrcType, mSrcIsNetwork, mSrcIsAsset, mRequestHeaders, mMainVer, mPatchVer);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
setSrc(mSrcUriString, mSrcType, mSrcIsNetwork,mSrcIsAsset);
|
setSrc(mSrcUriString, mSrcType, mSrcIsNetwork, mSrcIsAsset, mRequestHeaders);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onHostPause() {
|
public void onHostPause() {
|
||||||
if (mMediaPlayer != null && !mPlayInBackground) {
|
if (mMediaPlayerValid && !mPaused && !mPlayInBackground) {
|
||||||
mActiveStatePauseStatus = mPaused;
|
/* Pause the video in background
|
||||||
|
* Don't update the paused prop, developers should be able to update it on background
|
||||||
// Pause the video in background
|
* so that when you return to the app the video is paused
|
||||||
setPausedModifier(true);
|
*/
|
||||||
|
mBackgroundPaused = true;
|
||||||
|
mMediaPlayer.pause();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onHostResume() {
|
public void onHostResume() {
|
||||||
if (mMediaPlayer != null && !mPlayInBackground) {
|
mBackgroundPaused = false;
|
||||||
|
if (mMediaPlayerValid && !mPlayInBackground && !mPaused) {
|
||||||
new Handler().post(new Runnable() {
|
new Handler().post(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
// Restore original state
|
// Restore original state
|
||||||
setPausedModifier(mActiveStatePauseStatus);
|
setPausedModifier(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onHostDestroy() {
|
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) {
|
||||||
|
Map<String, String> 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ public class ReactVideoViewManager extends SimpleViewManager<ReactVideoView> {
|
|||||||
public static final String PROP_SRC = "src";
|
public static final String PROP_SRC = "src";
|
||||||
public static final String PROP_SRC_URI = "uri";
|
public static final String PROP_SRC_URI = "uri";
|
||||||
public static final String PROP_SRC_TYPE = "type";
|
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_IS_NETWORK = "isNetwork";
|
||||||
public static final String PROP_SRC_MAINVER = "mainVer";
|
public static final String PROP_SRC_MAINVER = "mainVer";
|
||||||
public static final String PROP_SRC_PATCHVER = "patchVer";
|
public static final String PROP_SRC_PATCHVER = "patchVer";
|
||||||
@ -86,6 +87,7 @@ public class ReactVideoViewManager extends SimpleViewManager<ReactVideoView> {
|
|||||||
src.getString(PROP_SRC_TYPE),
|
src.getString(PROP_SRC_TYPE),
|
||||||
src.getBoolean(PROP_SRC_IS_NETWORK),
|
src.getBoolean(PROP_SRC_IS_NETWORK),
|
||||||
src.getBoolean(PROP_SRC_IS_ASSET),
|
src.getBoolean(PROP_SRC_IS_ASSET),
|
||||||
|
src.getMap(PROP_SRC_HEADERS),
|
||||||
mainVer,
|
mainVer,
|
||||||
patchVer
|
patchVer
|
||||||
);
|
);
|
||||||
@ -95,8 +97,9 @@ public class ReactVideoViewManager extends SimpleViewManager<ReactVideoView> {
|
|||||||
src.getString(PROP_SRC_URI),
|
src.getString(PROP_SRC_URI),
|
||||||
src.getString(PROP_SRC_TYPE),
|
src.getString(PROP_SRC_TYPE),
|
||||||
src.getBoolean(PROP_SRC_IS_NETWORK),
|
src.getBoolean(PROP_SRC_IS_NETWORK),
|
||||||
src.getBoolean(PROP_SRC_IS_ASSET)
|
src.getBoolean(PROP_SRC_IS_ASSET),
|
||||||
);
|
src.getMap(PROP_SRC_HEADERS)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
150
ios/RCTVideo.m
150
ios/RCTVideo.m
@ -26,6 +26,7 @@ static NSString *const timedMetadata = @"timedMetadata";
|
|||||||
/* Required to publish events */
|
/* Required to publish events */
|
||||||
RCTEventDispatcher *_eventDispatcher;
|
RCTEventDispatcher *_eventDispatcher;
|
||||||
BOOL _playbackRateObserverRegistered;
|
BOOL _playbackRateObserverRegistered;
|
||||||
|
BOOL _videoLoadStarted;
|
||||||
|
|
||||||
bool _pendingSeek;
|
bool _pendingSeek;
|
||||||
float _pendingSeekTime;
|
float _pendingSeekTime;
|
||||||
@ -96,7 +97,7 @@ static NSString *const timedMetadata = @"timedMetadata";
|
|||||||
|
|
||||||
- (AVPlayerViewController*)createPlayerViewController:(AVPlayer*)player withPlayerItem:(AVPlayerItem*)playerItem {
|
- (AVPlayerViewController*)createPlayerViewController:(AVPlayer*)player withPlayerItem:(AVPlayerItem*)playerItem {
|
||||||
RCTVideoPlayerViewController* playerLayer= [[RCTVideoPlayerViewController alloc] init];
|
RCTVideoPlayerViewController* playerLayer= [[RCTVideoPlayerViewController alloc] init];
|
||||||
playerLayer.showsPlaybackControls = NO;
|
playerLayer.showsPlaybackControls = YES;
|
||||||
playerLayer.rctDelegate = self;
|
playerLayer.rctDelegate = self;
|
||||||
playerLayer.view.frame = self.bounds;
|
playerLayer.view.frame = self.bounds;
|
||||||
playerLayer.player = player;
|
playerLayer.player = player;
|
||||||
@ -324,6 +325,7 @@ static NSString *const timedMetadata = @"timedMetadata";
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
_videoLoadStarted = YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSURL*) urlFilePath:(NSString*) filepath {
|
- (NSURL*) urlFilePath:(NSString*) filepath {
|
||||||
@ -350,14 +352,24 @@ static NSString *const timedMetadata = @"timedMetadata";
|
|||||||
NSString *uri = [source objectForKey:@"uri"];
|
NSString *uri = [source objectForKey:@"uri"];
|
||||||
NSString *subtitleUri = _selectedTextTrack[@"uri"];
|
NSString *subtitleUri = _selectedTextTrack[@"uri"];
|
||||||
NSString *type = [source objectForKey:@"type"];
|
NSString *type = [source objectForKey:@"type"];
|
||||||
|
NSDictionary *headers = [source objectForKey:@"requestHeaders"];
|
||||||
|
|
||||||
AVURLAsset *asset;
|
AVURLAsset *asset;
|
||||||
AVURLAsset *subAsset;
|
AVURLAsset *subAsset;
|
||||||
|
|
||||||
if (isNetwork) {
|
if (isNetwork) {
|
||||||
|
NSMutableDictionary *assetOptions = [[NSMutableDictionary alloc]init];
|
||||||
|
/* Per #1091, this is not a public API. We need to either get approval from Apple to use this
|
||||||
|
* or use a different approach.
|
||||||
|
if ([headers count] > 0) {
|
||||||
|
[assetOptions setObject:headers forKey:@"AVURLAssetHTTPHeaderFieldsKey"];
|
||||||
|
}
|
||||||
|
*/
|
||||||
NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies];
|
NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies];
|
||||||
asset = [AVURLAsset URLAssetWithURL:[NSURL URLWithString:uri] options:@{AVURLAssetHTTPCookiesKey : cookies}];
|
[assetOptions setObject:cookies forKey:AVURLAssetHTTPCookiesKey];
|
||||||
subAsset = [AVURLAsset URLAssetWithURL:[NSURL URLWithString:subtitleUri] options:@{AVURLAssetHTTPCookiesKey : cookies}];
|
|
||||||
|
asset = [AVURLAsset URLAssetWithURL:[NSURL URLWithString:uri] options:assetOptions];
|
||||||
|
subAsset = [AVURLAsset URLAssetWithURL:[NSURL URLWithString:subtitleUri] options:assetOptions];
|
||||||
}
|
}
|
||||||
else if (isAsset) // assets on iOS have to be in the Documents folder
|
else if (isAsset) // assets on iOS have to be in the Documents folder
|
||||||
{
|
{
|
||||||
@ -404,66 +416,62 @@ static NSString *const timedMetadata = @"timedMetadata";
|
|||||||
|
|
||||||
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
|
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
|
||||||
{
|
{
|
||||||
if (object == _playerItem) {
|
if (object == _playerItem) {
|
||||||
|
|
||||||
// When timeMetadata is read the event onTimedMetadata is triggered
|
// When timeMetadata is read the event onTimedMetadata is triggered
|
||||||
if ([keyPath isEqualToString: timedMetadata])
|
if ([keyPath isEqualToString:timedMetadata]) {
|
||||||
{
|
NSArray<AVMetadataItem *> *items = [change objectForKey:@"new"];
|
||||||
|
if (items && ![items isEqual:[NSNull null]] && items.count > 0) {
|
||||||
|
NSMutableArray *array = [NSMutableArray new];
|
||||||
NSArray<AVMetadataItem *> *items = [change objectForKey:@"new"];
|
for (AVMetadataItem *item in items) {
|
||||||
if (items && ![items isEqual:[NSNull null]] && items.count > 0) {
|
NSString *value = item.value;
|
||||||
|
NSString *identifier = item.identifier;
|
||||||
NSMutableArray *array = [NSMutableArray new];
|
|
||||||
for (AVMetadataItem *item in items) {
|
if (![value isEqual: [NSNull null]]) {
|
||||||
|
NSDictionary *dictionary = [[NSDictionary alloc] initWithObjects:@[value, identifier] forKeys:@[@"value", @"identifier"]];
|
||||||
NSString *value = item.value;
|
|
||||||
NSString *identifier = item.identifier;
|
[array addObject:dictionary];
|
||||||
|
}
|
||||||
if (![value isEqual: [NSNull null]]) {
|
|
||||||
NSDictionary *dictionary = [[NSDictionary alloc] initWithObjects:@[value, identifier] forKeys:@[@"value", @"identifier"]];
|
|
||||||
|
|
||||||
[array addObject:dictionary];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.onTimedMetadata(@{
|
|
||||||
@"target": self.reactTag,
|
|
||||||
@"metadata": array
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.onTimedMetadata(@{
|
||||||
|
@"target": self.reactTag,
|
||||||
|
@"metadata": array
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ([keyPath isEqualToString:statusKeyPath]) {
|
if ([keyPath isEqualToString:statusKeyPath]) {
|
||||||
// Handle player item status change.
|
// Handle player item status change.
|
||||||
if (_playerItem.status == AVPlayerItemStatusReadyToPlay) {
|
if (_playerItem.status == AVPlayerItemStatusReadyToPlay) {
|
||||||
float duration = CMTimeGetSeconds(_playerItem.asset.duration);
|
float duration = CMTimeGetSeconds(_playerItem.asset.duration);
|
||||||
|
|
||||||
if (isnan(duration)) {
|
if (isnan(duration)) {
|
||||||
duration = 0.0;
|
duration = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
NSObject *width = @"undefined";
|
NSObject *width = @"undefined";
|
||||||
NSObject *height = @"undefined";
|
NSObject *height = @"undefined";
|
||||||
NSString *orientation = @"undefined";
|
NSString *orientation = @"undefined";
|
||||||
|
|
||||||
if ([_playerItem.asset tracksWithMediaType:AVMediaTypeVideo].count > 0) {
|
if ([_playerItem.asset tracksWithMediaType:AVMediaTypeVideo].count > 0) {
|
||||||
AVAssetTrack *videoAsset = [[_playerItem.asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
|
|
||||||
|
|
||||||
width = [NSNumber numberWithFloat:videoAsset.naturalSize.width];
|
|
||||||
height = [NSNumber numberWithFloat:videoAsset.naturalSize.height];
|
|
||||||
CGAffineTransform preferredTransform = [videoAsset preferredTransform];
|
|
||||||
|
|
||||||
if ((videoAsset.naturalSize.width == preferredTransform.tx
|
AVAssetTrack *videoTrack = [[_playerItem.asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
|
||||||
&& videoAsset.naturalSize.height == preferredTransform.ty)
|
width = [NSNumber numberWithFloat:videoTrack.naturalSize.width];
|
||||||
|| (preferredTransform.tx == 0 && preferredTransform.ty == 0))
|
height = [NSNumber numberWithFloat:videoTrack.naturalSize.height];
|
||||||
|
CGAffineTransform preferredTransform = [videoTrack preferredTransform];
|
||||||
|
|
||||||
|
if ((videoTrack.naturalSize.width == preferredTransform.tx
|
||||||
|
&& videoTrack.naturalSize.height == preferredTransform.ty)
|
||||||
|
|| (preferredTransform.tx == 0 && preferredTransform.ty == 0))
|
||||||
|
|
||||||
{
|
{
|
||||||
orientation = @"landscape";
|
orientation = @"landscape";
|
||||||
} else
|
} else {
|
||||||
orientation = @"portrait";
|
orientation = @"portrait";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(self.onVideoLoad) {
|
if (self.onVideoLoad && _videoLoadStarted) {
|
||||||
self.onVideoLoad(@{@"duration": [NSNumber numberWithFloat:duration],
|
self.onVideoLoad(@{@"duration": [NSNumber numberWithFloat:duration],
|
||||||
@"currentTime": [NSNumber numberWithFloat:CMTimeGetSeconds(_playerItem.currentTime)],
|
@"currentTime": [NSNumber numberWithFloat:CMTimeGetSeconds(_playerItem.currentTime)],
|
||||||
@"canPlayReverse": [NSNumber numberWithBool:_playerItem.canPlayReverse],
|
@"canPlayReverse": [NSNumber numberWithBool:_playerItem.canPlayReverse],
|
||||||
@ -473,21 +481,21 @@ static NSString *const timedMetadata = @"timedMetadata";
|
|||||||
@"canStepBackward": [NSNumber numberWithBool:_playerItem.canStepBackward],
|
@"canStepBackward": [NSNumber numberWithBool:_playerItem.canStepBackward],
|
||||||
@"canStepForward": [NSNumber numberWithBool:_playerItem.canStepForward],
|
@"canStepForward": [NSNumber numberWithBool:_playerItem.canStepForward],
|
||||||
@"naturalSize": @{
|
@"naturalSize": @{
|
||||||
@"width": width,
|
@"width": width,
|
||||||
@"height": height,
|
@"height": height,
|
||||||
@"orientation": orientation
|
@"orientation": orientation
|
||||||
},
|
},
|
||||||
@"textTracks": [self getTextTrackInfo],
|
@"textTracks": [self getTextTrackInfo],
|
||||||
@"target": self.reactTag});
|
@"target": self.reactTag});
|
||||||
}
|
}
|
||||||
|
_videoLoadStarted = NO;
|
||||||
|
|
||||||
[self attachListeners];
|
[self attachListeners];
|
||||||
[self applyModifiers];
|
[self applyModifiers];
|
||||||
} else if(_playerItem.status == AVPlayerItemStatusFailed && self.onVideoError) {
|
} else if (_playerItem.status == AVPlayerItemStatusFailed && self.onVideoError) {
|
||||||
self.onVideoError(@{@"error": @{@"code": [NSNumber numberWithInteger: _playerItem.error.code],
|
self.onVideoError(@{@"error": @{@"code": [NSNumber numberWithInteger: _playerItem.error.code],
|
||||||
@"domain": _playerItem.error.domain},
|
@"domain": _playerItem.error.domain},
|
||||||
@"target": self.reactTag});
|
@"target": self.reactTag});
|
||||||
}
|
}
|
||||||
} else if ([keyPath isEqualToString:playbackBufferEmptyKeyPath]) {
|
} else if ([keyPath isEqualToString:playbackBufferEmptyKeyPath]) {
|
||||||
_playerBufferEmpty = YES;
|
_playerBufferEmpty = YES;
|
||||||
@ -500,28 +508,28 @@ static NSString *const timedMetadata = @"timedMetadata";
|
|||||||
_playerBufferEmpty = NO;
|
_playerBufferEmpty = NO;
|
||||||
self.onVideoBuffer(@{@"isBuffering": @(NO), @"target": self.reactTag});
|
self.onVideoBuffer(@{@"isBuffering": @(NO), @"target": self.reactTag});
|
||||||
}
|
}
|
||||||
} else if (object == _playerLayer) {
|
} else if (object == _playerLayer) {
|
||||||
if([keyPath isEqualToString:readyForDisplayKeyPath] && [change objectForKey:NSKeyValueChangeNewKey]) {
|
if([keyPath isEqualToString:readyForDisplayKeyPath] && [change objectForKey:NSKeyValueChangeNewKey]) {
|
||||||
if([change objectForKey:NSKeyValueChangeNewKey] && self.onReadyForDisplay) {
|
if([change objectForKey:NSKeyValueChangeNewKey] && self.onReadyForDisplay) {
|
||||||
self.onReadyForDisplay(@{@"target": self.reactTag});
|
self.onReadyForDisplay(@{@"target": self.reactTag});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (object == _player) {
|
} else if (object == _player) {
|
||||||
if([keyPath isEqualToString:playbackRate]) {
|
if([keyPath isEqualToString:playbackRate]) {
|
||||||
if(self.onPlaybackRateChange) {
|
if(self.onPlaybackRateChange) {
|
||||||
self.onPlaybackRateChange(@{@"playbackRate": [NSNumber numberWithFloat:_player.rate],
|
self.onPlaybackRateChange(@{@"playbackRate": [NSNumber numberWithFloat:_player.rate],
|
||||||
@"target": self.reactTag});
|
@"target": self.reactTag});
|
||||||
}
|
|
||||||
if(_playbackStalled && _player.rate > 0) {
|
|
||||||
if(self.onPlaybackResume) {
|
|
||||||
self.onPlaybackResume(@{@"playbackRate": [NSNumber numberWithFloat:_player.rate],
|
|
||||||
@"target": self.reactTag});
|
|
||||||
}
|
|
||||||
_playbackStalled = NO;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if(_playbackStalled && _player.rate > 0) {
|
||||||
|
if(self.onPlaybackResume) {
|
||||||
|
self.onPlaybackResume(@{@"playbackRate": [NSNumber numberWithFloat:_player.rate],
|
||||||
|
@"target": self.reactTag});
|
||||||
|
}
|
||||||
|
_playbackStalled = NO;
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
|
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
120
package.json
120
package.json
@ -1,83 +1,49 @@
|
|||||||
{
|
{
|
||||||
"_args": [
|
"name": "react-native-video",
|
||||||
[
|
"version": "3.0.0",
|
||||||
"git://github.com/nfb-onf/react-native-video.git",
|
"description": "A <Video /> element for react-native",
|
||||||
"/Users/amishra/Development/react_films"
|
"main": "Video.js",
|
||||||
]
|
"license": "MIT",
|
||||||
],
|
"author": "Brent Vatne <brentvatne@gmail.com> (https://github.com/brentvatne)",
|
||||||
"_from": "git://github.com/nfb-onf/react-native-video.git",
|
"contributors": [
|
||||||
"_id": "react-native-video@git://github.com/nfb-onf/react-native-video.git#8ce39e5b82108e6b9ea8549bd72ba58e95f04647",
|
{
|
||||||
"_inBundle": false,
|
"name": "Isaiah Grey",
|
||||||
"_integrity": "",
|
"email": "isaiahgrey@gmail.com"
|
||||||
"_location": "/react-native-video",
|
},
|
||||||
"_phantomChildren": {},
|
{
|
||||||
"_requested": {
|
"name": "Johannes Lumpe",
|
||||||
"type": "git",
|
"email": "johannes@lum.pe"
|
||||||
"raw": "git://github.com/nfb-onf/react-native-video.git",
|
},
|
||||||
"rawSpec": "git://github.com/nfb-onf/react-native-video.git",
|
{
|
||||||
"saveSpec": "git://github.com/nfb-onf/react-native-video.git",
|
"name": "Baris Sencan",
|
||||||
"fetchSpec": "git://github.com/nfb-onf/react-native-video.git",
|
"email": "baris.sncn@gmail.com"
|
||||||
"gitCommittish": null
|
},
|
||||||
},
|
{
|
||||||
"_requiredBy": [
|
"name": "Hampton Maxwell",
|
||||||
"/"
|
"email": "me@hamptonmaxwell.com"
|
||||||
],
|
}
|
||||||
"_resolved": "git://github.com/nfb-onf/react-native-video.git#8ce39e5b82108e6b9ea8549bd72ba58e95f04647",
|
],
|
||||||
"_spec": "git://github.com/nfb-onf/react-native-video.git",
|
"repository": {
|
||||||
"_where": "/Users/amishra/Development/react_films",
|
"type": "git",
|
||||||
"author": {
|
"url": "git@github.com:brentvatne/react-native-video.git"
|
||||||
"name": "Brent Vatne",
|
|
||||||
"email": "brentvatne@gmail.com",
|
|
||||||
"url": "https://github.com/brentvatne"
|
|
||||||
},
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/brentvatne/react-native-video/issues"
|
|
||||||
},
|
|
||||||
"contributors": [
|
|
||||||
{
|
|
||||||
"name": "Isaiah Grey",
|
|
||||||
"email": "isaiahgrey@gmail.com"
|
|
||||||
},
|
},
|
||||||
{
|
"devDependencies": {
|
||||||
"name": "Johannes Lumpe",
|
"jest-cli": "0.2.1",
|
||||||
"email": "johannes@lum.pe"
|
"eslint": "1.10.3",
|
||||||
|
"babel-eslint": "5.0.0-beta8",
|
||||||
|
"eslint-plugin-react": "3.16.1",
|
||||||
|
"eslint-config-airbnb": "4.0.0"
|
||||||
},
|
},
|
||||||
{
|
"dependencies": {
|
||||||
"name": "Baris Sencan",
|
"keymirror": "0.1.1",
|
||||||
"email": "baris.sncn@gmail.com"
|
"prop-types": "^15.5.10"
|
||||||
},
|
},
|
||||||
{
|
"scripts": {
|
||||||
"name": "Hampton Maxwell",
|
"test": "node_modules/.bin/eslint *.js"
|
||||||
"email": "me@hamptonmaxwell.com"
|
},
|
||||||
|
"rnpm": {
|
||||||
|
"android": {
|
||||||
|
"sourceDir": "./android-exoplayer"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
|
||||||
"dependencies": {
|
|
||||||
"keymirror": "0.1.1",
|
|
||||||
"prop-types": "^15.5.10"
|
|
||||||
},
|
|
||||||
"description": "A <Video /> element for react-native",
|
|
||||||
"devDependencies": {
|
|
||||||
"babel-eslint": "5.0.0-beta8",
|
|
||||||
"eslint": "1.10.3",
|
|
||||||
"eslint-config-airbnb": "4.0.0",
|
|
||||||
"eslint-plugin-react": "3.16.1",
|
|
||||||
"jest-cli": "0.2.1"
|
|
||||||
},
|
|
||||||
"homepage": "https://github.com/brentvatne/react-native-video#readme",
|
|
||||||
"license": "MIT",
|
|
||||||
"main": "Video.js",
|
|
||||||
"name": "react-native-video",
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "git+ssh://git@github.com/brentvatne/react-native-video.git"
|
|
||||||
},
|
|
||||||
"rnpm": {
|
|
||||||
"android": {
|
|
||||||
"sourceDir": "./android-exoplayer"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"test": "eslint *.js"
|
|
||||||
},
|
|
||||||
"version": "2.2.0"
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user