Merge pull request #1 from react-native-community/master

RN 0.60
This commit is contained in:
Axel Vencatareddy 2019-09-28 22:37:41 +02:00 committed by GitHub
commit 0583f4a0af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 6000 additions and 3541 deletions

66
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,66 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
# Bug
<!--
Please provide a clear and concise description of what the bug is.
Include screenshots if needed.
Please test using the latest release of the library, as maybe said bug has been already fixed.
If the library has multiple install methods, describe installation method (e.g., pod, not pod, with jetifier etc)
-->
## Platform
<!--
Platform where your bug is happening. If Android, report if using Android or Android Exoplayer
-->
Which player are you experiencing the problem on:
* iOS
* Android ExoPlayer
* Android MediaPlayer
* Windows UWP
* Windows WPF
## Environment info
<!--
Run `react-native info` in your terminal and copy the results here. Also, include the *precise* version number of this library that you are using in the project
-->
React native info output:
```bash
// paste it here
```
Library version: x.x.x
## Steps To Reproduce
<!--
Issues without reproduction steps or code are likely to stall.
-->
1.
2.
...
## Expected behaviour
1.
2.
## Reproducible sample code
<!--
Please add to your issue a repro, a fresh codebase with the minimal changes so that the bug can be tested in isolation
-->
## Video sample
If possible, include a link to the video that has the problem that can be streamed or downloaded from.

View File

@ -0,0 +1,32 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
# Feature Request
<!--
This issue should serve for you to present or pitch an idea to the maintainers - but remember that it would be better if you were to submit a PR instead 🤗
-->
## Why it is needed
<!--
Please tell us a bit more of why you want this feature to be added, what's its origin
-->
## Possible implementation
<!--
It really helps if you could describe from a technical POV how this new feature would work, which code it rely on, etc
-->
### Code sample
<!--
Please show how the new code could work, if doable
-->

View File

@ -1,5 +1,38 @@
## Changelog ## Changelog
### Version 5.1.0-alpha1
* Fixed Exoplayer doesn't work with mute=true (Android). [#1696](https://github.com/react-native-community/react-native-video/pull/1696)
* Added support for automaticallyWaitsToMinimizeStalling property (iOS) [#1723](https://github.com/react-native-community/react-native-video/pull/1723)
* Bump Exoplayer to 2.10.4, remove deprecated usages of Exoplayer methods (Android). [#1753](https://github.com/react-native-community/react-native-video/pull/1753)
* Preserve Exoplayer BandwidthMeter instance across video plays, this should noticeably improve streaming bandwidth detection (Android).
### Version 5.0.2
* Fix crash when RCTVideo's superclass doesn't observe the keyPath 'frame' (iOS) [#1720](https://github.com/react-native-community/react-native-video/pull/1720)
### Version 5.0.1
* Fix AndroidX Support bad merge
### Version 5.0.0 [Deprecated]
* AndroidX Support
### Version 4.4.4
* Handle racing conditions when props are setted on exoplayer
### Version 4.4.3
* Fix mute/unmute when controls are present (iOS) [#1654](https://github.com/react-native-community/react-native-video/pull/1654)
* Fix Android videos being able to play with background music/audio from other apps.
* Fixed memory leak on iOS when using `controls` [#1647](https://github.com/react-native-community/react-native-video/pull/1647)
* (Android) Update gradle and target SDK [#1629](https://github.com/react-native-community/react-native-video/pull/1629)
* Fix iOS stressed mount/unmount crash [#1646](https://github.com/react-native-community/react-native-video/pull/1646)
### Version 4.4.2
* Change compileOnly to implementation on gradle (for newer gradle versions and react-native 0.59 support) [#1592](https://github.com/react-native-community/react-native-video/pull/1592)
* Replaced RCTBubblingEventBlock events by RCTDirectEventBlock to avoid event name collisions [#1625](https://github.com/react-native-community/react-native-video/pull/1625)
* Added `onPlaybackRateChange` to README [#1578](https://github.com/react-native-community/react-native-video/pull/1578)
* Added `onReadyForDisplay` to README [#1627](https://github.com/react-native-community/react-native-video/pull/1627)
* Improved handling of poster image. Fixes bug with displaying video and poster simultaneously. [#1627](https://github.com/react-native-community/react-native-video/pull/1627)
* Fix background audio stopping on iOS when using `controls` [#1614](https://github.com/react-native-community/react-native-video/pull/1614)
### Version 4.4.1 ### Version 4.4.1
* Fix tvOS picture-in-picture compilation regression [#1518](https://github.com/react-native-community/react-native-video/pull/1518) * Fix tvOS picture-in-picture compilation regression [#1518](https://github.com/react-native-community/react-native-video/pull/1518)
* fullscreen rotation issues with iOS built-in controls [#1441](https://github.com/react-native-community/react-native-video/pull/1441) * fullscreen rotation issues with iOS built-in controls [#1441](https://github.com/react-native-community/react-native-video/pull/1441)

147
README.md
View File

@ -3,10 +3,16 @@
A `<Video>` component for react-native, as seen in A `<Video>` component for react-native, as seen in
[react-native-login](https://github.com/brentvatne/react-native-login)! [react-native-login](https://github.com/brentvatne/react-native-login)!
Version 5.x recommends react-native >= 0.60.0 for Android 64bit builds and Android X support.
Version 4.x requires react-native >= 0.57.0 Version 4.x requires react-native >= 0.57.0
Version 3.x requires react-native >= 0.40.0 Version 3.x requires react-native >= 0.40.0
### Version 5.0.0 breaking changes
Version 5 introduces breaking changes on Android, please check carefully the steps described there: [Android Installation](#Android-installation)
### Version 4.0.0 breaking changes ### Version 4.0.0 breaking changes
Version 4.0.0 changes some behaviors and may require updates to your Gradle files. See [Updating](#updating) for details. Version 4.0.0 changes some behaviors and may require updates to your Gradle files. See [Updating](#updating) for details.
@ -20,6 +26,11 @@ Version 3.0 features a number of changes to existing behavior. See [Updating](#u
## Table of Contents ## Table of Contents
* [Installation](#installation) * [Installation](#installation)
* [iOS](#ios-installation)
* [tvOS](#tvos-installation)
* [Android](#android-installation)
* [Windows](#windows-installation)
* [react-native-dom](#react-native-dom-installation)
* [Usage](#usage) * [Usage](#usage)
* [iOS App Transport Security](#ios-app-transport-security) * [iOS App Transport Security](#ios-app-transport-security)
* [Audio Mixing](#audio-mixing) * [Audio Mixing](#audio-mixing)
@ -42,14 +53,21 @@ yarn add react-native-video
Then follow the instructions for your platform to link react-native-video into your project: Then follow the instructions for your platform to link react-native-video into your project:
### iOS installation
<details> <details>
<summary>iOS</summary> <summary>iOS details</summary>
### Standard Method #### Standard Method
**React Native 0.60 and above**
Run `pod install` in the `ios` directory. Linking is not required in React Native 0.60 and above.
**React Native 0.59 and below**
Run `react-native link react-native-video` to link the react-native-video library. Run `react-native link react-native-video` to link the react-native-video library.
### Using CocoaPods (required to enable caching) #### Using CocoaPods (required to enable caching)
Setup your Podfile like it is described in the [react-native documentation](https://facebook.github.io/react-native/docs/integration-with-existing-apps#configuring-cocoapods-dependencies). Setup your Podfile like it is described in the [react-native documentation](https://facebook.github.io/react-native/docs/integration-with-existing-apps#configuring-cocoapods-dependencies).
@ -73,8 +91,9 @@ end
</details> </details>
<details> ### tvOS installation
<summary>tvOS</summary> <details>
<summary>tvOS details</summary>
`react-native link react-native-video` doesnt work properly with the tvOS target so we need to add the library manually. `react-native link react-native-video` doesnt work properly with the tvOS target so we need to add the library manually.
@ -95,14 +114,15 @@ Select RCTVideo-tvOS
<img src="./docs/tvOS-step-4.jpg" width="40%"> <img src="./docs/tvOS-step-4.jpg" width="40%">
</details> </details>
### Android installation
<details> <details>
<summary>Android</summary> <summary>Android details</summary>
Run `react-native link react-native-video` to link the react-native-video library. Run `react-native link react-native-video` to link the react-native-video library.
Or if you have trouble, make the following additions to the given files manually: Or if you have trouble, make the following additions to the given files manually:
**android/settings.gradle** #### **android/settings.gradle**
The newer ExoPlayer library will work for most people. The newer ExoPlayer library will work for most people.
@ -118,17 +138,30 @@ include ':react-native-video'
project(':react-native-video').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-video/android') project(':react-native-video').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-video/android')
``` ```
#### **android/app/build.gradle**
**android/app/build.gradle** From version >= 5.0.0, you have to apply this changes:
```gradle ```diff
dependencies { dependencies {
... ...
compile project(':react-native-video') compile project(':react-native-video')
+ implementation "androidx.appcompat:appcompat:1.0.0"
- implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
} }
``` ```
**MainApplication.java** #### **android/gradle.properties**
Migrating to AndroidX (needs version >= 5.0.0):
```gradle.properties
android.useAndroidX=true
android.enableJetifier=true
```
#### **MainApplication.java**
On top, where imports are: On top, where imports are:
@ -149,12 +182,13 @@ protected List<ReactPackage> getPackages() {
``` ```
</details> </details>
### Windows installation
<details> <details>
<summary>Windows</summary> <summary>Windows details</summary>
Make the following additions to the given files manually: Make the following additions to the given files manually:
**windows/myapp.sln** #### **windows/myapp.sln**
Add the `ReactNativeVideo` project to your solution. Add the `ReactNativeVideo` project to your solution.
@ -163,7 +197,7 @@ Add the `ReactNativeVideo` project to your solution.
* UWP: Select `node_modules\react-native-video\windows\ReactNativeVideo\ReactNativeVideo.csproj` * UWP: Select `node_modules\react-native-video\windows\ReactNativeVideo\ReactNativeVideo.csproj`
* WPF: Select `node_modules\react-native-video\windows\ReactNativeVideo.Net46\ReactNativeVideo.Net46.csproj` * WPF: Select `node_modules\react-native-video\windows\ReactNativeVideo.Net46\ReactNativeVideo.Net46.csproj`
**windows/myapp/myapp.csproj** #### **windows/myapp/myapp.csproj**
Add a reference to `ReactNativeVideo` to your main application project. From Visual Studio 2015: Add a reference to `ReactNativeVideo` to your main application project. From Visual Studio 2015:
@ -171,7 +205,7 @@ Add a reference to `ReactNativeVideo` to your main application project. From Vis
* UWP: Check `ReactNativeVideo` from Solution Projects. * UWP: Check `ReactNativeVideo` from Solution Projects.
* WPF: Check `ReactNativeVideo.Net46` from Solution Projects. * WPF: Check `ReactNativeVideo.Net46` from Solution Projects.
**MainPage.cs** #### **MainPage.cs**
Add the `ReactVideoPackage` class to your list of exported packages. Add the `ReactVideoPackage` class to your list of exported packages.
```cs ```cs
@ -198,12 +232,13 @@ using System.Collections.Generic;
``` ```
</details> </details>
### react-native-dom installation
<details> <details>
<summary>react-native-dom</summary> <summary>react-native-dom details</summary>
Make the following additions to the given files manually: Make the following additions to the given files manually:
**dom/bootstrap.js** #### **dom/bootstrap.js**
Import RCTVideoManager and add it to the list of nativeModules: Import RCTVideoManager and add it to the list of nativeModules:
@ -257,8 +292,10 @@ var styles = StyleSheet.create({
### Configurable props ### Configurable props
* [allowsExternalPlayback](#allowsexternalplayback) * [allowsExternalPlayback](#allowsexternalplayback)
* [audioOnly](#audioonly) * [audioOnly](#audioonly)
* [automaticallyWaitsToMinimizeStalling](#automaticallyWaitsToMinimizeStalling)
* [bufferConfig](#bufferconfig) * [bufferConfig](#bufferconfig)
* [controls](#controls) * [controls](#controls)
* [disableFocus](#disableFocus)
* [filter](#filter) * [filter](#filter)
* [filterEnabled](#filterEnabled) * [filterEnabled](#filterEnabled)
* [fullscreen](#fullscreen) * [fullscreen](#fullscreen)
@ -302,7 +339,9 @@ var styles = StyleSheet.create({
* [onFullscreenPlayerDidDismiss](#onfullscreenplayerdiddismiss) * [onFullscreenPlayerDidDismiss](#onfullscreenplayerdiddismiss)
* [onLoad](#onload) * [onLoad](#onload)
* [onLoadStart](#onloadstart) * [onLoadStart](#onloadstart)
* [onReadyForDisplay](#onreadyfordisplay)
* [onPictureInPictureStatusChanged](#onpictureinpicturestatuschanged) * [onPictureInPictureStatusChanged](#onpictureinpicturestatuschanged)
* [onPlaybackRateChange](#onplaybackratechange)
* [onProgress](#onprogress) * [onProgress](#onprogress)
* [onSeek](#onseek) * [onSeek](#onseek)
* [onRestoreUserInterfaceForPictureInPictureStop](#onrestoreuserinterfaceforpictureinpicturestop) * [onRestoreUserInterfaceForPictureInPictureStop](#onrestoreuserinterfaceforpictureinpicturestop)
@ -333,6 +372,13 @@ For this to work, the poster prop must be set.
Platforms: all Platforms: all
#### automaticallyWaitsToMinimizeStalling
A Boolean value that indicates whether the player should automatically delay playback in order to minimize stalling. For clients linked against iOS 10.0 and later
* **false** - Immediately starts playback
* **true (default)** - Delays playback in order to minimize stalling
Platforms: iOS
#### bufferConfig #### bufferConfig
Adjust the buffer settings. This prop takes an object with one or more of the properties listed below. Adjust the buffer settings. This prop takes an object with one or more of the properties listed below.
@ -368,6 +414,13 @@ For Android MediaPlayer, you will need to build your own controls or use a packa
Platforms: Android ExoPlayer, iOS, react-native-dom Platforms: Android ExoPlayer, iOS, react-native-dom
#### disableFocus
Determines whether video audio should override background music/audio in Android devices.
* ** false (default)** - Override background audio/music
* **true** - Let background audio/music from other apps play
Platforms: Android Exoplayer
#### filter #### filter
Add video filter Add video filter
* **FilterType.NONE (default)** - No Filter * **FilterType.NONE (default)** - No Filter
@ -953,6 +1006,17 @@ Example:
Platforms: all Platforms: all
#### onReadyForDisplay
Callback function that is called when the first video frame is ready for display. This is when the poster is removed.
Payload: none
* iOS: [readyForDisplay](https://developer.apple.com/documentation/avkit/avplayerviewcontroller/1615830-readyfordisplay?language=objc)
* Android: [MEDIA_INFO_VIDEO_RENDERING_START](https://developer.android.com/reference/android/media/MediaPlayer#MEDIA_INFO_VIDEO_RENDERING_START)
* Android ExoPlayer [STATE_READY](https://exoplayer.dev/doc/reference/com/google/android/exoplayer2/Player.html#STATE_READY)
Platforms: Android ExoPlayer, Android MediaPlayer, iOS, Web
#### onPictureInPictureStatusChanged #### onPictureInPictureStatusChanged
Callback function that is called when picture in picture becomes active or inactive. Callback function that is called when picture in picture becomes active or inactive.
@ -969,6 +1033,23 @@ isActive: true
Platforms: iOS Platforms: iOS
#### onPlaybackRateChange
Callback function that is called when the rate of playback changes - either paused or starts/resumes.
Property | Type | Description
--- | --- | ---
playbackRate | number | 0 when playback is paused, 1 when playing at normal speed. Other values when playback is slowed down or sped up
Example:
```
{
playbackRate: 0, // indicates paused
}
```
Platforms: all
#### onProgress #### onProgress
Callback function that is called every progressUpdateInterval seconds with info about which position the media is currently playing. Callback function that is called every progressUpdateInterval seconds with info about which position the media is currently playing.
@ -1231,6 +1312,38 @@ To enable audio to play in background on iOS the audio session needs to be set t
## Updating ## Updating
### Version 5.0.0
Probably you want to update your gradle version:
#### gradle-wrapper.properties
```diff
- distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
+ distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
```
#### **android/app/build.gradle**
From version >= 5.0.0, you have to apply this changes:
```diff
dependencies {
...
compile project(':react-native-video')
+ implementation "androidx.appcompat:appcompat:1.0.0"
- implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}"
}
```
#### **android/gradle.properties**
Migrating to AndroidX (needs version >= 5.0.0):
```gradle.properties
android.useAndroidX=true
android.enableJetifier=true
```
### Version 4.0.0 ### Version 4.0.0
#### Gradle 3 and target SDK 26 requirement #### Gradle 3 and target SDK 26 requirement

View File

@ -20,7 +20,7 @@ export default class Video extends Component {
super(props); super(props);
this.state = { this.state = {
showPoster: true, showPoster: !!props.poster
}; };
} }
@ -86,6 +86,12 @@ export default class Video extends Component {
this._root = component; this._root = component;
}; };
_hidePoster = () => {
if (this.state.showPoster) {
this.setState({showPoster: false});
}
}
_onLoadStart = (event) => { _onLoadStart = (event) => {
if (this.props.onLoadStart) { if (this.props.onLoadStart) {
this.props.onLoadStart(event.nativeEvent); this.props.onLoadStart(event.nativeEvent);
@ -93,6 +99,10 @@ export default class Video extends Component {
}; };
_onLoad = (event) => { _onLoad = (event) => {
// Need to hide poster here for windows as onReadyForDisplay is not implemented
if (Platform.OS === 'windows') {
this._hidePoster();
}
if (this.props.onLoad) { if (this.props.onLoad) {
this.props.onLoad(event.nativeEvent); this.props.onLoad(event.nativeEvent);
} }
@ -117,10 +127,6 @@ export default class Video extends Component {
}; };
_onSeek = (event) => { _onSeek = (event) => {
if (this.state.showPoster && !this.props.audioOnly) {
this.setState({showPoster: false});
}
if (this.props.onSeek) { if (this.props.onSeek) {
this.props.onSeek(event.nativeEvent); this.props.onSeek(event.nativeEvent);
} }
@ -163,6 +169,7 @@ export default class Video extends Component {
}; };
_onReadyForDisplay = (event) => { _onReadyForDisplay = (event) => {
this._hidePoster();
if (this.props.onReadyForDisplay) { if (this.props.onReadyForDisplay) {
this.props.onReadyForDisplay(event.nativeEvent); this.props.onReadyForDisplay(event.nativeEvent);
} }
@ -181,10 +188,6 @@ export default class Video extends Component {
}; };
_onPlaybackRateChange = (event) => { _onPlaybackRateChange = (event) => {
if (this.state.showPoster && event.nativeEvent.playbackRate !== 0 && !this.props.audioOnly) {
this.setState({showPoster: false});
}
if (this.props.onPlaybackRateChange) { if (this.props.onPlaybackRateChange) {
this.props.onPlaybackRateChange(event.nativeEvent); this.props.onPlaybackRateChange(event.nativeEvent);
} }
@ -308,15 +311,16 @@ export default class Video extends Component {
}; };
return ( return (
<React.Fragment> <View style={nativeProps.style}>
<RCTVideo ref={this._assignRoot} {...nativeProps} /> <RCTVideo
{this.props.poster && ref={this._assignRoot}
this.state.showPoster && ( {...nativeProps}
<View style={nativeProps.style}> style={StyleSheet.absoluteFill}
<Image style={posterStyle} source={{ uri: this.props.poster }} /> />
</View> {this.state.showPoster && (
)} <Image style={posterStyle} source={{ uri: this.props.poster }} />
</React.Fragment> )}
</View>
); );
} }
} }
@ -378,6 +382,7 @@ Video.propTypes = {
poster: PropTypes.string, poster: PropTypes.string,
posterResizeMode: Image.propTypes.resizeMode, posterResizeMode: Image.propTypes.resizeMode,
repeat: PropTypes.bool, repeat: PropTypes.bool,
automaticallyWaitsToMinimizeStalling: PropTypes.bool,
allowsExternalPlayback: PropTypes.bool, allowsExternalPlayback: PropTypes.bool,
selectedAudioTrack: PropTypes.shape({ selectedAudioTrack: PropTypes.shape({
type: PropTypes.string.isRequired, type: PropTypes.string.isRequired,

View File

@ -5,8 +5,8 @@ def safeExtGet(prop, fallback) {
} }
android { android {
compileSdkVersion safeExtGet('compileSdkVersion', 27) compileSdkVersion safeExtGet('compileSdkVersion', 28)
buildToolsVersion safeExtGet('buildToolsVersion', '27.0.3') buildToolsVersion safeExtGet('buildToolsVersion', '28.0.3')
compileOptions { compileOptions {
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8
@ -15,26 +15,26 @@ android {
defaultConfig { defaultConfig {
minSdkVersion safeExtGet('minSdkVersion', 16) minSdkVersion safeExtGet('minSdkVersion', 16)
targetSdkVersion safeExtGet('targetSdkVersion', 27) targetSdkVersion safeExtGet('targetSdkVersion', 28)
versionCode 1 versionCode 1
versionName "1.0" versionName "1.0"
} }
} }
dependencies { dependencies {
compileOnly "com.facebook.react:react-native:${safeExtGet('reactNativeVersion', '+')}" implementation "com.facebook.react:react-native:${safeExtGet('reactNativeVersion', '+')}"
implementation('com.google.android.exoplayer:exoplayer:2.9.3') { implementation('com.google.android.exoplayer:exoplayer:2.10.4') {
exclude group: 'com.android.support' exclude group: 'com.android.support'
} }
// All support libs must use the same version // All support libs must use the same version
implementation "com.android.support:support-annotations:${safeExtGet('supportLibVersion', '+')}" implementation "androidx.annotation:annotation:1.1.0"
implementation "com.android.support:support-compat:${safeExtGet('supportLibVersion', '+')}" implementation "androidx.core:core:1.1.0"
implementation "com.android.support:support-media-compat:${safeExtGet('supportLibVersion', '+')}" implementation "androidx.media:media:1.1.0"
implementation('com.google.android.exoplayer:extension-okhttp:2.9.3') { implementation('com.google.android.exoplayer:extension-okhttp:2.10.4') {
exclude group: 'com.squareup.okhttp3', module: 'okhttp' exclude group: 'com.squareup.okhttp3', module: 'okhttp'
} }
implementation 'com.squareup.okhttp3:okhttp:3.12.1' implementation 'com.squareup.okhttp3:okhttp:3.14.3'
} }

View File

@ -1,8 +1,5 @@
package com.brentvatne.exoplayer; package com.brentvatne.exoplayer;
import android.content.Context;
import android.content.ContextWrapper;
import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReactContext;
import com.facebook.react.modules.network.CookieJarContainer; import com.facebook.react.modules.network.CookieJarContainer;
import com.facebook.react.modules.network.ForwardingCookieHandler; import com.facebook.react.modules.network.ForwardingCookieHandler;
@ -14,12 +11,10 @@ import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.upstream.HttpDataSource; import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import okhttp3.Cookie;
import okhttp3.JavaNetCookieJar; import okhttp3.JavaNetCookieJar;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import java.util.Map; import java.util.Map;
public class DataSourceUtil { public class DataSourceUtil {
private DataSourceUtil() { private DataSourceUtil() {

View File

@ -0,0 +1,26 @@
package com.brentvatne.exoplayer;
import android.content.Context;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
public class DefaultReactExoplayerConfig implements ReactExoplayerConfig {
private final DefaultBandwidthMeter bandwidthMeter;
public DefaultReactExoplayerConfig(Context context) {
this.bandwidthMeter = new DefaultBandwidthMeter.Builder(context).build();
}
@Override
public LoadErrorHandlingPolicy buildLoadErrorHandlingPolicy(int minLoadRetryCount) {
return new DefaultLoadErrorHandlingPolicy(minLoadRetryCount);
}
@Override
public DefaultBandwidthMeter getBandwidthMeter() {
return bandwidthMeter;
}
}

View File

@ -2,7 +2,7 @@ package com.brentvatne.exoplayer;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.content.Context; import android.content.Context;
import android.support.v4.content.ContextCompat; import androidx.core.content.ContextCompat;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Log; import android.util.Log;
import android.view.Gravity; import android.view.Gravity;

View File

@ -15,6 +15,6 @@ class RawResourceDataSourceFactory implements DataSource.Factory {
@Override @Override
public DataSource createDataSource() { public DataSource createDataSource() {
return new RawResourceDataSource(context, null); return new RawResourceDataSource(context);
} }
} }

View File

@ -0,0 +1,13 @@
package com.brentvatne.exoplayer;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
/**
* Extension points to configure the Exoplayer instance
*/
public interface ReactExoplayerConfig {
LoadErrorHandlingPolicy buildLoadErrorHandlingPolicy(int minLoadRetryCount);
DefaultBandwidthMeter getBandwidthMeter();
}

View File

@ -27,26 +27,25 @@ import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.ThemedReactContext;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.DefaultLoadControl; import com.google.android.exoplayer2.DefaultLoadControl;
import com.google.android.exoplayer2.DefaultRenderersFactory;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.ExoPlayerFactory; import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer; import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer;
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil; import com.google.android.exoplayer2.mediacodec.MediaCodecUtil;
import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.MetadataRenderer; import com.google.android.exoplayer2.metadata.MetadataOutput;
import com.google.android.exoplayer2.source.BehindLiveWindowException; import com.google.android.exoplayer2.source.BehindLiveWindowException;
import com.google.android.exoplayer2.source.ExtractorMediaSource;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MergingMediaSource; 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.SingleSampleMediaSource;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.dash.DashMediaSource; import com.google.android.exoplayer2.source.dash.DashMediaSource;
import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource; import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource;
import com.google.android.exoplayer2.source.hls.HlsMediaSource; import com.google.android.exoplayer2.source.hls.HlsMediaSource;
@ -54,42 +53,36 @@ import com.google.android.exoplayer2.source.smoothstreaming.DefaultSsChunkSource
import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource; import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection; import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.FixedTrackSelection;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector; import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.ui.PlayerControlView;
import com.google.android.exoplayer2.upstream.BandwidthMeter;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultAllocator; import com.google.android.exoplayer2.upstream.DefaultAllocator;
import com.google.android.exoplayer2.upstream.BandwidthMeter;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.ui.PlayerControlView;
import java.net.CookieHandler; 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.util.Map;
import java.lang.Object;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
@SuppressLint("ViewConstructor") @SuppressLint("ViewConstructor")
class ReactExoplayerView extends FrameLayout implements class ReactExoplayerView extends FrameLayout implements
LifecycleEventListener, LifecycleEventListener,
ExoPlayer.EventListener, Player.EventListener,
BandwidthMeter.EventListener, BandwidthMeter.EventListener,
BecomingNoisyListener, BecomingNoisyListener,
AudioManager.OnAudioFocusChangeListener, AudioManager.OnAudioFocusChangeListener,
MetadataRenderer.Output { MetadataOutput {
private static final String TAG = "ReactExoplayerView"; private static final String TAG = "ReactExoplayerView";
private static final DefaultBandwidthMeter BANDWIDTH_METER = new DefaultBandwidthMeter();
private static final CookieManager DEFAULT_COOKIE_MANAGER; private static final CookieManager DEFAULT_COOKIE_MANAGER;
private static final int SHOW_PROGRESS = 1; private static final int SHOW_PROGRESS = 1;
private static final int REPORT_BANDWIDTH = 1;
static { static {
DEFAULT_COOKIE_MANAGER = new CookieManager(); DEFAULT_COOKIE_MANAGER = new CookieManager();
@ -97,11 +90,12 @@ class ReactExoplayerView extends FrameLayout implements
} }
private final VideoEventEmitter eventEmitter; private final VideoEventEmitter eventEmitter;
private final ReactExoplayerConfig config;
private final DefaultBandwidthMeter bandwidthMeter;
private PlayerControlView playerControlView; private PlayerControlView playerControlView;
private View playPauseControlContainer; private View playPauseControlContainer;
private Player.EventListener eventListener; private Player.EventListener eventListener;
private Handler mainHandler;
private ExoPlayerView exoPlayerView; private ExoPlayerView exoPlayerView;
private DataSource.Factory mediaDataSourceFactory; private DataSource.Factory mediaDataSourceFactory;
@ -116,6 +110,7 @@ class ReactExoplayerView extends FrameLayout implements
private boolean isInBackground; private boolean isInBackground;
private boolean isPaused; private boolean isPaused;
private boolean isBuffering; private boolean isBuffering;
private boolean muted = false;
private float rate = 1f; private float rate = 1f;
private float audioVolume = 1f; private float audioVolume = 1f;
private int minLoadRetryCount = 3; private int minLoadRetryCount = 3;
@ -135,7 +130,6 @@ class ReactExoplayerView extends FrameLayout implements
private Dynamic audioTrackValue; private Dynamic audioTrackValue;
private String videoTrackType; private String videoTrackType;
private Dynamic videoTrackValue; private Dynamic videoTrackValue;
private ReadableArray audioTracks;
private String textTrackType; private String textTrackType;
private Dynamic textTrackValue; private Dynamic textTrackValue;
private ReadableArray textTracks; private ReadableArray textTracks;
@ -144,6 +138,7 @@ class ReactExoplayerView extends FrameLayout implements
private boolean playInBackground = false; private boolean playInBackground = false;
private Map<String, String> requestHeaders; private Map<String, String> requestHeaders;
private boolean mReportBandwidth = false; private boolean mReportBandwidth = false;
private boolean controls;
// \ End props // \ End props
// React // React
@ -157,7 +152,7 @@ class ReactExoplayerView extends FrameLayout implements
switch (msg.what) { switch (msg.what) {
case SHOW_PROGRESS: case SHOW_PROGRESS:
if (player != null if (player != null
&& player.getPlaybackState() == ExoPlayer.STATE_READY && player.getPlaybackState() == Player.STATE_READY
&& player.getPlayWhenReady() && player.getPlayWhenReady()
) { ) {
long pos = player.getCurrentPosition(); long pos = player.getCurrentPosition();
@ -171,11 +166,12 @@ class ReactExoplayerView extends FrameLayout implements
} }
}; };
public ReactExoplayerView(ThemedReactContext context) { public ReactExoplayerView(ThemedReactContext context, ReactExoplayerConfig config) {
super(context); super(context);
this.themedReactContext = context; this.themedReactContext = context;
this.eventEmitter = new VideoEventEmitter(context); this.eventEmitter = new VideoEventEmitter(context);
this.config = config;
this.bandwidthMeter = config.getBandwidthMeter();
createViews(); createViews();
@ -196,7 +192,6 @@ class ReactExoplayerView extends FrameLayout implements
private void createViews() { private void createViews() {
clearResumePosition(); clearResumePosition();
mediaDataSourceFactory = buildDataSourceFactory(true); mediaDataSourceFactory = buildDataSourceFactory(true);
mainHandler = new Handler();
if (CookieHandler.getDefault() != DEFAULT_COOKIE_MANAGER) { if (CookieHandler.getDefault() != DEFAULT_COOKIE_MANAGER) {
CookieHandler.setDefault(DEFAULT_COOKIE_MANAGER); CookieHandler.setDefault(DEFAULT_COOKIE_MANAGER);
} }
@ -267,6 +262,7 @@ class ReactExoplayerView extends FrameLayout implements
* Toggling the visibility of the player control view * Toggling the visibility of the player control view
*/ */
private void togglePlayerControlVisibility() { private void togglePlayerControlVisibility() {
if(player == null) return;
reLayout(playerControlView); reLayout(playerControlView);
if (playerControlView.isVisible()) { if (playerControlView.isVisible()) {
playerControlView.hide(); playerControlView.hide();
@ -312,10 +308,15 @@ class ReactExoplayerView extends FrameLayout implements
* Adding Player control to the frame layout * Adding Player control to the frame layout
*/ */
private void addPlayerControl() { private void addPlayerControl() {
if(player == null) return;
LayoutParams layoutParams = new LayoutParams( LayoutParams layoutParams = new LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT); LayoutParams.MATCH_PARENT);
playerControlView.setLayoutParams(layoutParams); playerControlView.setLayoutParams(layoutParams);
int indexOfPC = indexOfChild(playerControlView);
if (indexOfPC != -1) {
removeViewAt(indexOfPC);
}
addView(playerControlView, 1, layoutParams); addView(playerControlView, 1, layoutParams);
} }
@ -333,53 +334,72 @@ class ReactExoplayerView extends FrameLayout implements
} }
private void initializePlayer() { private void initializePlayer() {
if (player == null) { ReactExoplayerView self = this;
TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory(BANDWIDTH_METER); // This ensures all props have been settled, to avoid async racing conditions.
trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory); new Handler().postDelayed(new Runnable() {
trackSelector.setParameters(trackSelector.buildUponParameters() @Override
public void run() {
if (player == null) {
TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory();
trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
trackSelector.setParameters(trackSelector.buildUponParameters()
.setMaxVideoBitrate(maxBitRate == 0 ? Integer.MAX_VALUE : maxBitRate)); .setMaxVideoBitrate(maxBitRate == 0 ? Integer.MAX_VALUE : maxBitRate));
DefaultAllocator allocator = new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE); DefaultAllocator allocator = new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE);
DefaultLoadControl defaultLoadControl = new DefaultLoadControl(allocator, minBufferMs, maxBufferMs, bufferForPlaybackMs, bufferForPlaybackAfterRebufferMs, -1, true); DefaultLoadControl.Builder defaultLoadControlBuilder = new DefaultLoadControl.Builder();
player = ExoPlayerFactory.newSimpleInstance(getContext(), trackSelector, defaultLoadControl); defaultLoadControlBuilder.setAllocator(allocator);
player.addListener(this); defaultLoadControlBuilder.setBufferDurationsMs(minBufferMs, maxBufferMs, bufferForPlaybackMs, bufferForPlaybackAfterRebufferMs);
player.setMetadataOutput(this); defaultLoadControlBuilder.setTargetBufferBytes(-1);
exoPlayerView.setPlayer(player); defaultLoadControlBuilder.setPrioritizeTimeOverSizeThresholds(true);
audioBecomingNoisyReceiver.setListener(this); DefaultLoadControl defaultLoadControl = defaultLoadControlBuilder.createDefaultLoadControl();
BANDWIDTH_METER.addEventListener(new Handler(), this); DefaultRenderersFactory renderersFactory =
setPlayWhenReady(!isPaused); new DefaultRenderersFactory(getContext())
playerNeedsSource = true; .setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF);
// TODO: Add drmSessionManager to 5th param from: https://github.com/react-native-community/react-native-video/pull/1445
player = ExoPlayerFactory.newSimpleInstance(getContext(), renderersFactory,
trackSelector, defaultLoadControl, null, bandwidthMeter);
player.addListener(self);
player.addMetadataOutput(self);
exoPlayerView.setPlayer(player);
audioBecomingNoisyReceiver.setListener(self);
bandwidthMeter.addEventListener(new Handler(), self);
setPlayWhenReady(!isPaused);
playerNeedsSource = true;
PlaybackParameters params = new PlaybackParameters(rate, 1f); PlaybackParameters params = new PlaybackParameters(rate, 1f);
player.setPlaybackParameters(params); player.setPlaybackParameters(params);
} }
if (playerNeedsSource && srcUri != null) { if (playerNeedsSource && srcUri != null) {
ArrayList<MediaSource> mediaSourceList = buildTextSources(); ArrayList<MediaSource> mediaSourceList = buildTextSources();
MediaSource videoSource = buildMediaSource(srcUri, extension); MediaSource videoSource = buildMediaSource(srcUri, extension);
MediaSource mediaSource; MediaSource mediaSource;
if (mediaSourceList.size() == 0) { if (mediaSourceList.size() == 0) {
mediaSource = videoSource; mediaSource = videoSource;
} else { } else {
mediaSourceList.add(0, videoSource); mediaSourceList.add(0, videoSource);
MediaSource[] textSourceArray = mediaSourceList.toArray( MediaSource[] textSourceArray = mediaSourceList.toArray(
new MediaSource[mediaSourceList.size()] new MediaSource[mediaSourceList.size()]
); );
mediaSource = new MergingMediaSource(textSourceArray); mediaSource = new MergingMediaSource(textSourceArray);
}
boolean haveResumePosition = resumeWindow != C.INDEX_UNSET;
if (haveResumePosition) {
player.seekTo(resumeWindow, resumePosition);
}
player.prepare(mediaSource, !haveResumePosition, false);
playerNeedsSource = false;
eventEmitter.loadStart();
loadVideoStarted = true;
}
// Initializing the playerControlView
initializePlayerControl();
setControls(controls);
applyModifiers();
} }
}, 1);
boolean haveResumePosition = resumeWindow != C.INDEX_UNSET;
if (haveResumePosition) {
player.seekTo(resumeWindow, resumePosition);
}
player.prepare(mediaSource, !haveResumePosition, false);
playerNeedsSource = false;
eventEmitter.loadStart();
loadVideoStarted = true;
}
// Initializing the playerControlView
initializePlayerControl();
} }
private MediaSource buildMediaSource(Uri uri, String overrideExtension) { private MediaSource buildMediaSource(Uri uri, String overrideExtension) {
@ -387,21 +407,31 @@ class ReactExoplayerView extends FrameLayout implements
: uri.getLastPathSegment()); : uri.getLastPathSegment());
switch (type) { switch (type) {
case C.TYPE_SS: case C.TYPE_SS:
return new SsMediaSource(uri, buildDataSourceFactory(false), return new SsMediaSource.Factory(
new DefaultSsChunkSource.Factory(mediaDataSourceFactory), new DefaultSsChunkSource.Factory(mediaDataSourceFactory),
minLoadRetryCount, SsMediaSource.DEFAULT_LIVE_PRESENTATION_DELAY_MS, buildDataSourceFactory(false)
mainHandler, null); ).setLoadErrorHandlingPolicy(
config.buildLoadErrorHandlingPolicy(minLoadRetryCount)
).createMediaSource(uri);
case C.TYPE_DASH: case C.TYPE_DASH:
return new DashMediaSource(uri, buildDataSourceFactory(false), return new DashMediaSource.Factory(
new DefaultDashChunkSource.Factory(mediaDataSourceFactory), new DefaultDashChunkSource.Factory(mediaDataSourceFactory),
minLoadRetryCount, DashMediaSource.DEFAULT_LIVE_PRESENTATION_DELAY_MS, buildDataSourceFactory(false)
mainHandler, null); ).setLoadErrorHandlingPolicy(
config.buildLoadErrorHandlingPolicy(minLoadRetryCount)
).createMediaSource(uri);
case C.TYPE_HLS: case C.TYPE_HLS:
return new HlsMediaSource(uri, mediaDataSourceFactory, return new HlsMediaSource.Factory(
minLoadRetryCount, mainHandler, null); mediaDataSourceFactory
).setLoadErrorHandlingPolicy(
config.buildLoadErrorHandlingPolicy(minLoadRetryCount)
).createMediaSource(uri);
case C.TYPE_OTHER: case C.TYPE_OTHER:
return new ExtractorMediaSource(uri, mediaDataSourceFactory, new DefaultExtractorsFactory(), return new ProgressiveMediaSource.Factory(
mainHandler, null); mediaDataSourceFactory
).setLoadErrorHandlingPolicy(
config.buildLoadErrorHandlingPolicy(minLoadRetryCount)
).createMediaSource(uri);
default: { default: {
throw new IllegalStateException("Unsupported type: " + type); throw new IllegalStateException("Unsupported type: " + type);
} }
@ -431,25 +461,26 @@ class ReactExoplayerView extends FrameLayout implements
private MediaSource buildTextSource(String title, Uri uri, String mimeType, String language) { private MediaSource buildTextSource(String title, Uri uri, String mimeType, String language) {
Format textFormat = Format.createTextSampleFormat(title, mimeType, Format.NO_VALUE, language); Format textFormat = Format.createTextSampleFormat(title, mimeType, Format.NO_VALUE, language);
return new SingleSampleMediaSource(uri, mediaDataSourceFactory, textFormat, C.TIME_UNSET); return new SingleSampleMediaSource.Factory(mediaDataSourceFactory)
.createMediaSource(uri, textFormat, C.TIME_UNSET);
} }
private void releasePlayer() { private void releasePlayer() {
if (player != null) { if (player != null) {
updateResumePosition(); updateResumePosition();
player.release(); player.release();
player.setMetadataOutput(null); player.removeMetadataOutput(this);
player = null;
trackSelector = null; trackSelector = null;
player = null;
} }
progressHandler.removeMessages(SHOW_PROGRESS); progressHandler.removeMessages(SHOW_PROGRESS);
themedReactContext.removeLifecycleEventListener(this); themedReactContext.removeLifecycleEventListener(this);
audioBecomingNoisyReceiver.removeListener(); audioBecomingNoisyReceiver.removeListener();
BANDWIDTH_METER.removeEventListener(this); bandwidthMeter.removeEventListener(this);
} }
private boolean requestAudioFocus() { private boolean requestAudioFocus() {
if (disableFocus) { if (disableFocus || srcUri == null) {
return true; return true;
} }
int result = audioManager.requestAudioFocus(this, int result = audioManager.requestAudioFocus(this,
@ -476,12 +507,12 @@ class ReactExoplayerView extends FrameLayout implements
private void startPlayback() { private void startPlayback() {
if (player != null) { if (player != null) {
switch (player.getPlaybackState()) { switch (player.getPlaybackState()) {
case ExoPlayer.STATE_IDLE: case Player.STATE_IDLE:
case ExoPlayer.STATE_ENDED: case Player.STATE_ENDED:
initializePlayer(); initializePlayer();
break; break;
case ExoPlayer.STATE_BUFFERING: case Player.STATE_BUFFERING:
case ExoPlayer.STATE_READY: case Player.STATE_READY:
if (!player.getPlayWhenReady()) { if (!player.getPlayWhenReady()) {
setPlayWhenReady(true); setPlayWhenReady(true);
} }
@ -534,12 +565,13 @@ class ReactExoplayerView extends FrameLayout implements
/** /**
* Returns a new DataSource factory. * Returns a new DataSource factory.
* *
* @param useBandwidthMeter Whether to set {@link #BANDWIDTH_METER} as a listener to the new * @param useBandwidthMeter Whether to set {@link #bandwidthMeter} as a listener to the new
* DataSource factory. * DataSource factory.
* @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, requestHeaders); return DataSourceUtil.getDefaultDataSourceFactory(this.themedReactContext,
useBandwidthMeter ? bandwidthMeter : null, requestHeaders);
} }
// AudioManager.OnAudioFocusChangeListener implementation // AudioManager.OnAudioFocusChangeListener implementation
@ -560,10 +592,14 @@ class ReactExoplayerView extends FrameLayout implements
if (player != null) { if (player != null) {
if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) { if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
// Lower the volume // Lower the volume
player.setVolume(audioVolume * 0.8f); if (!muted) {
player.setVolume(audioVolume * 0.8f);
}
} else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) { } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
// Raise it back to normal // Raise it back to normal
player.setVolume(audioVolume * 1); if (!muted) {
player.setVolume(audioVolume * 1);
}
} }
} }
} }
@ -575,7 +611,7 @@ class ReactExoplayerView extends FrameLayout implements
eventEmitter.audioBecomingNoisy(); eventEmitter.audioBecomingNoisy();
} }
// ExoPlayer.EventListener implementation // Player.EventListener implementation
@Override @Override
public void onLoadingChanged(boolean isLoading) { public void onLoadingChanged(boolean isLoading) {
@ -586,26 +622,26 @@ class ReactExoplayerView extends FrameLayout implements
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
String text = "onStateChanged: playWhenReady=" + playWhenReady + ", playbackState="; String text = "onStateChanged: playWhenReady=" + playWhenReady + ", playbackState=";
switch (playbackState) { switch (playbackState) {
case ExoPlayer.STATE_IDLE: case Player.STATE_IDLE:
text += "idle"; text += "idle";
eventEmitter.idle(); eventEmitter.idle();
break; break;
case ExoPlayer.STATE_BUFFERING: case Player.STATE_BUFFERING:
text += "buffering"; text += "buffering";
onBuffering(true); onBuffering(true);
break; break;
case ExoPlayer.STATE_READY: case Player.STATE_READY:
text += "ready"; text += "ready";
eventEmitter.ready(); eventEmitter.ready();
onBuffering(false); onBuffering(false);
startProgressHandler(); startProgressHandler();
videoLoaded(); videoLoaded();
//Setting the visibility for the playerControlView //Setting the visibility for the playerControlView
if(playerControlView != null) { if (playerControlView != null) {
playerControlView.show(); playerControlView.show();
} }
break; break;
case ExoPlayer.STATE_ENDED: case Player.STATE_ENDED:
text += "ended"; text += "ended";
eventEmitter.end(); eventEmitter.end();
onStopPlayback(); onStopPlayback();
@ -731,7 +767,7 @@ class ReactExoplayerView extends FrameLayout implements
} }
// When repeat is turned on, reaching the end of the video will not cause a state change // When repeat is turned on, reaching the end of the video will not cause a state change
// so we need to explicitly detect it. // so we need to explicitly detect it.
if (reason == ExoPlayer.DISCONTINUITY_REASON_PERIOD_TRANSITION if (reason == Player.DISCONTINUITY_REASON_PERIOD_TRANSITION
&& player.getRepeatMode() == Player.REPEAT_MODE_ONE) { && player.getRepeatMode() == Player.REPEAT_MODE_ONE) {
eventEmitter.end(); eventEmitter.end();
} }
@ -849,7 +885,9 @@ class ReactExoplayerView extends FrameLayout implements
this.srcUri = uri; this.srcUri = uri;
this.extension = extension; this.extension = extension;
this.requestHeaders = headers; this.requestHeaders = headers;
this.mediaDataSourceFactory = DataSourceUtil.getDefaultDataSourceFactory(this.themedReactContext, BANDWIDTH_METER, this.requestHeaders); this.mediaDataSourceFactory =
DataSourceUtil.getDefaultDataSourceFactory(this.themedReactContext, bandwidthMeter,
this.requestHeaders);
if (!isOriginalSourceNull && !isSourceEqual) { if (!isOriginalSourceNull && !isSourceEqual) {
reloadSource(); reloadSource();
@ -894,6 +932,11 @@ class ReactExoplayerView extends FrameLayout implements
exoPlayerView.setResizeMode(resizeMode); exoPlayerView.setResizeMode(resizeMode);
} }
private void applyModifiers() {
setRepeatModifier(repeat);
setMutedModifier(muted);
}
public void setRepeatModifier(boolean repeat) { public void setRepeatModifier(boolean repeat) {
if (player != null) { if (player != null) {
if (repeat) { if (repeat) {
@ -906,6 +949,7 @@ class ReactExoplayerView extends FrameLayout implements
} }
public void setSelectedTrack(int trackType, String type, Dynamic value) { public void setSelectedTrack(int trackType, String type, Dynamic value) {
if (player == null) return;
int rendererIndex = getTrackRendererIndex(trackType); int rendererIndex = getTrackRendererIndex(trackType);
if (rendererIndex == C.INDEX_UNSET) { if (rendererIndex == C.INDEX_UNSET) {
return; return;
@ -1048,6 +1092,7 @@ class ReactExoplayerView extends FrameLayout implements
} }
public void setMutedModifier(boolean muted) { public void setMutedModifier(boolean muted) {
this.muted = muted;
audioVolume = muted ? 0.f : 1.f; audioVolume = muted ? 0.f : 1.f;
if (player != null) { if (player != null) {
player.setVolume(audioVolume); player.setVolume(audioVolume);
@ -1156,10 +1201,15 @@ class ReactExoplayerView extends FrameLayout implements
* @param controls Controls prop, if true enable controls, if false disable them * @param controls Controls prop, if true enable controls, if false disable them
*/ */
public void setControls(boolean controls) { public void setControls(boolean controls) {
if (controls && exoPlayerView != null) { this.controls = controls;
if (player == null || exoPlayerView == null) return;
if (controls) {
addPlayerControl(); addPlayerControl();
} else if (getChildAt(1) instanceof PlayerControlView && exoPlayerView != null) { } else {
removeViewAt(1); int indexOfPC = indexOfChild(playerControlView);
if (indexOfPC != -1) {
removeViewAt(indexOfPC);
}
} }
} }
} }

View File

@ -60,6 +60,12 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
private static final String PROP_HIDE_SHUTTER_VIEW = "hideShutterView"; private static final String PROP_HIDE_SHUTTER_VIEW = "hideShutterView";
private static final String PROP_CONTROLS = "controls"; private static final String PROP_CONTROLS = "controls";
private ReactExoplayerConfig config;
public ReactExoplayerViewManager(ReactExoplayerConfig config) {
this.config = config;
}
@Override @Override
public String getName() { public String getName() {
return REACT_CLASS; return REACT_CLASS;
@ -67,7 +73,7 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
@Override @Override
protected ReactExoplayerView createViewInstance(ThemedReactContext themedReactContext) { protected ReactExoplayerView createViewInstance(ThemedReactContext themedReactContext) {
return new ReactExoplayerView(themedReactContext); return new ReactExoplayerView(themedReactContext, config);
} }
@Override @Override

View File

@ -1,6 +1,6 @@
package com.brentvatne.exoplayer; package com.brentvatne.exoplayer;
import android.support.annotation.IntDef; import androidx.annotation.IntDef;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;

View File

@ -1,6 +1,6 @@
package com.brentvatne.exoplayer; package com.brentvatne.exoplayer;
import android.support.annotation.StringDef; import androidx.annotation.StringDef;
import android.view.View; import android.view.View;
import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.Arguments;

View File

@ -1,5 +1,7 @@
package com.brentvatne.react; package com.brentvatne.react;
import com.brentvatne.exoplayer.DefaultReactExoplayerConfig;
import com.brentvatne.exoplayer.ReactExoplayerConfig;
import com.brentvatne.exoplayer.ReactExoplayerViewManager; import com.brentvatne.exoplayer.ReactExoplayerViewManager;
import com.facebook.react.ReactPackage; import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule; import com.facebook.react.bridge.JavaScriptModule;
@ -12,6 +14,15 @@ import java.util.List;
public class ReactVideoPackage implements ReactPackage { public class ReactVideoPackage implements ReactPackage {
private ReactExoplayerConfig config;
public ReactVideoPackage() {
}
public ReactVideoPackage(ReactExoplayerConfig config) {
this.config = config;
}
@Override @Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) { public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Collections.emptyList(); return Collections.emptyList();
@ -25,6 +36,9 @@ public class ReactVideoPackage implements ReactPackage {
@Override @Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) { public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.<ViewManager>singletonList(new ReactExoplayerViewManager()); if (config == null) {
config = new DefaultReactExoplayerConfig(reactContext);
}
return Collections.singletonList(new ReactExoplayerViewManager(config));
} }
} }

View File

@ -5,12 +5,12 @@ def safeExtGet(prop, fallback) {
} }
android { android {
compileSdkVersion safeExtGet('compileSdkVersion', 27) compileSdkVersion safeExtGet('compileSdkVersion', 28)
buildToolsVersion safeExtGet('buildToolsVersion', '27.0.3') buildToolsVersion safeExtGet('buildToolsVersion', '28.0.3')
defaultConfig { defaultConfig {
minSdkVersion safeExtGet('minSdkVersion', 16) minSdkVersion safeExtGet('minSdkVersion', 16)
targetSdkVersion safeExtGet('targetSdkVersion', 27) targetSdkVersion safeExtGet('targetSdkVersion', 28)
versionCode 1 versionCode 1
versionName "1.0" versionName "1.0"
ndk { ndk {
@ -21,6 +21,6 @@ android {
dependencies { dependencies {
//noinspection GradleDynamicVersion //noinspection GradleDynamicVersion
compileOnly "com.facebook.react:react-native:${safeExtGet('reactNativeVersion', '+')}" implementation "com.facebook.react:react-native:${safeExtGet('reactNativeVersion', '+')}"
implementation 'com.yqritc:android-scalablevideoview:1.0.4' implementation 'com.yqritc:android-scalablevideoview:1.0.4'
} }

View File

@ -37,6 +37,7 @@ class RCTVideo extends RCTView {
this.videoElement = this.initializeVideoElement(); this.videoElement = this.initializeVideoElement();
this.videoElement.addEventListener("ended", this.onEnd); this.videoElement.addEventListener("ended", this.onEnd);
this.videoElement.addEventListener("loadeddata", this.onLoad); this.videoElement.addEventListener("loadeddata", this.onLoad);
this.videoElement.addEventListener("canplay", this.onReadyForDisplay);
this.videoElement.addEventListener("loadstart", this.onLoadStart); this.videoElement.addEventListener("loadstart", this.onLoadStart);
this.videoElement.addEventListener("pause", this.onPause); this.videoElement.addEventListener("pause", this.onPause);
this.videoElement.addEventListener("play", this.onPlay); this.videoElement.addEventListener("play", this.onPlay);
@ -51,6 +52,7 @@ class RCTVideo extends RCTView {
detachFromView(view: UIView) { detachFromView(view: UIView) {
this.videoElement.removeEventListener("ended", this.onEnd); this.videoElement.removeEventListener("ended", this.onEnd);
this.videoElement.removeEventListener("loadeddata", this.onLoad); this.videoElement.removeEventListener("loadeddata", this.onLoad);
this.videoElement.removeEventListener("canplay", this.onReadyForDisplay);
this.videoElement.removeEventListener("loadstart", this.onLoadStart); this.videoElement.removeEventListener("loadstart", this.onLoadStart);
this.videoElement.removeEventListener("pause", this.onPause); this.videoElement.removeEventListener("pause", this.onPause);
this.videoElement.removeEventListener("play", this.onPlay); this.videoElement.removeEventListener("play", this.onPlay);
@ -203,6 +205,10 @@ class RCTVideo extends RCTView {
this.sendEvent("topVideoLoad", payload); this.sendEvent("topVideoLoad", payload);
} }
onReadyForDisplay = () => {
this.sendEvent("onReadyForDisplay");
}
onLoadStart = () => { onLoadStart = () => {
const src = this.videoElement.currentSrc; const src = this.videoElement.currentSrc;
const payload = { const payload = {

View File

@ -1,3 +0,0 @@
{
"presets": ["react-native"]
}

View File

@ -65,6 +65,10 @@ import com.android.build.OutputFile
* ] * ]
*/ */
project.ext.react = [
entryFile: "index.android.js",
enableHermes: false,
]
apply from: "../../node_modules/react-native/react.gradle" apply from: "../../node_modules/react-native/react.gradle"
/** /**
@ -83,13 +87,16 @@ def enableSeparateBuildPerCPUArchitecture = false
def enableProguardInReleaseBuilds = false def enableProguardInReleaseBuilds = false
android { android {
compileSdkVersion 25 compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion '25.0.2' buildToolsVersion rootProject.ext.buildToolsVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
defaultConfig { defaultConfig {
applicationId "com.videoplayer" applicationId "com.videoplayer"
minSdkVersion 16 minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion 25 targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1 versionCode 1
versionName "1.0" versionName "1.0"
ndk { ndk {
@ -108,6 +115,7 @@ android {
release { release {
signingConfig signingConfigs.debug signingConfig signingConfigs.debug
minifyEnabled enableProguardInReleaseBuilds minifyEnabled enableProguardInReleaseBuilds
matchingFallbacks = ['release', 'debug']
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
} }
} }
@ -127,10 +135,9 @@ android {
} }
dependencies { dependencies {
compile "com.android.support:appcompat-v7:25.2.0" implementation project(':react-native-video')
compile "com.facebook.react:react-native:+" // From node_modules implementation "com.facebook.react:react-native:+" // From node_modules
compile project(':react-native-video') implementation 'org.webkit:android-jsc:+'
// compile project(':react-native-video-exoplayer') // uncomment to use exoplayer
} }
// Run this once to be able to run the application with BUCK // Run this once to be able to run the application with BUCK

View File

@ -6,11 +6,7 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-sdk <application
android:minSdkVersion="16"
android:targetSdkVersion="22" />
<application
android:name=".MainApplication" android:name=".MainApplication"
android:allowBackup="true" android:allowBackup="true"
android:label="@string/app_name" android:label="@string/app_name"

View File

@ -2,8 +2,8 @@ package com.videoplayer;
import android.app.Application; import android.app.Application;
import com.brentvatne.react.ReactVideoPackage;
import com.facebook.react.ReactApplication; import com.facebook.react.ReactApplication;
import com.brentvatne.react.ReactVideoPackage;
import com.facebook.react.ReactNativeHost; import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage; import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage; import com.facebook.react.shell.MainReactPackage;

View File

@ -1,11 +1,20 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
ext {
buildToolsVersion = "28.0.3"
minSdkVersion = 16
compileSdkVersion = 28
targetSdkVersion = 28
supportLibVersion = "28.0.0"
}
repositories { repositories {
jcenter() jcenter()
google()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:2.3.0' classpath 'com.android.tools.build:gradle:3.4.1'
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files
@ -15,10 +24,18 @@ buildscript {
allprojects { allprojects {
repositories { repositories {
mavenLocal() mavenLocal()
google()
maven {
url "https://jitpack.io"
}
jcenter() jcenter()
maven { maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url "$rootDir/../node_modules/react-native/android" url "$rootDir/../node_modules/react-native/android"
} }
maven {
// Android JSC is installed from npm
url("$rootDir/../node_modules/jsc-android/dist")
}
} }
} }

View File

@ -17,4 +17,5 @@
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true # org.gradle.parallel=true
android.useDeprecatedNdk=true android.useAndroidX=true
android.enableJetifier=true

View File

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip

View File

@ -1,12 +1,6 @@
rootProject.name = 'VideoPlayer' rootProject.name = 'VideoPlayer'
include ':app', include ':react-native-video'
':react-native-video', project(':react-native-video').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-video/android-exoplayer')
':react-native-video-exoplayer'
// Quick Local Development include ':app'
//project(':react-native-video').projectDir = new File(rootProject.projectDir, '../../android')
//project(':react-native-video-exoplayer').projectDir = new File(rootProject.projectDir, '../../android-exoplayer')
project(':react-native-video').projectDir = new File(rootProject.projectDir, '../../android')
project(':react-native-video-exoplayer').projectDir = new File(rootProject.projectDir, '../../android-exoplayer')

View File

@ -0,0 +1,3 @@
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
};

View File

@ -0,0 +1,10 @@
module.exports = {
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: false,
},
}),
},
};

View File

@ -4,18 +4,23 @@
"private": true, "private": true,
"scripts": { "scripts": {
"start": "node node_modules/react-native/local-cli/cli.js start", "start": "node node_modules/react-native/local-cli/cli.js start",
"test": "jest" "postinstall": "rm -rf node_modules/react-native-video/{examples,node_modules}",
"test": "jest",
"lint": "eslint ."
}, },
"dependencies": { "dependencies": {
"react": "16.4.1", "react": "16.9.0",
"react-native": "0.56.0", "react-native": "0.60.5",
"react-native-video": "file:../.." "react-native-video": "file:../.."
}, },
"devDependencies": { "devDependencies": {
"babel-jest": "22.4.1", "@babel/core": "^7.6.0",
"babel-preset-react-native": "5.0.2", "@babel/runtime": "^7.6.0",
"express": "^4.16.2", "@react-native-community/eslint-config": "^0.0.5",
"jest": "22.4.2", "babel-jest": "^24.9.0",
"react-test-renderer": "16.2.0" "eslint": "^6.4.0",
"jest": "^24.9.0",
"metro-react-native-babel-preset": "^0.56.0",
"react-test-renderer": "16.8.6"
} }
} }

View File

@ -1,7 +0,0 @@
const blacklist = require('metro').createBlacklist;
module.exports = {
getBlacklistRE: function() {
return blacklist([/node_modules\/react-native-video\/examples\/.*/]);
}
};

File diff suppressed because it is too large Load Diff

View File

@ -21,27 +21,27 @@
@interface RCTVideo : UIView <RCTVideoPlayerViewControllerDelegate, AVPictureInPictureControllerDelegate> @interface RCTVideo : UIView <RCTVideoPlayerViewControllerDelegate, AVPictureInPictureControllerDelegate>
#endif #endif
@property (nonatomic, copy) RCTBubblingEventBlock onVideoLoadStart; @property (nonatomic, copy) RCTDirectEventBlock onVideoLoadStart;
@property (nonatomic, copy) RCTBubblingEventBlock onVideoLoad; @property (nonatomic, copy) RCTDirectEventBlock onVideoLoad;
@property (nonatomic, copy) RCTBubblingEventBlock onVideoBuffer; @property (nonatomic, copy) RCTDirectEventBlock onVideoBuffer;
@property (nonatomic, copy) RCTBubblingEventBlock onVideoError; @property (nonatomic, copy) RCTDirectEventBlock onVideoError;
@property (nonatomic, copy) RCTBubblingEventBlock onVideoProgress; @property (nonatomic, copy) RCTDirectEventBlock onVideoProgress;
@property (nonatomic, copy) RCTBubblingEventBlock onBandwidthUpdate; @property (nonatomic, copy) RCTDirectEventBlock onBandwidthUpdate;
@property (nonatomic, copy) RCTBubblingEventBlock onVideoSeek; @property (nonatomic, copy) RCTDirectEventBlock onVideoSeek;
@property (nonatomic, copy) RCTBubblingEventBlock onVideoEnd; @property (nonatomic, copy) RCTDirectEventBlock onVideoEnd;
@property (nonatomic, copy) RCTBubblingEventBlock onTimedMetadata; @property (nonatomic, copy) RCTDirectEventBlock onTimedMetadata;
@property (nonatomic, copy) RCTBubblingEventBlock onVideoAudioBecomingNoisy; @property (nonatomic, copy) RCTDirectEventBlock onVideoAudioBecomingNoisy;
@property (nonatomic, copy) RCTBubblingEventBlock onVideoFullscreenPlayerWillPresent; @property (nonatomic, copy) RCTDirectEventBlock onVideoFullscreenPlayerWillPresent;
@property (nonatomic, copy) RCTBubblingEventBlock onVideoFullscreenPlayerDidPresent; @property (nonatomic, copy) RCTDirectEventBlock onVideoFullscreenPlayerDidPresent;
@property (nonatomic, copy) RCTBubblingEventBlock onVideoFullscreenPlayerWillDismiss; @property (nonatomic, copy) RCTDirectEventBlock onVideoFullscreenPlayerWillDismiss;
@property (nonatomic, copy) RCTBubblingEventBlock onVideoFullscreenPlayerDidDismiss; @property (nonatomic, copy) RCTDirectEventBlock onVideoFullscreenPlayerDidDismiss;
@property (nonatomic, copy) RCTBubblingEventBlock onReadyForDisplay; @property (nonatomic, copy) RCTDirectEventBlock onReadyForDisplay;
@property (nonatomic, copy) RCTBubblingEventBlock onPlaybackStalled; @property (nonatomic, copy) RCTDirectEventBlock onPlaybackStalled;
@property (nonatomic, copy) RCTBubblingEventBlock onPlaybackResume; @property (nonatomic, copy) RCTDirectEventBlock onPlaybackResume;
@property (nonatomic, copy) RCTBubblingEventBlock onPlaybackRateChange; @property (nonatomic, copy) RCTDirectEventBlock onPlaybackRateChange;
@property (nonatomic, copy) RCTBubblingEventBlock onVideoExternalPlaybackChange; @property (nonatomic, copy) RCTDirectEventBlock onVideoExternalPlaybackChange;
@property (nonatomic, copy) RCTBubblingEventBlock onPictureInPictureStatusChanged; @property (nonatomic, copy) RCTDirectEventBlock onPictureInPictureStatusChanged;
@property (nonatomic, copy) RCTBubblingEventBlock onRestoreUserInterfaceForPictureInPictureStop; @property (nonatomic, copy) RCTDirectEventBlock onRestoreUserInterfaceForPictureInPictureStop;
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER; - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;

View File

@ -54,6 +54,7 @@ static int const RCTVideoUnset = -1;
float _rate; float _rate;
float _maxBitRate; float _maxBitRate;
BOOL _automaticallyWaitsToMinimizeStalling;
BOOL _muted; BOOL _muted;
BOOL _paused; BOOL _paused;
BOOL _repeat; BOOL _repeat;
@ -87,7 +88,7 @@ static int const RCTVideoUnset = -1;
{ {
if ((self = [super init])) { if ((self = [super init])) {
_eventDispatcher = eventDispatcher; _eventDispatcher = eventDispatcher;
_automaticallyWaitsToMinimizeStalling = YES;
_playbackRateObserverRegistered = NO; _playbackRateObserverRegistered = NO;
_isExternalPlaybackActiveObserverRegistered = NO; _isExternalPlaybackActiveObserverRegistered = NO;
_playbackStalled = NO; _playbackStalled = NO;
@ -223,6 +224,7 @@ static int const RCTVideoUnset = -1;
if (_playInBackground) { if (_playInBackground) {
// Needed to play sound in background. See https://developer.apple.com/library/ios/qa/qa1668/_index.html // Needed to play sound in background. See https://developer.apple.com/library/ios/qa/qa1668/_index.html
[_playerLayer setPlayer:nil]; [_playerLayer setPlayer:nil];
[_playerViewController setPlayer:nil];
} }
} }
@ -231,6 +233,7 @@ static int const RCTVideoUnset = -1;
[self applyModifiers]; [self applyModifiers];
if (_playInBackground) { if (_playInBackground) {
[_playerLayer setPlayer:_player]; [_playerLayer setPlayer:_player];
[_playerViewController setPlayer:_player];
} }
} }
@ -354,8 +357,6 @@ static int const RCTVideoUnset = -1;
[self setMaxBitRate:_maxBitRate]; [self setMaxBitRate:_maxBitRate];
[_player pause]; [_player pause];
[_playerViewController.view removeFromSuperview];
_playerViewController = nil;
if (_playbackRateObserverRegistered) { if (_playbackRateObserverRegistered) {
[_player removeObserver:self forKeyPath:playbackRate context:nil]; [_player removeObserver:self forKeyPath:playbackRate context:nil];
@ -376,6 +377,9 @@ static int const RCTVideoUnset = -1;
_isExternalPlaybackActiveObserverRegistered = YES; _isExternalPlaybackActiveObserverRegistered = YES;
[self addPlayerTimeObserver]; [self addPlayerTimeObserver];
if (@available(iOS 10.0, *)) {
[self setAutomaticallyWaitsToMinimizeStalling:_automaticallyWaitsToMinimizeStalling];
}
//Perform on next run loop, otherwise onVideoLoadStart is nil //Perform on next run loop, otherwise onVideoLoadStart is nil
if (self.onVideoLoadStart) { if (self.onVideoLoadStart) {
@ -578,27 +582,11 @@ static int const RCTVideoUnset = -1;
- (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
{ {
// when controls==true, this is a hack to reset the rootview when rotation happens in fullscreen
if (object == _playerViewController.contentOverlayView) {
if ([keyPath isEqualToString:@"frame"]) {
CGRect oldRect = [change[NSKeyValueChangeOldKey] CGRectValue]; if([keyPath isEqualToString:readyForDisplayKeyPath] && [change objectForKey:NSKeyValueChangeNewKey] && self.onReadyForDisplay) {
CGRect newRect = [change[NSKeyValueChangeNewKey] CGRectValue]; self.onReadyForDisplay(@{@"target": self.reactTag});
return;
if (!CGRectEqualToRect(oldRect, newRect)) {
if (CGRectEqualToRect(newRect, [UIScreen mainScreen].bounds)) {
NSLog(@"in fullscreen");
} else NSLog(@"not fullscreen");
[self.reactViewController.view setFrame:[UIScreen mainScreen].bounds];
[self.reactViewController.view setNeedsLayout];
}
return;
} else
return [super observeValueForKeyPath:keyPath ofObject:object change:change context: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]) {
@ -690,12 +678,6 @@ static int const RCTVideoUnset = -1;
_playerBufferEmpty = NO; _playerBufferEmpty = NO;
self.onVideoBuffer(@{@"isBuffering": @(NO), @"target": self.reactTag}); self.onVideoBuffer(@{@"isBuffering": @(NO), @"target": self.reactTag});
} }
} else if (object == _playerLayer) {
if([keyPath isEqualToString:readyForDisplayKeyPath] && [change objectForKey:NSKeyValueChangeNewKey]) {
if([change objectForKey:NSKeyValueChangeNewKey] && self.onReadyForDisplay) {
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) {
@ -716,8 +698,24 @@ static int const RCTVideoUnset = -1;
@"target": self.reactTag}); @"target": self.reactTag});
} }
} }
} else { } else if (object == _playerViewController.contentOverlayView) {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; // when controls==true, this is a hack to reset the rootview when rotation happens in fullscreen
if ([keyPath isEqualToString:@"frame"]) {
CGRect oldRect = [change[NSKeyValueChangeOldKey] CGRectValue];
CGRect newRect = [change[NSKeyValueChangeNewKey] CGRectValue];
if (!CGRectEqualToRect(oldRect, newRect)) {
if (CGRectEqualToRect(newRect, [UIScreen mainScreen].bounds)) {
NSLog(@"in fullscreen");
} else NSLog(@"not fullscreen");
[self.reactViewController.view setFrame:[UIScreen mainScreen].bounds];
[self.reactViewController.view setNeedsLayout];
}
return;
}
} }
} }
@ -870,7 +868,13 @@ static int const RCTVideoUnset = -1;
} else if([_ignoreSilentSwitch isEqualToString:@"obey"]) { } else if([_ignoreSilentSwitch isEqualToString:@"obey"]) {
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:nil]; [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:nil];
} }
[_player play];
if (@available(iOS 10.0, *) && !_automaticallyWaitsToMinimizeStalling) {
[_player playImmediatelyAtRate:_rate];
} else {
[_player play];
[_player setRate:_rate];
}
[_player setRate:_rate]; [_player setRate:_rate];
} }
@ -957,11 +961,19 @@ static int const RCTVideoUnset = -1;
_playerItem.preferredPeakBitRate = maxBitRate; _playerItem.preferredPeakBitRate = maxBitRate;
} }
- (void)setAutomaticallyWaitsToMinimizeStalling:(BOOL)waits
{
_automaticallyWaitsToMinimizeStalling = waits;
_player.automaticallyWaitsToMinimizeStalling = waits;
}
- (void)applyModifiers - (void)applyModifiers
{ {
if (_muted) { if (_muted) {
[_player setVolume:0]; if (!_controls) {
[_player setVolume:0];
}
[_player setMuted:YES]; [_player setMuted:YES];
} else { } else {
[_player setVolume:_volume]; [_player setVolume:_volume];
@ -1283,7 +1295,9 @@ static int const RCTVideoUnset = -1;
{ {
if( _player ) if( _player )
{ {
_playerViewController = [self createPlayerViewController:_player withPlayerItem:_playerItem]; if (!_playerViewController) {
_playerViewController = [self createPlayerViewController:_player withPlayerItem:_playerItem];
}
// to prevent video from being animated when resizeMode is 'cover' // to prevent video from being animated when resizeMode is 'cover'
// resize mode must be set before subview is added // resize mode must be set before subview is added
[self setResizeMode:_resizeMode]; [self setResizeMode:_resizeMode];
@ -1294,6 +1308,8 @@ static int const RCTVideoUnset = -1;
[self addSubview:_playerViewController.view]; [self addSubview:_playerViewController.view];
} }
[_playerViewController addObserver:self forKeyPath:readyForDisplayKeyPath options:NSKeyValueObservingOptionNew context:nil];
[_playerViewController.contentOverlayView addObserver:self forKeyPath:@"frame" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:NULL]; [_playerViewController.contentOverlayView addObserver:self forKeyPath:@"frame" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:NULL];
} }
} }
@ -1488,7 +1504,10 @@ static int const RCTVideoUnset = -1;
[self removePlayerLayer]; [self removePlayerLayer];
[_playerViewController.contentOverlayView removeObserver:self forKeyPath:@"frame"]; [_playerViewController.contentOverlayView removeObserver:self forKeyPath:@"frame"];
[_playerViewController removeObserver:self forKeyPath:readyForDisplayKeyPath];
[_playerViewController.view removeFromSuperview]; [_playerViewController.view removeFromSuperview];
_playerViewController.rctDelegate = nil;
_playerViewController.player = nil;
_playerViewController = nil; _playerViewController = nil;
[self removePlayerTimeObserver]; [self removePlayerTimeObserver];

View File

@ -22,6 +22,7 @@ RCT_EXPORT_VIEW_PROPERTY(src, NSDictionary);
RCT_EXPORT_VIEW_PROPERTY(maxBitRate, float); RCT_EXPORT_VIEW_PROPERTY(maxBitRate, float);
RCT_EXPORT_VIEW_PROPERTY(resizeMode, NSString); RCT_EXPORT_VIEW_PROPERTY(resizeMode, NSString);
RCT_EXPORT_VIEW_PROPERTY(repeat, BOOL); RCT_EXPORT_VIEW_PROPERTY(repeat, BOOL);
RCT_EXPORT_VIEW_PROPERTY(automaticallyWaitsToMinimizeStalling, BOOL);
RCT_EXPORT_VIEW_PROPERTY(allowsExternalPlayback, BOOL); RCT_EXPORT_VIEW_PROPERTY(allowsExternalPlayback, BOOL);
RCT_EXPORT_VIEW_PROPERTY(textTracks, NSArray); RCT_EXPORT_VIEW_PROPERTY(textTracks, NSArray);
RCT_EXPORT_VIEW_PROPERTY(selectedTextTrack, NSDictionary); RCT_EXPORT_VIEW_PROPERTY(selectedTextTrack, NSDictionary);
@ -45,25 +46,25 @@ RCT_EXPORT_VIEW_PROPERTY(filterEnabled, BOOL);
RCT_EXPORT_VIEW_PROPERTY(progressUpdateInterval, float); RCT_EXPORT_VIEW_PROPERTY(progressUpdateInterval, float);
RCT_EXPORT_VIEW_PROPERTY(restoreUserInterfaceForPIPStopCompletionHandler, BOOL); RCT_EXPORT_VIEW_PROPERTY(restoreUserInterfaceForPIPStopCompletionHandler, BOOL);
/* Should support: onLoadStart, onLoad, and onError to stay consistent with Image */ /* Should support: onLoadStart, onLoad, and onError to stay consistent with Image */
RCT_EXPORT_VIEW_PROPERTY(onVideoLoadStart, RCTBubblingEventBlock); RCT_EXPORT_VIEW_PROPERTY(onVideoLoadStart, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onVideoLoad, RCTBubblingEventBlock); RCT_EXPORT_VIEW_PROPERTY(onVideoLoad, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onVideoBuffer, RCTBubblingEventBlock); RCT_EXPORT_VIEW_PROPERTY(onVideoBuffer, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onVideoError, RCTBubblingEventBlock); RCT_EXPORT_VIEW_PROPERTY(onVideoError, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onVideoProgress, RCTBubblingEventBlock); RCT_EXPORT_VIEW_PROPERTY(onVideoProgress, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onBandwidthUpdate, RCTBubblingEventBlock); RCT_EXPORT_VIEW_PROPERTY(onBandwidthUpdate, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onVideoSeek, RCTBubblingEventBlock); RCT_EXPORT_VIEW_PROPERTY(onVideoSeek, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onVideoEnd, RCTBubblingEventBlock); RCT_EXPORT_VIEW_PROPERTY(onVideoEnd, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onTimedMetadata, RCTBubblingEventBlock); RCT_EXPORT_VIEW_PROPERTY(onTimedMetadata, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onVideoAudioBecomingNoisy, RCTBubblingEventBlock); RCT_EXPORT_VIEW_PROPERTY(onVideoAudioBecomingNoisy, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onVideoFullscreenPlayerWillPresent, RCTBubblingEventBlock); RCT_EXPORT_VIEW_PROPERTY(onVideoFullscreenPlayerWillPresent, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onVideoFullscreenPlayerDidPresent, RCTBubblingEventBlock); RCT_EXPORT_VIEW_PROPERTY(onVideoFullscreenPlayerDidPresent, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onVideoFullscreenPlayerWillDismiss, RCTBubblingEventBlock); RCT_EXPORT_VIEW_PROPERTY(onVideoFullscreenPlayerWillDismiss, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onVideoFullscreenPlayerDidDismiss, RCTBubblingEventBlock); RCT_EXPORT_VIEW_PROPERTY(onVideoFullscreenPlayerDidDismiss, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onReadyForDisplay, RCTBubblingEventBlock); RCT_EXPORT_VIEW_PROPERTY(onReadyForDisplay, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onPlaybackStalled, RCTBubblingEventBlock); RCT_EXPORT_VIEW_PROPERTY(onPlaybackStalled, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onPlaybackResume, RCTBubblingEventBlock); RCT_EXPORT_VIEW_PROPERTY(onPlaybackResume, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onPlaybackRateChange, RCTBubblingEventBlock); RCT_EXPORT_VIEW_PROPERTY(onPlaybackRateChange, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onVideoExternalPlaybackChange, RCTBubblingEventBlock); RCT_EXPORT_VIEW_PROPERTY(onVideoExternalPlaybackChange, RCTDirectEventBlock);
RCT_REMAP_METHOD(save, RCT_REMAP_METHOD(save,
options:(NSDictionary *)options options:(NSDictionary *)options
reactTag:(nonnull NSNumber *)reactTag reactTag:(nonnull NSNumber *)reactTag
@ -79,8 +80,8 @@ RCT_REMAP_METHOD(save,
} }
}]; }];
} }
RCT_EXPORT_VIEW_PROPERTY(onPictureInPictureStatusChanged, RCTBubblingEventBlock); RCT_EXPORT_VIEW_PROPERTY(onPictureInPictureStatusChanged, RCTDirectEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onRestoreUserInterfaceForPictureInPictureStop, RCTBubblingEventBlock); RCT_EXPORT_VIEW_PROPERTY(onRestoreUserInterfaceForPictureInPictureStop, RCTDirectEventBlock);
- (NSDictionary *)constantsToExport - (NSDictionary *)constantsToExport
{ {

View File

@ -1,6 +1,6 @@
{ {
"name": "react-native-video", "name": "react-native-video",
"version": "4.4.1", "version": "5.1.0-alpha1",
"description": "A <Video /> element for react-native", "description": "A <Video /> element for react-native",
"main": "Video.js", "main": "Video.js",
"license": "MIT", "license": "MIT",
@ -45,11 +45,6 @@
"scripts": { "scripts": {
"test": "node_modules/.bin/eslint *.js" "test": "node_modules/.bin/eslint *.js"
}, },
"rnpm": {
"android": {
"sourceDir": "./android-exoplayer"
}
},
"files": [ "files": [
"android-exoplayer", "android-exoplayer",
"android", "android",