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
### 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
* 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)

143
README.md
View File

@ -3,10 +3,16 @@
A `<Video>` component for react-native, as seen in
[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 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 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
* [Installation](#installation)
* [iOS](#ios-installation)
* [tvOS](#tvos-installation)
* [Android](#android-installation)
* [Windows](#windows-installation)
* [react-native-dom](#react-native-dom-installation)
* [Usage](#usage)
* [iOS App Transport Security](#ios-app-transport-security)
* [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:
### iOS installation
<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.
### 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).
@ -73,8 +91,9 @@ end
</details>
### tvOS installation
<details>
<summary>tvOS</summary>
<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.
@ -95,14 +114,15 @@ Select RCTVideo-tvOS
<img src="./docs/tvOS-step-4.jpg" width="40%">
</details>
### Android installation
<details>
<summary>Android</summary>
<summary>Android details</summary>
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:
**android/settings.gradle**
#### **android/settings.gradle**
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')
```
#### **android/app/build.gradle**
**android/app/build.gradle**
From version >= 5.0.0, you have to apply this changes:
```gradle
```diff
dependencies {
...
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:
@ -149,12 +182,13 @@ protected List<ReactPackage> getPackages() {
```
</details>
### Windows installation
<details>
<summary>Windows</summary>
<summary>Windows details</summary>
Make the following additions to the given files manually:
**windows/myapp.sln**
#### **windows/myapp.sln**
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`
* 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:
@ -171,7 +205,7 @@ Add a reference to `ReactNativeVideo` to your main application project. From Vis
* UWP: Check `ReactNativeVideo` from Solution Projects.
* WPF: Check `ReactNativeVideo.Net46` from Solution Projects.
**MainPage.cs**
#### **MainPage.cs**
Add the `ReactVideoPackage` class to your list of exported packages.
```cs
@ -198,12 +232,13 @@ using System.Collections.Generic;
```
</details>
### react-native-dom installation
<details>
<summary>react-native-dom</summary>
<summary>react-native-dom details</summary>
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:
@ -257,8 +292,10 @@ var styles = StyleSheet.create({
### Configurable props
* [allowsExternalPlayback](#allowsexternalplayback)
* [audioOnly](#audioonly)
* [automaticallyWaitsToMinimizeStalling](#automaticallyWaitsToMinimizeStalling)
* [bufferConfig](#bufferconfig)
* [controls](#controls)
* [disableFocus](#disableFocus)
* [filter](#filter)
* [filterEnabled](#filterEnabled)
* [fullscreen](#fullscreen)
@ -302,7 +339,9 @@ var styles = StyleSheet.create({
* [onFullscreenPlayerDidDismiss](#onfullscreenplayerdiddismiss)
* [onLoad](#onload)
* [onLoadStart](#onloadstart)
* [onReadyForDisplay](#onreadyfordisplay)
* [onPictureInPictureStatusChanged](#onpictureinpicturestatuschanged)
* [onPlaybackRateChange](#onplaybackratechange)
* [onProgress](#onprogress)
* [onSeek](#onseek)
* [onRestoreUserInterfaceForPictureInPictureStop](#onrestoreuserinterfaceforpictureinpicturestop)
@ -333,6 +372,13 @@ For this to work, the poster prop must be set.
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
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
#### 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
Add video filter
* **FilterType.NONE (default)** - No Filter
@ -953,6 +1006,17 @@ Example:
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
Callback function that is called when picture in picture becomes active or inactive.
@ -969,6 +1033,23 @@ isActive: true
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
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
### 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
#### Gradle 3 and target SDK 26 requirement

View File

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

View File

@ -5,8 +5,8 @@ def safeExtGet(prop, fallback) {
}
android {
compileSdkVersion safeExtGet('compileSdkVersion', 27)
buildToolsVersion safeExtGet('buildToolsVersion', '27.0.3')
compileSdkVersion safeExtGet('compileSdkVersion', 28)
buildToolsVersion safeExtGet('buildToolsVersion', '28.0.3')
compileOptions {
targetCompatibility JavaVersion.VERSION_1_8
@ -15,26 +15,26 @@ android {
defaultConfig {
minSdkVersion safeExtGet('minSdkVersion', 16)
targetSdkVersion safeExtGet('targetSdkVersion', 27)
targetSdkVersion safeExtGet('targetSdkVersion', 28)
versionCode 1
versionName "1.0"
}
}
dependencies {
compileOnly "com.facebook.react:react-native:${safeExtGet('reactNativeVersion', '+')}"
implementation('com.google.android.exoplayer:exoplayer:2.9.3') {
implementation "com.facebook.react:react-native:${safeExtGet('reactNativeVersion', '+')}"
implementation('com.google.android.exoplayer:exoplayer:2.10.4') {
exclude group: 'com.android.support'
}
// All support libs must use the same version
implementation "com.android.support:support-annotations:${safeExtGet('supportLibVersion', '+')}"
implementation "com.android.support:support-compat:${safeExtGet('supportLibVersion', '+')}"
implementation "com.android.support:support-media-compat:${safeExtGet('supportLibVersion', '+')}"
implementation "androidx.annotation:annotation:1.1.0"
implementation "androidx.core:core:1.1.0"
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'
}
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;
import android.content.Context;
import android.content.ContextWrapper;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.modules.network.CookieJarContainer;
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.util.Util;
import okhttp3.Cookie;
import okhttp3.JavaNetCookieJar;
import okhttp3.OkHttpClient;
import java.util.Map;
public class 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.content.Context;
import android.support.v4.content.ContextCompat;
import androidx.core.content.ContextCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;

View File

@ -15,6 +15,6 @@ class RawResourceDataSourceFactory implements DataSource.Factory {
@Override
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.google.android.exoplayer2.C;
import com.google.android.exoplayer2.DefaultLoadControl;
import com.google.android.exoplayer2.DefaultRenderersFactory;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.ExoPlayerFactory;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
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.MediaCodecUtil;
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.ExtractorMediaSource;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MergingMediaSource;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.source.SingleSampleMediaSource;
import com.google.android.exoplayer2.source.TrackGroupArray;
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.DefaultDashChunkSource;
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.trackselection.AdaptiveTrackSelection;
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.TrackSelection;
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.DefaultAllocator;
import com.google.android.exoplayer2.upstream.BandwidthMeter;
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.ui.PlayerControlView;
import java.net.CookieHandler;
import java.net.CookieManager;
import java.net.CookiePolicy;
import java.lang.Math;
import java.util.Map;
import java.lang.Object;
import java.util.ArrayList;
import java.util.Locale;
import java.util.Map;
@SuppressLint("ViewConstructor")
class ReactExoplayerView extends FrameLayout implements
LifecycleEventListener,
ExoPlayer.EventListener,
Player.EventListener,
BandwidthMeter.EventListener,
BecomingNoisyListener,
AudioManager.OnAudioFocusChangeListener,
MetadataRenderer.Output {
MetadataOutput {
private static final String TAG = "ReactExoplayerView";
private static final DefaultBandwidthMeter BANDWIDTH_METER = new DefaultBandwidthMeter();
private static final CookieManager DEFAULT_COOKIE_MANAGER;
private static final int SHOW_PROGRESS = 1;
private static final int REPORT_BANDWIDTH = 1;
static {
DEFAULT_COOKIE_MANAGER = new CookieManager();
@ -97,11 +90,12 @@ class ReactExoplayerView extends FrameLayout implements
}
private final VideoEventEmitter eventEmitter;
private final ReactExoplayerConfig config;
private final DefaultBandwidthMeter bandwidthMeter;
private PlayerControlView playerControlView;
private View playPauseControlContainer;
private Player.EventListener eventListener;
private Handler mainHandler;
private ExoPlayerView exoPlayerView;
private DataSource.Factory mediaDataSourceFactory;
@ -116,6 +110,7 @@ class ReactExoplayerView extends FrameLayout implements
private boolean isInBackground;
private boolean isPaused;
private boolean isBuffering;
private boolean muted = false;
private float rate = 1f;
private float audioVolume = 1f;
private int minLoadRetryCount = 3;
@ -135,7 +130,6 @@ class ReactExoplayerView extends FrameLayout implements
private Dynamic audioTrackValue;
private String videoTrackType;
private Dynamic videoTrackValue;
private ReadableArray audioTracks;
private String textTrackType;
private Dynamic textTrackValue;
private ReadableArray textTracks;
@ -144,6 +138,7 @@ class ReactExoplayerView extends FrameLayout implements
private boolean playInBackground = false;
private Map<String, String> requestHeaders;
private boolean mReportBandwidth = false;
private boolean controls;
// \ End props
// React
@ -157,7 +152,7 @@ class ReactExoplayerView extends FrameLayout implements
switch (msg.what) {
case SHOW_PROGRESS:
if (player != null
&& player.getPlaybackState() == ExoPlayer.STATE_READY
&& player.getPlaybackState() == Player.STATE_READY
&& player.getPlayWhenReady()
) {
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);
this.themedReactContext = context;
this.eventEmitter = new VideoEventEmitter(context);
this.config = config;
this.bandwidthMeter = config.getBandwidthMeter();
createViews();
@ -196,7 +192,6 @@ class ReactExoplayerView extends FrameLayout implements
private void createViews() {
clearResumePosition();
mediaDataSourceFactory = buildDataSourceFactory(true);
mainHandler = new Handler();
if (CookieHandler.getDefault() != 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
*/
private void togglePlayerControlVisibility() {
if(player == null) return;
reLayout(playerControlView);
if (playerControlView.isVisible()) {
playerControlView.hide();
@ -312,10 +308,15 @@ class ReactExoplayerView extends FrameLayout implements
* Adding Player control to the frame layout
*/
private void addPlayerControl() {
if(player == null) return;
LayoutParams layoutParams = new LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
playerControlView.setLayoutParams(layoutParams);
int indexOfPC = indexOfChild(playerControlView);
if (indexOfPC != -1) {
removeViewAt(indexOfPC);
}
addView(playerControlView, 1, layoutParams);
}
@ -333,20 +334,35 @@ class ReactExoplayerView extends FrameLayout implements
}
private void initializePlayer() {
ReactExoplayerView self = this;
// This ensures all props have been settled, to avoid async racing conditions.
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
if (player == null) {
TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory(BANDWIDTH_METER);
TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory();
trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
trackSelector.setParameters(trackSelector.buildUponParameters()
.setMaxVideoBitrate(maxBitRate == 0 ? Integer.MAX_VALUE : maxBitRate));
DefaultAllocator allocator = new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE);
DefaultLoadControl defaultLoadControl = new DefaultLoadControl(allocator, minBufferMs, maxBufferMs, bufferForPlaybackMs, bufferForPlaybackAfterRebufferMs, -1, true);
player = ExoPlayerFactory.newSimpleInstance(getContext(), trackSelector, defaultLoadControl);
player.addListener(this);
player.setMetadataOutput(this);
DefaultLoadControl.Builder defaultLoadControlBuilder = new DefaultLoadControl.Builder();
defaultLoadControlBuilder.setAllocator(allocator);
defaultLoadControlBuilder.setBufferDurationsMs(minBufferMs, maxBufferMs, bufferForPlaybackMs, bufferForPlaybackAfterRebufferMs);
defaultLoadControlBuilder.setTargetBufferBytes(-1);
defaultLoadControlBuilder.setPrioritizeTimeOverSizeThresholds(true);
DefaultLoadControl defaultLoadControl = defaultLoadControlBuilder.createDefaultLoadControl();
DefaultRenderersFactory renderersFactory =
new DefaultRenderersFactory(getContext())
.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(this);
BANDWIDTH_METER.addEventListener(new Handler(), this);
audioBecomingNoisyReceiver.setListener(self);
bandwidthMeter.addEventListener(new Handler(), self);
setPlayWhenReady(!isPaused);
playerNeedsSource = true;
@ -380,6 +396,10 @@ class ReactExoplayerView extends FrameLayout implements
// Initializing the playerControlView
initializePlayerControl();
setControls(controls);
applyModifiers();
}
}, 1);
}
private MediaSource buildMediaSource(Uri uri, String overrideExtension) {
@ -387,21 +407,31 @@ class ReactExoplayerView extends FrameLayout implements
: uri.getLastPathSegment());
switch (type) {
case C.TYPE_SS:
return new SsMediaSource(uri, buildDataSourceFactory(false),
return new SsMediaSource.Factory(
new DefaultSsChunkSource.Factory(mediaDataSourceFactory),
minLoadRetryCount, SsMediaSource.DEFAULT_LIVE_PRESENTATION_DELAY_MS,
mainHandler, null);
buildDataSourceFactory(false)
).setLoadErrorHandlingPolicy(
config.buildLoadErrorHandlingPolicy(minLoadRetryCount)
).createMediaSource(uri);
case C.TYPE_DASH:
return new DashMediaSource(uri, buildDataSourceFactory(false),
return new DashMediaSource.Factory(
new DefaultDashChunkSource.Factory(mediaDataSourceFactory),
minLoadRetryCount, DashMediaSource.DEFAULT_LIVE_PRESENTATION_DELAY_MS,
mainHandler, null);
buildDataSourceFactory(false)
).setLoadErrorHandlingPolicy(
config.buildLoadErrorHandlingPolicy(minLoadRetryCount)
).createMediaSource(uri);
case C.TYPE_HLS:
return new HlsMediaSource(uri, mediaDataSourceFactory,
minLoadRetryCount, mainHandler, null);
return new HlsMediaSource.Factory(
mediaDataSourceFactory
).setLoadErrorHandlingPolicy(
config.buildLoadErrorHandlingPolicy(minLoadRetryCount)
).createMediaSource(uri);
case C.TYPE_OTHER:
return new ExtractorMediaSource(uri, mediaDataSourceFactory, new DefaultExtractorsFactory(),
mainHandler, null);
return new ProgressiveMediaSource.Factory(
mediaDataSourceFactory
).setLoadErrorHandlingPolicy(
config.buildLoadErrorHandlingPolicy(minLoadRetryCount)
).createMediaSource(uri);
default: {
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) {
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() {
if (player != null) {
updateResumePosition();
player.release();
player.setMetadataOutput(null);
player = null;
player.removeMetadataOutput(this);
trackSelector = null;
player = null;
}
progressHandler.removeMessages(SHOW_PROGRESS);
themedReactContext.removeLifecycleEventListener(this);
audioBecomingNoisyReceiver.removeListener();
BANDWIDTH_METER.removeEventListener(this);
bandwidthMeter.removeEventListener(this);
}
private boolean requestAudioFocus() {
if (disableFocus) {
if (disableFocus || srcUri == null) {
return true;
}
int result = audioManager.requestAudioFocus(this,
@ -476,12 +507,12 @@ class ReactExoplayerView extends FrameLayout implements
private void startPlayback() {
if (player != null) {
switch (player.getPlaybackState()) {
case ExoPlayer.STATE_IDLE:
case ExoPlayer.STATE_ENDED:
case Player.STATE_IDLE:
case Player.STATE_ENDED:
initializePlayer();
break;
case ExoPlayer.STATE_BUFFERING:
case ExoPlayer.STATE_READY:
case Player.STATE_BUFFERING:
case Player.STATE_READY:
if (!player.getPlayWhenReady()) {
setPlayWhenReady(true);
}
@ -534,12 +565,13 @@ class ReactExoplayerView extends FrameLayout implements
/**
* 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.
* @return A new DataSource factory.
*/
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
@ -560,13 +592,17 @@ class ReactExoplayerView extends FrameLayout implements
if (player != null) {
if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
// Lower the volume
if (!muted) {
player.setVolume(audioVolume * 0.8f);
}
} else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
// Raise it back to normal
if (!muted) {
player.setVolume(audioVolume * 1);
}
}
}
}
// AudioBecomingNoisyListener implementation
@ -575,7 +611,7 @@ class ReactExoplayerView extends FrameLayout implements
eventEmitter.audioBecomingNoisy();
}
// ExoPlayer.EventListener implementation
// Player.EventListener implementation
@Override
public void onLoadingChanged(boolean isLoading) {
@ -586,15 +622,15 @@ class ReactExoplayerView extends FrameLayout implements
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
String text = "onStateChanged: playWhenReady=" + playWhenReady + ", playbackState=";
switch (playbackState) {
case ExoPlayer.STATE_IDLE:
case Player.STATE_IDLE:
text += "idle";
eventEmitter.idle();
break;
case ExoPlayer.STATE_BUFFERING:
case Player.STATE_BUFFERING:
text += "buffering";
onBuffering(true);
break;
case ExoPlayer.STATE_READY:
case Player.STATE_READY:
text += "ready";
eventEmitter.ready();
onBuffering(false);
@ -605,7 +641,7 @@ class ReactExoplayerView extends FrameLayout implements
playerControlView.show();
}
break;
case ExoPlayer.STATE_ENDED:
case Player.STATE_ENDED:
text += "ended";
eventEmitter.end();
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
// 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) {
eventEmitter.end();
}
@ -849,7 +885,9 @@ class ReactExoplayerView extends FrameLayout implements
this.srcUri = uri;
this.extension = extension;
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) {
reloadSource();
@ -894,6 +932,11 @@ class ReactExoplayerView extends FrameLayout implements
exoPlayerView.setResizeMode(resizeMode);
}
private void applyModifiers() {
setRepeatModifier(repeat);
setMutedModifier(muted);
}
public void setRepeatModifier(boolean repeat) {
if (player != null) {
if (repeat) {
@ -906,6 +949,7 @@ class ReactExoplayerView extends FrameLayout implements
}
public void setSelectedTrack(int trackType, String type, Dynamic value) {
if (player == null) return;
int rendererIndex = getTrackRendererIndex(trackType);
if (rendererIndex == C.INDEX_UNSET) {
return;
@ -1048,6 +1092,7 @@ class ReactExoplayerView extends FrameLayout implements
}
public void setMutedModifier(boolean muted) {
this.muted = muted;
audioVolume = muted ? 0.f : 1.f;
if (player != null) {
player.setVolume(audioVolume);
@ -1156,10 +1201,15 @@ class ReactExoplayerView extends FrameLayout implements
* @param controls Controls prop, if true enable controls, if false disable them
*/
public void setControls(boolean controls) {
if (controls && exoPlayerView != null) {
this.controls = controls;
if (player == null || exoPlayerView == null) return;
if (controls) {
addPlayerControl();
} else if (getChildAt(1) instanceof PlayerControlView && exoPlayerView != null) {
removeViewAt(1);
} else {
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_CONTROLS = "controls";
private ReactExoplayerConfig config;
public ReactExoplayerViewManager(ReactExoplayerConfig config) {
this.config = config;
}
@Override
public String getName() {
return REACT_CLASS;
@ -67,7 +73,7 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
@Override
protected ReactExoplayerView createViewInstance(ThemedReactContext themedReactContext) {
return new ReactExoplayerView(themedReactContext);
return new ReactExoplayerView(themedReactContext, config);
}
@Override

View File

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

View File

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

View File

@ -1,5 +1,7 @@
package com.brentvatne.react;
import com.brentvatne.exoplayer.DefaultReactExoplayerConfig;
import com.brentvatne.exoplayer.ReactExoplayerConfig;
import com.brentvatne.exoplayer.ReactExoplayerViewManager;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
@ -12,6 +14,15 @@ import java.util.List;
public class ReactVideoPackage implements ReactPackage {
private ReactExoplayerConfig config;
public ReactVideoPackage() {
}
public ReactVideoPackage(ReactExoplayerConfig config) {
this.config = config;
}
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Collections.emptyList();
@ -25,6 +36,9 @@ public class ReactVideoPackage implements ReactPackage {
@Override
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 {
compileSdkVersion safeExtGet('compileSdkVersion', 27)
buildToolsVersion safeExtGet('buildToolsVersion', '27.0.3')
compileSdkVersion safeExtGet('compileSdkVersion', 28)
buildToolsVersion safeExtGet('buildToolsVersion', '28.0.3')
defaultConfig {
minSdkVersion safeExtGet('minSdkVersion', 16)
targetSdkVersion safeExtGet('targetSdkVersion', 27)
targetSdkVersion safeExtGet('targetSdkVersion', 28)
versionCode 1
versionName "1.0"
ndk {
@ -21,6 +21,6 @@ android {
dependencies {
//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'
}

View File

@ -37,6 +37,7 @@ class RCTVideo extends RCTView {
this.videoElement = this.initializeVideoElement();
this.videoElement.addEventListener("ended", this.onEnd);
this.videoElement.addEventListener("loadeddata", this.onLoad);
this.videoElement.addEventListener("canplay", this.onReadyForDisplay);
this.videoElement.addEventListener("loadstart", this.onLoadStart);
this.videoElement.addEventListener("pause", this.onPause);
this.videoElement.addEventListener("play", this.onPlay);
@ -51,6 +52,7 @@ class RCTVideo extends RCTView {
detachFromView(view: UIView) {
this.videoElement.removeEventListener("ended", this.onEnd);
this.videoElement.removeEventListener("loadeddata", this.onLoad);
this.videoElement.removeEventListener("canplay", this.onReadyForDisplay);
this.videoElement.removeEventListener("loadstart", this.onLoadStart);
this.videoElement.removeEventListener("pause", this.onPause);
this.videoElement.removeEventListener("play", this.onPlay);
@ -203,6 +205,10 @@ class RCTVideo extends RCTView {
this.sendEvent("topVideoLoad", payload);
}
onReadyForDisplay = () => {
this.sendEvent("onReadyForDisplay");
}
onLoadStart = () => {
const src = this.videoElement.currentSrc;
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"
/**
@ -83,13 +87,16 @@ def enableSeparateBuildPerCPUArchitecture = false
def enableProguardInReleaseBuilds = false
android {
compileSdkVersion 25
buildToolsVersion '25.0.2'
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
defaultConfig {
applicationId "com.videoplayer"
minSdkVersion 16
targetSdkVersion 25
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0"
ndk {
@ -108,6 +115,7 @@ android {
release {
signingConfig signingConfigs.debug
minifyEnabled enableProguardInReleaseBuilds
matchingFallbacks = ['release', 'debug']
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
}
@ -127,10 +135,9 @@ android {
}
dependencies {
compile "com.android.support:appcompat-v7:25.2.0"
compile "com.facebook.react:react-native:+" // From node_modules
compile project(':react-native-video')
// compile project(':react-native-video-exoplayer') // uncomment to use exoplayer
implementation project(':react-native-video')
implementation "com.facebook.react:react-native:+" // From node_modules
implementation 'org.webkit:android-jsc:+'
}
// Run this once to be able to run the application with BUCK

View File

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

View File

@ -2,8 +2,8 @@ package com.videoplayer;
import android.app.Application;
import com.brentvatne.react.ReactVideoPackage;
import com.facebook.react.ReactApplication;
import com.brentvatne.react.ReactVideoPackage;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
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.
buildscript {
ext {
buildToolsVersion = "28.0.3"
minSdkVersion = 16
compileSdkVersion = 28
targetSdkVersion = 28
supportLibVersion = "28.0.0"
}
repositories {
jcenter()
google()
}
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
// in the individual module build.gradle files
@ -15,10 +24,18 @@ buildscript {
allprojects {
repositories {
mavenLocal()
google()
maven {
url "https://jitpack.io"
}
jcenter()
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
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
# 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
zipStoreBase=GRADLE_USER_HOME
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'
include ':app',
':react-native-video',
':react-native-video-exoplayer'
include ':react-native-video'
project(':react-native-video').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-video/android-exoplayer')
// Quick Local Development
//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')
include ':app'

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,
"scripts": {
"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": {
"react": "16.4.1",
"react-native": "0.56.0",
"react": "16.9.0",
"react-native": "0.60.5",
"react-native-video": "file:../.."
},
"devDependencies": {
"babel-jest": "22.4.1",
"babel-preset-react-native": "5.0.2",
"express": "^4.16.2",
"jest": "22.4.2",
"react-test-renderer": "16.2.0"
"@babel/core": "^7.6.0",
"@babel/runtime": "^7.6.0",
"@react-native-community/eslint-config": "^0.0.5",
"babel-jest": "^24.9.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>
#endif
@property (nonatomic, copy) RCTBubblingEventBlock onVideoLoadStart;
@property (nonatomic, copy) RCTBubblingEventBlock onVideoLoad;
@property (nonatomic, copy) RCTBubblingEventBlock onVideoBuffer;
@property (nonatomic, copy) RCTBubblingEventBlock onVideoError;
@property (nonatomic, copy) RCTBubblingEventBlock onVideoProgress;
@property (nonatomic, copy) RCTBubblingEventBlock onBandwidthUpdate;
@property (nonatomic, copy) RCTBubblingEventBlock onVideoSeek;
@property (nonatomic, copy) RCTBubblingEventBlock onVideoEnd;
@property (nonatomic, copy) RCTBubblingEventBlock onTimedMetadata;
@property (nonatomic, copy) RCTBubblingEventBlock onVideoAudioBecomingNoisy;
@property (nonatomic, copy) RCTBubblingEventBlock onVideoFullscreenPlayerWillPresent;
@property (nonatomic, copy) RCTBubblingEventBlock onVideoFullscreenPlayerDidPresent;
@property (nonatomic, copy) RCTBubblingEventBlock onVideoFullscreenPlayerWillDismiss;
@property (nonatomic, copy) RCTBubblingEventBlock onVideoFullscreenPlayerDidDismiss;
@property (nonatomic, copy) RCTBubblingEventBlock onReadyForDisplay;
@property (nonatomic, copy) RCTBubblingEventBlock onPlaybackStalled;
@property (nonatomic, copy) RCTBubblingEventBlock onPlaybackResume;
@property (nonatomic, copy) RCTBubblingEventBlock onPlaybackRateChange;
@property (nonatomic, copy) RCTBubblingEventBlock onVideoExternalPlaybackChange;
@property (nonatomic, copy) RCTBubblingEventBlock onPictureInPictureStatusChanged;
@property (nonatomic, copy) RCTBubblingEventBlock onRestoreUserInterfaceForPictureInPictureStop;
@property (nonatomic, copy) RCTDirectEventBlock onVideoLoadStart;
@property (nonatomic, copy) RCTDirectEventBlock onVideoLoad;
@property (nonatomic, copy) RCTDirectEventBlock onVideoBuffer;
@property (nonatomic, copy) RCTDirectEventBlock onVideoError;
@property (nonatomic, copy) RCTDirectEventBlock onVideoProgress;
@property (nonatomic, copy) RCTDirectEventBlock onBandwidthUpdate;
@property (nonatomic, copy) RCTDirectEventBlock onVideoSeek;
@property (nonatomic, copy) RCTDirectEventBlock onVideoEnd;
@property (nonatomic, copy) RCTDirectEventBlock onTimedMetadata;
@property (nonatomic, copy) RCTDirectEventBlock onVideoAudioBecomingNoisy;
@property (nonatomic, copy) RCTDirectEventBlock onVideoFullscreenPlayerWillPresent;
@property (nonatomic, copy) RCTDirectEventBlock onVideoFullscreenPlayerDidPresent;
@property (nonatomic, copy) RCTDirectEventBlock onVideoFullscreenPlayerWillDismiss;
@property (nonatomic, copy) RCTDirectEventBlock onVideoFullscreenPlayerDidDismiss;
@property (nonatomic, copy) RCTDirectEventBlock onReadyForDisplay;
@property (nonatomic, copy) RCTDirectEventBlock onPlaybackStalled;
@property (nonatomic, copy) RCTDirectEventBlock onPlaybackResume;
@property (nonatomic, copy) RCTDirectEventBlock onPlaybackRateChange;
@property (nonatomic, copy) RCTDirectEventBlock onVideoExternalPlaybackChange;
@property (nonatomic, copy) RCTDirectEventBlock onPictureInPictureStatusChanged;
@property (nonatomic, copy) RCTDirectEventBlock onRestoreUserInterfaceForPictureInPictureStop;
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;

View File

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

View File

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

View File

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