Merge branch 'master' into master

This commit is contained in:
Hampton Maxwell 2018-05-29 15:08:24 -07:00 committed by GitHub
commit fefbe801ef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 278 additions and 92 deletions

139
README.md
View File

@ -5,11 +5,27 @@ A `<Video>` component for react-native, as seen in
Requires react-native >= 0.40.0, for RN support of 0.19.0 - 0.39.0 please use a pre 1.0 version. Requires react-native >= 0.40.0, for RN support of 0.19.0 - 0.39.0 please use a pre 1.0 version.
### Add it to your project ## TOC
Run `npm i -S react-native-video` * [Installation](#installation)
* [Usage](#usage)
#### iOS ## Installation
Using npm:
```shell
npm install --save react-native-video
```
or using yarn:
```shell
yarn add react-native-video
```
<details>
<summary>iOS</summary>
Run `react-native link` to link the react-native-video library. Run `react-native link` to link the react-native-video library.
@ -28,9 +44,11 @@ If you would like to allow other apps to play music over your video component, a
} }
``` ```
Note: you can also use the `ignoreSilentSwitch` prop, shown below. Note: you can also use the `ignoreSilentSwitch` prop, shown below.
</details>
#### tvOS <details>
<summary>tvOS</summary>
Run `react-native link` to link the react-native-video library. Run `react-native link` to link the react-native-video library.
`react-native link` dont works properly with the tvOS target so we need to add the library manually. `react-native link` dont works properly with the tvOS target so we need to add the library manually.
@ -50,10 +68,10 @@ Scroll to « Linked Frameworks and Libraries » and tap on the + button
Select RCTVideo-tvOS Select RCTVideo-tvOS
<img src="./docs/tvOS-step-4.jpg" width="40%"> <img src="./docs/tvOS-step-4.jpg" width="40%">
</details>
Thats all, you can use react-native-video for your tvOS application <details>
<summary>Android</summary>
#### Android
Run `react-native link` to link the react-native-video library. Run `react-native link` to link the react-native-video library.
@ -61,11 +79,21 @@ 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.
```gradle
include ':react-native-video'
project(':react-native-video').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-video/android-exoplayer')
```
If you need to use the old Android media player based player, use the following instead:
```gradle ```gradle
include ':react-native-video' 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**
```gradle ```gradle
@ -94,8 +122,10 @@ protected List<ReactPackage> getPackages() {
); );
} }
``` ```
</details>
#### Windows <details>
<summary>Windows</summary>
Make the following additions to the given files manually: Make the following additions to the given files manually:
@ -104,19 +134,17 @@ Make the following additions to the given files manually:
Add the `ReactNativeVideo` project to your solution. Add the `ReactNativeVideo` project to your solution.
1. Open the solution in Visual Studio 2015 1. Open the solution in Visual Studio 2015
2. Right-click Solution icon in Solution Explorer > Add > Existing Project... 2. Right-click Solution icon in Solution Explorer > Add > Existing Project
3. * 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:
1. Right-click main application project > Add > Reference... 1. Right-click main application project > Add > Reference...
2. * 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**
@ -143,6 +171,7 @@ using System.Collections.Generic;
... ...
``` ```
</details>
## Usage ## Usage
@ -152,31 +181,39 @@ using System.Collections.Generic;
// on a single screen if you like. // on a single screen if you like.
<Video source={{uri: "background"}} // Can be a URL or a local file. <Video source={{uri: "background"}} // Can be a URL or a local file.
poster="https://baconmockup.com/300/200/" // uri to an image to display until the video plays
ref={(ref) => { ref={(ref) => {
this.player = ref this.player = ref
}} // Store reference }} // Store reference
rate={1.0} // 0 is paused, 1 is normal. rate={1.0} // 0 is paused, 1 is normal.
volume={1.0} // 0 is muted, 1 is normal. volume={1.0} // 0 is muted, 1 is normal.
muted={false} // Mutes the audio entirely. muted={true|false} // Mutes the audio entirely. Default false
paused={false} // Pauses playback entirely. paused={true|false} // Pauses playback entirely. Default false
resizeMode="cover" // Fill the whole screen at aspect ratio.* resizeMode="cover" // Fill the whole screen at aspect ratio.*
repeat={true} // Repeat forever. repeat={true|false} // Repeat forever. Default false
playInBackground={false} // Audio continues to play when app entering background. playInBackground={true|false} // Audio continues to play when app entering background. Default false
playWhenInactive={false} // [iOS] Video continues to play when control or notification center are shown. playWhenInactive={true|false} // [iOS] Video continues to play when control or notification center are shown. Default false
ignoreSilentSwitch={"ignore"} // [iOS] ignore | obey - When 'ignore', audio will still play with the iOS hard silent switch set to silent. When 'obey', audio will toggle with the switch. When not specified, will inherit audio settings as usual. ignoreSilentSwitch={"ignore"} // [iOS] ignore | obey - When 'ignore', audio will still play with the iOS hard silent switch set to silent. When 'obey', audio will toggle with the switch. When not specified, will inherit audio settings as usual.
progressUpdateInterval={250.0} // [iOS] Interval to fire onProgress (default to ~250ms) progressUpdateInterval={250.0} // [iOS] Interval to fire onProgress (default to ~250ms)
onBuffer={this.onBuffer} // Callback when remote video is buffering
onEnd={this.onEnd} // Callback when playback finishes
onError={this.videoError} // Callback when video cannot be loaded
onFullscreenPlayerWillPresent={this.fullScreenPlayerWillPresent} // Callback before fullscreen starts
onFullscreenPlayerDidPresent={this.fullScreenPlayerDidPresent} // Callback after fullscreen started
onFullscreenPlayerWillDismiss={this.fullScreenPlayerWillDismiss} // Callback before fullscreen stops
onFullscreenPlayerDidDismiss={this.fullScreenPlayerDidDissmiss} // Callback after fullscreen stopped
onLoadStart={this.loadStart} // Callback when video starts to load onLoadStart={this.loadStart} // Callback when video starts to load
onLoad={this.setDuration} // Callback when video loads onLoad={this.setDuration} // Callback when video loads
onProgress={this.setTime} // Callback every ~250ms with currentTime onProgress={this.setTime} // Callback every ~250ms with currentTime
onEnd={this.onEnd} // Callback when playback finishes
onError={this.videoError} // Callback when video cannot be loaded
onBuffer={this.onBuffer} // Callback when remote video is buffering
onTimedMetadata={this.onTimedMetadata} // Callback when the stream receive some metadata onTimedMetadata={this.onTimedMetadata} // Callback when the stream receive some metadata
style={styles.backgroundVideo} /> style={styles.backgroundVideo} />
// Later to trigger fullscreen // Later to trigger fullscreen
this.player.presentFullscreenPlayer() this.player.presentFullscreenPlayer()
// Disable fullscreen
this.player.dismissFullscreenPlayer()
// To set video position in seconds (seek) // To set video position in seconds (seek)
this.player.seek(0) this.player.seek(0)
@ -191,39 +228,24 @@ var styles = StyleSheet.create({
}, },
}); });
``` ```
To see the full list of available props, you can check the [propTypes](https://github.com/react-native-community/react-native-video/blob/master/Video.js#L246) of the Video.js component.
- * *For iOS you also need to specify muted for this to work* - By default, iOS 9+ will only load encrypted HTTPS urls. If you need to load content from a webserver that only supports HTTP, you will need to modify your Info.plist file and add the following entry:
## Android Expansion File Usage <img src="./docs/AppTransportSecuritySetting.png" width="50%">
```javascript For more detailed info check this [article](https://cocoacasts.com/how-to-add-app-transport-security-exception-domains)
// Within your render function, assuming you have a file called </details>
// "background.mp4" in your expansion file. Just add your main and (if applicable) patch version
<Video source={{uri: "background", mainVer: 1, patchVer: 0}} // Looks for .mp4 file (background.mp4) in the given expansion version.
rate={1.0} // 0 is paused, 1 is normal.
volume={1.0} // 0 is muted, 1 is normal.
muted={false} // Mutes the audio entirely.
paused={false} // Pauses playback entirely.
resizeMode="cover" // Fill the whole screen at aspect ratio.
repeat={true} // Repeat forever.
onLoadStart={this.loadStart} // Callback when video starts to load
onLoad={this.setDuration} // Callback when video loads
onProgress={this.setTime} // Callback every ~250ms with currentTime
onEnd={this.onEnd} // Callback when playback finishes
onError={this.videoError} // Callback when video cannot be loaded
style={styles.backgroundVideo} />
// Later on in your styles.. ### Android Expansion File Usage
var styles = Stylesheet.create({ Within your render function, assuming you have a file called
backgroundVideo: { "background.mp4" in your expansion file. Just add your main and (if applicable) patch version
position: 'absolute',
top: 0,
left: 0,
bottom: 0,
right: 0,
},
});
``` ```
<Video
source={{uri: "background", mainVer: 1, patchVer: 0}}
/>
```
This will look for an .mp4 file (background.mp4) in the given expansion version.
### Load files with the RN Asset System ### Load files with the RN Asset System
@ -231,10 +253,7 @@ The asset system [introduced in RN `0.14`](http://www.reactnative.com/react-nati
``` ```
<Video <Video
repeat
resizeMode='cover'
source={require('../assets/video/turntable.mp4')} source={require('../assets/video/turntable.mp4')}
style={styles.backgroundVideo}
/> />
``` ```
@ -242,16 +261,6 @@ The asset system [introduced in RN `0.14`](http://www.reactnative.com/react-nati
To enable audio to play in background on iOS the audio session needs to be set to `AVAudioSessionCategoryPlayback`. See [Apple documentation][3] for additional details. (NOTE: there is now a ticket to [expose this as a prop]( https://github.com/react-native-community/react-native-video/issues/310) ) To enable audio to play in background on iOS the audio session needs to be set to `AVAudioSessionCategoryPlayback`. See [Apple documentation][3] for additional details. (NOTE: there is now a ticket to [expose this as a prop]( https://github.com/react-native-community/react-native-video/issues/310) )
## Static Methods
`seek(seconds)`
Seeks the video to the specified time (in seconds). Access using a ref to the component
`presentFullscreenPlayer()`
Toggles a fullscreen player. Access using a ref to the component.
## Examples ## Examples
- See an [Example integration][1] in `react-native-login` *note that this example uses an older version of this library, before we used `export default` -- if you use `require` you will need to do `require('react-native-video').default` as per instructions above.* - See an [Example integration][1] in `react-native-login` *note that this example uses an older version of this library, before we used `export default` -- if you use `require` you will need to do `require('react-native-video').default` as per instructions above.*

View File

@ -1,6 +1,6 @@
import React, {Component} from 'react'; import React, {Component} from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import {StyleSheet, requireNativeComponent, NativeModules, View, Image} from 'react-native'; import {StyleSheet, requireNativeComponent, NativeModules, View, ViewPropTypes, Image} from 'react-native';
import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource'; import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource';
import VideoResizeMode from './VideoResizeMode.js'; import VideoResizeMode from './VideoResizeMode.js';
@ -342,7 +342,7 @@ Video.propTypes = {
translateX: PropTypes.number, translateX: PropTypes.number,
translateY: PropTypes.number, translateY: PropTypes.number,
rotation: PropTypes.number, rotation: PropTypes.number,
...View.propTypes, ...ViewPropTypes,
}; };
const RCTVideo = requireNativeComponent('RCTVideo', Video, { const RCTVideo = requireNativeComponent('RCTVideo', Video, {

1
ViewPropTypes Normal file
View File

@ -0,0 +1 @@
M Video.js

View File

@ -12,9 +12,9 @@ android {
dependencies { dependencies {
provided 'com.facebook.react:react-native:+' provided 'com.facebook.react:react-native:+'
compile 'com.google.android.exoplayer:exoplayer:r2.4.0' compile 'com.google.android.exoplayer:exoplayer:2.7.3'
compile('com.google.android.exoplayer:extension-okhttp:r2.4.0') { compile('com.google.android.exoplayer:extension-okhttp:2.7.3') {
exclude group: 'com.squareup.okhttp3', module: 'okhttp' exclude group: 'com.squareup.okhttp3', module: 'okhttp'
} }
compile 'com.squareup.okhttp3:okhttp:3.4.2' compile 'com.squareup.okhttp3:okhttp:3.9.1'
} }

View File

@ -27,6 +27,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.ui.SubtitleView; import com.google.android.exoplayer2.ui.SubtitleView;
import java.util.List; import java.util.List;
import java.lang.Object;
@TargetApi(16) @TargetApi(16)
public final class ExoPlayerView extends FrameLayout { public final class ExoPlayerView extends FrameLayout {
@ -212,12 +213,12 @@ public final class ExoPlayerView extends FrameLayout {
} }
@Override @Override
public void onPositionDiscontinuity() { public void onPositionDiscontinuity(int reason) {
// Do nothing. // Do nothing.
} }
@Override @Override
public void onTimelineChanged(Timeline timeline, Object manifest) { public void onTimelineChanged(Timeline timeline, Object manifest, int reason) {
// Do nothing. // Do nothing.
} }
@ -235,6 +236,21 @@ public final class ExoPlayerView extends FrameLayout {
public void onMetadata(Metadata metadata) { public void onMetadata(Metadata metadata) {
Log.d("onMetadata", "onMetadata"); Log.d("onMetadata", "onMetadata");
} }
@Override
public void onSeekProcessed() {
// Do nothing.
}
@Override
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
// Do nothing.
}
@Override
public void onRepeatModeChanged(int repeatMode) {
// Do nothing.
}
} }
} }

View File

@ -1,6 +1,7 @@
package com.brentvatne.exoplayer; package com.brentvatne.exoplayer;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.media.AudioManager; import android.media.AudioManager;
import android.net.Uri; import android.net.Uri;
@ -8,6 +9,8 @@ import android.os.Handler;
import android.os.Message; import android.os.Message;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.view.View;
import android.view.Window;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import com.brentvatne.react.R; import com.brentvatne.react.R;
@ -23,6 +26,7 @@ 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.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.extractor.DefaultExtractorsFactory;
@ -54,6 +58,7 @@ import java.net.CookieManager;
import java.net.CookiePolicy; import java.net.CookiePolicy;
import java.lang.Math; import java.lang.Math;
import java.util.Map; import java.util.Map;
import java.lang.Object;
@SuppressLint("ViewConstructor") @SuppressLint("ViewConstructor")
class ReactExoplayerView extends FrameLayout implements class ReactExoplayerView extends FrameLayout implements
@ -87,6 +92,7 @@ class ReactExoplayerView extends FrameLayout implements
private int resumeWindow; private int resumeWindow;
private long resumePosition; private long resumePosition;
private boolean loadVideoStarted; private boolean loadVideoStarted;
private boolean isFullscreen;
private boolean isPaused = true; private boolean isPaused = true;
private boolean isBuffering; private boolean isBuffering;
private float rate = 1f; private float rate = 1f;
@ -116,7 +122,8 @@ class ReactExoplayerView extends FrameLayout implements
&& player.getPlayWhenReady() && player.getPlayWhenReady()
) { ) {
long pos = player.getCurrentPosition(); long pos = player.getCurrentPosition();
eventEmitter.progressChanged(pos, player.getBufferedPercentage()); long bufferedDuration = player.getBufferedPercentage() * player.getDuration();
eventEmitter.progressChanged(pos, bufferedDuration, player.getDuration());
msg = obtainMessage(SHOW_PROGRESS); msg = obtainMessage(SHOW_PROGRESS);
sendMessageDelayed(msg, Math.round(mProgressUpdateInterval)); sendMessageDelayed(msg, Math.round(mProgressUpdateInterval));
} }
@ -333,6 +340,9 @@ class ReactExoplayerView extends FrameLayout implements
} }
private void onStopPlayback() { private void onStopPlayback() {
if (isFullscreen) {
setFullscreen(false);
}
setKeepScreenOn(false); setKeepScreenOn(false);
audioManager.abandonAudioFocus(this); audioManager.abandonAudioFocus(this);
} }
@ -458,17 +468,38 @@ class ReactExoplayerView extends FrameLayout implements
} }
@Override @Override
public void onPositionDiscontinuity() { public void onPositionDiscontinuity(int reason) {
if (playerNeedsSource) { if (playerNeedsSource) {
// This will only occur if the user has performed a seek whilst in the error state. Update the // This will only occur if the user has performed a seek whilst in the error state. Update the
// resume position so that if the user then retries, playback will resume from the position to // resume position so that if the user then retries, playback will resume from the position to
// which they seeked. // which they seeked.
updateResumePosition(); updateResumePosition();
} }
// 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
&& player.getRepeatMode() == Player.REPEAT_MODE_ONE) {
eventEmitter.end();
}
} }
@Override @Override
public void onTimelineChanged(Timeline timeline, Object manifest) { public void onTimelineChanged(Timeline timeline, Object manifest, int reason) {
// Do nothing.
}
@Override
public void onSeekProcessed() {
// Do nothing.
}
@Override
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
// Do nothing.
}
@Override
public void onRepeatModeChanged(int repeatMode) {
// Do nothing. // Do nothing.
} }
@ -485,6 +516,7 @@ class ReactExoplayerView extends FrameLayout implements
@Override @Override
public void onPlayerError(ExoPlaybackException e) { public void onPlayerError(ExoPlaybackException e) {
String errorString = null; String errorString = null;
Exception ex = e;
if (e.type == ExoPlaybackException.TYPE_RENDERER) { if (e.type == ExoPlaybackException.TYPE_RENDERER) {
Exception cause = e.getRendererException(); Exception cause = e.getRendererException();
if (cause instanceof MediaCodecRenderer.DecoderInitializationException) { if (cause instanceof MediaCodecRenderer.DecoderInitializationException) {
@ -507,8 +539,12 @@ class ReactExoplayerView extends FrameLayout implements
} }
} }
} }
else if (e.type == ExoPlaybackException.TYPE_SOURCE) {
ex = e.getSourceException();
errorString = getResources().getString(R.string.unrecognized_media_format);
}
if (errorString != null) { if (errorString != null) {
eventEmitter.error(errorString, e); eventEmitter.error(errorString, ex);
} }
playerNeedsSource = true; playerNeedsSource = true;
if (isBehindLiveWindow(e)) { if (isBehindLiveWindow(e)) {
@ -585,6 +621,13 @@ class ReactExoplayerView extends FrameLayout implements
} }
public void setRepeatModifier(boolean repeat) { public void setRepeatModifier(boolean repeat) {
if (player != null) {
if (repeat) {
player.setRepeatMode(Player.REPEAT_MODE_ONE);
} else {
player.setRepeatMode(Player.REPEAT_MODE_OFF);
}
}
this.repeat = repeat; this.repeat = repeat;
} }
@ -636,4 +679,37 @@ class ReactExoplayerView extends FrameLayout implements
public void setDisableFocus(boolean disableFocus) { public void setDisableFocus(boolean disableFocus) {
this.disableFocus = disableFocus; this.disableFocus = disableFocus;
} }
public void setFullscreen(boolean fullscreen) {
if (fullscreen == isFullscreen) {
return; // Avoid generating events when nothing is changing
}
isFullscreen = fullscreen;
Activity activity = themedReactContext.getCurrentActivity();
if (activity == null) {
return;
}
Window window = activity.getWindow();
View decorView = window.getDecorView();
int uiOptions;
if (isFullscreen) {
if (Util.SDK_INT >= 19) { // 4.4+
uiOptions = SYSTEM_UI_FLAG_HIDE_NAVIGATION
| SYSTEM_UI_FLAG_IMMERSIVE_STICKY
| SYSTEM_UI_FLAG_FULLSCREEN;
} else {
uiOptions = SYSTEM_UI_FLAG_HIDE_NAVIGATION
| SYSTEM_UI_FLAG_FULLSCREEN;
}
eventEmitter.fullscreenWillPresent();
decorView.setSystemUiVisibility(uiOptions);
eventEmitter.fullscreenDidPresent();
} else {
uiOptions = View.SYSTEM_UI_FLAG_VISIBLE;
eventEmitter.fullscreenWillDismiss();
decorView.setSystemUiVisibility(uiOptions);
eventEmitter.fullscreenDidDismiss();
}
}
} }

View File

@ -34,6 +34,7 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
private static final String PROP_RATE = "rate"; private static final String PROP_RATE = "rate";
private static final String PROP_PLAY_IN_BACKGROUND = "playInBackground"; private static final String PROP_PLAY_IN_BACKGROUND = "playInBackground";
private static final String PROP_DISABLE_FOCUS = "disableFocus"; private static final String PROP_DISABLE_FOCUS = "disableFocus";
private static final String PROP_FULLSCREEN = "fullscreen";
@Override @Override
public String getName() { public String getName() {
@ -159,6 +160,11 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
videoView.setDisableFocus(disableFocus); videoView.setDisableFocus(disableFocus);
} }
@ReactProp(name = PROP_FULLSCREEN, defaultBoolean = false)
public void setFullscreen(final ReactExoplayerView videoView, final boolean fullscreen) {
videoView.setFullscreen(fullscreen);
}
private boolean startsWithValidScheme(String uriString) { private boolean startsWithValidScheme(String uriString) {
return uriString.startsWith("http://") return uriString.startsWith("http://")
|| uriString.startsWith("https://") || uriString.startsWith("https://")

View File

@ -31,6 +31,11 @@ class VideoEventEmitter {
private static final String EVENT_PROGRESS = "onVideoProgress"; private static final String EVENT_PROGRESS = "onVideoProgress";
private static final String EVENT_SEEK = "onVideoSeek"; private static final String EVENT_SEEK = "onVideoSeek";
private static final String EVENT_END = "onVideoEnd"; private static final String EVENT_END = "onVideoEnd";
private static final String EVENT_FULLSCREEN_WILL_PRESENT = "onVideoFullscreenPlayerWillPresent";
private static final String EVENT_FULLSCREEN_DID_PRESENT = "onVideoFullscreenPlayerDidPresent";
private static final String EVENT_FULLSCREEN_WILL_DISMISS = "onVideoFullscreenPlayerWillDismiss";
private static final String EVENT_FULLSCREEN_DID_DISMISS = "onVideoFullscreenPlayerDidDismiss";
private static final String EVENT_STALLED = "onPlaybackStalled"; private static final String EVENT_STALLED = "onPlaybackStalled";
private static final String EVENT_RESUME = "onPlaybackResume"; private static final String EVENT_RESUME = "onPlaybackResume";
private static final String EVENT_READY = "onReadyForDisplay"; private static final String EVENT_READY = "onReadyForDisplay";
@ -48,6 +53,10 @@ class VideoEventEmitter {
EVENT_PROGRESS, EVENT_PROGRESS,
EVENT_SEEK, EVENT_SEEK,
EVENT_END, EVENT_END,
EVENT_FULLSCREEN_WILL_PRESENT,
EVENT_FULLSCREEN_DID_PRESENT,
EVENT_FULLSCREEN_WILL_DISMISS,
EVENT_FULLSCREEN_DID_DISMISS,
EVENT_STALLED, EVENT_STALLED,
EVENT_RESUME, EVENT_RESUME,
EVENT_READY, EVENT_READY,
@ -67,6 +76,10 @@ class VideoEventEmitter {
EVENT_PROGRESS, EVENT_PROGRESS,
EVENT_SEEK, EVENT_SEEK,
EVENT_END, EVENT_END,
EVENT_FULLSCREEN_WILL_PRESENT,
EVENT_FULLSCREEN_DID_PRESENT,
EVENT_FULLSCREEN_WILL_DISMISS,
EVENT_FULLSCREEN_DID_DISMISS,
EVENT_STALLED, EVENT_STALLED,
EVENT_RESUME, EVENT_RESUME,
EVENT_READY, EVENT_READY,
@ -89,6 +102,7 @@ class VideoEventEmitter {
private static final String EVENT_PROP_DURATION = "duration"; private static final String EVENT_PROP_DURATION = "duration";
private static final String EVENT_PROP_PLAYABLE_DURATION = "playableDuration"; private static final String EVENT_PROP_PLAYABLE_DURATION = "playableDuration";
private static final String EVENT_PROP_SEEKABLE_DURATION = "seekableDuration";
private static final String EVENT_PROP_CURRENT_TIME = "currentTime"; private static final String EVENT_PROP_CURRENT_TIME = "currentTime";
private static final String EVENT_PROP_SEEK_TIME = "seekTime"; private static final String EVENT_PROP_SEEK_TIME = "seekTime";
private static final String EVENT_PROP_NATURAL_SIZE = "naturalSize"; private static final String EVENT_PROP_NATURAL_SIZE = "naturalSize";
@ -141,10 +155,11 @@ class VideoEventEmitter {
receiveEvent(EVENT_LOAD, event); receiveEvent(EVENT_LOAD, event);
} }
void progressChanged(double currentPosition, double bufferedDuration) { void progressChanged(double currentPosition, double bufferedDuration, double seekableDuration) {
WritableMap event = Arguments.createMap(); WritableMap event = Arguments.createMap();
event.putDouble(EVENT_PROP_CURRENT_TIME, currentPosition / 1000D); event.putDouble(EVENT_PROP_CURRENT_TIME, currentPosition / 1000D);
event.putDouble(EVENT_PROP_PLAYABLE_DURATION, bufferedDuration / 1000D); event.putDouble(EVENT_PROP_PLAYABLE_DURATION, bufferedDuration / 1000D);
event.putDouble(EVENT_PROP_SEEKABLE_DURATION, seekableDuration / 1000D);
receiveEvent(EVENT_PROGRESS, event); receiveEvent(EVENT_PROGRESS, event);
} }
@ -173,6 +188,22 @@ class VideoEventEmitter {
receiveEvent(EVENT_END, null); receiveEvent(EVENT_END, null);
} }
void fullscreenWillPresent() {
receiveEvent(EVENT_FULLSCREEN_WILL_PRESENT, null);
}
void fullscreenDidPresent() {
receiveEvent(EVENT_FULLSCREEN_DID_PRESENT, null);
}
void fullscreenWillDismiss() {
receiveEvent(EVENT_FULLSCREEN_WILL_DISMISS, null);
}
void fullscreenDidDismiss() {
receiveEvent(EVENT_FULLSCREEN_DID_DISMISS, null);
}
void error(String errorString, Exception exception) { void error(String errorString, Exception exception) {
WritableMap error = Arguments.createMap(); WritableMap error = Arguments.createMap();
error.putString(EVENT_PROP_ERROR_STRING, errorString); error.putString(EVENT_PROP_ERROR_STRING, errorString);

View File

@ -9,4 +9,6 @@
<string name="error_instantiating_decoder">Unable to instantiate decoder <xliff:g id="decoder_name">%1$s</xliff:g></string> <string name="error_instantiating_decoder">Unable to instantiate decoder <xliff:g id="decoder_name">%1$s</xliff:g></string>
<string name="unrecognized_media_format">Unrecognized media format</string>
</resources> </resources>

View File

@ -5,6 +5,7 @@ import android.content.res.AssetFileDescriptor;
import android.graphics.Matrix; import android.graphics.Matrix;
import android.media.MediaPlayer; import android.media.MediaPlayer;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.os.Handler; import android.os.Handler;
import android.util.Log; import android.util.Log;
import android.view.MotionEvent; import android.view.MotionEvent;
@ -67,6 +68,7 @@ public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnP
public static final String EVENT_PROP_DURATION = "duration"; public static final String EVENT_PROP_DURATION = "duration";
public static final String EVENT_PROP_PLAYABLE_DURATION = "playableDuration"; public static final String EVENT_PROP_PLAYABLE_DURATION = "playableDuration";
public static final String EVENT_PROP_SEEKABLE_DURATION = "seekableDuration";
public static final String EVENT_PROP_CURRENT_TIME = "currentTime"; public static final String EVENT_PROP_CURRENT_TIME = "currentTime";
public static final String EVENT_PROP_SEEK_TIME = "seekTime"; public static final String EVENT_PROP_SEEK_TIME = "seekTime";
public static final String EVENT_PROP_NATURALSIZE = "naturalSize"; public static final String EVENT_PROP_NATURALSIZE = "naturalSize";
@ -99,6 +101,7 @@ public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnP
private float mVolume = 1.0f; private float mVolume = 1.0f;
private float mProgressUpdateInterval = 250.0f; private float mProgressUpdateInterval = 250.0f;
private float mRate = 1.0f; private float mRate = 1.0f;
private float mActiveRate = 1.0f;
private boolean mPlayInBackground = false; private boolean mPlayInBackground = false;
private boolean mActiveStatePauseStatus = false; private boolean mActiveStatePauseStatus = false;
private boolean mActiveStatePauseStatusInitialized = false; private boolean mActiveStatePauseStatusInitialized = false;
@ -131,6 +134,7 @@ public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnP
WritableMap event = Arguments.createMap(); WritableMap event = Arguments.createMap();
event.putDouble(EVENT_PROP_CURRENT_TIME, mMediaPlayer.getCurrentPosition() / 1000.0); event.putDouble(EVENT_PROP_CURRENT_TIME, mMediaPlayer.getCurrentPosition() / 1000.0);
event.putDouble(EVENT_PROP_PLAYABLE_DURATION, mVideoBufferedDuration / 1000.0); //TODO:mBufferUpdateRunnable event.putDouble(EVENT_PROP_PLAYABLE_DURATION, mVideoBufferedDuration / 1000.0); //TODO:mBufferUpdateRunnable
event.putDouble(EVENT_PROP_SEEKABLE_DURATION, mVideoDuration / 1000.0);
mEventEmitter.receiveEvent(getId(), Events.EVENT_PROGRESS.toString(), event); mEventEmitter.receiveEvent(getId(), Events.EVENT_PROGRESS.toString(), event);
// Check for update after an interval // Check for update after an interval
@ -311,6 +315,7 @@ public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnP
WritableMap event = Arguments.createMap(); WritableMap event = Arguments.createMap();
event.putMap(ReactVideoViewManager.PROP_SRC, src); event.putMap(ReactVideoViewManager.PROP_SRC, src);
mEventEmitter.receiveEvent(getId(), Events.EVENT_LOAD_START.toString(), event); mEventEmitter.receiveEvent(getId(), Events.EVENT_LOAD_START.toString(), event);
isCompleted = false;
try { try {
prepareAsync(this); prepareAsync(this);
@ -357,6 +362,10 @@ public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnP
} else { } else {
if (!mMediaPlayer.isPlaying()) { if (!mMediaPlayer.isPlaying()) {
start(); start();
// Setting the rate unpauses, so we have to wait for an unpause
if (mRate != mActiveRate) {
setRateModifier(mRate);
}
// Also Start the Progress Update Handler // Also Start the Progress Update Handler
mProgressUpdateHandler.post(mProgressUpdateRunnable); mProgressUpdateHandler.post(mProgressUpdateRunnable);
@ -391,8 +400,14 @@ public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnP
mRate = rate; mRate = rate;
if (mMediaPlayerValid) { if (mMediaPlayerValid) {
// TODO: Implement this. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Log.e(ReactVideoViewManager.REACT_CLASS, "Setting playback rate is not yet supported on Android"); if (!mPaused) { // Applying the rate while paused will cause the video to start
mMediaPlayer.setPlaybackParams(mMediaPlayer.getPlaybackParams().setSpeed(rate));
mActiveRate = rate;
}
} else {
Log.e(ReactVideoViewManager.REACT_CLASS, "Setting playback rate is not yet supported on Android versions below 6.0");
}
} }
} }
@ -402,7 +417,7 @@ public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnP
setPausedModifier(mPaused); setPausedModifier(mPaused);
setMutedModifier(mMuted); setMutedModifier(mMuted);
setProgressUpdateInterval(mProgressUpdateInterval); setProgressUpdateInterval(mProgressUpdateInterval);
// setRateModifier(mRate); setRateModifier(mRate);
} }
public void setPlayInBackground(final boolean playInBackground) { public void setPlayInBackground(final boolean playInBackground) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -93,7 +93,7 @@ static NSString *const timedMetadata = @"timedMetadata";
playerLayer.showsPlaybackControls = NO; playerLayer.showsPlaybackControls = NO;
playerLayer.rctDelegate = self; playerLayer.rctDelegate = self;
playerLayer.view.frame = self.bounds; playerLayer.view.frame = self.bounds;
playerLayer.player = _player; playerLayer.player = player;
playerLayer.view.frame = self.bounds; playerLayer.view.frame = self.bounds;
return playerLayer; return playerLayer;
} }
@ -124,6 +124,16 @@ static NSString *const timedMetadata = @"timedMetadata";
return (kCMTimeRangeZero); return (kCMTimeRangeZero);
} }
- (void)addPlayerTimeObserver
{
const Float64 progressUpdateIntervalMS = _progressUpdateInterval / 1000;
// @see endScrubbing in AVPlayerDemoPlaybackViewController.m of https://developer.apple.com/library/ios/samplecode/AVPlayerDemo/Introduction/Intro.html
__weak RCTVideo *weakSelf = self;
_timeObserver = [_player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(progressUpdateIntervalMS, NSEC_PER_SEC)
queue:NULL
usingBlock:^(CMTime time) { [weakSelf sendProgressUpdate]; }
];
}
/* Cancels the previously registered time observer. */ /* Cancels the previously registered time observer. */
-(void)removePlayerTimeObserver -(void)removePlayerTimeObserver
@ -188,6 +198,9 @@ static NSString *const timedMetadata = @"timedMetadata";
CMTime currentTime = _player.currentTime; CMTime currentTime = _player.currentTime;
const Float64 duration = CMTimeGetSeconds(playerDuration); const Float64 duration = CMTimeGetSeconds(playerDuration);
const Float64 currentTimeSecs = CMTimeGetSeconds(currentTime); const Float64 currentTimeSecs = CMTimeGetSeconds(currentTime);
[[NSNotificationCenter defaultCenter] postNotificationName:@"RCTVideo_progress" object:nil userInfo:@{@"progress": [NSNumber numberWithDouble: currentTimeSecs / duration]}];
if( currentTimeSecs >= 0 && self.onVideoProgress) { if( currentTimeSecs >= 0 && self.onVideoProgress) {
self.onVideoProgress(@{ self.onVideoProgress(@{
@"currentTime": [NSNumber numberWithFloat:CMTimeGetSeconds(currentTime)], @"currentTime": [NSNumber numberWithFloat:CMTimeGetSeconds(currentTime)],
@ -249,6 +262,9 @@ static NSString *const timedMetadata = @"timedMetadata";
* observer set */ * observer set */
- (void)removePlayerItemObservers - (void)removePlayerItemObservers
{ {
if (_playerLayer) {
[_playerLayer removeObserver:self forKeyPath:readyForDisplayKeyPath];
}
if (_playerItemObserversSet) { if (_playerItemObserversSet) {
[_playerItem removeObserver:self forKeyPath:statusKeyPath]; [_playerItem removeObserver:self forKeyPath:statusKeyPath];
[_playerItem removeObserver:self forKeyPath:playbackBufferEmptyKeyPath]; [_playerItem removeObserver:self forKeyPath:playbackBufferEmptyKeyPath];
@ -283,13 +299,7 @@ static NSString *const timedMetadata = @"timedMetadata";
[_player addObserver:self forKeyPath:playbackRate options:0 context:nil]; [_player addObserver:self forKeyPath:playbackRate options:0 context:nil];
_playbackRateObserverRegistered = YES; _playbackRateObserverRegistered = YES;
const Float64 progressUpdateIntervalMS = _progressUpdateInterval / 1000; [self addPlayerTimeObserver];
// @see endScrubbing in AVPlayerDemoPlaybackViewController.m of https://developer.apple.com/library/ios/samplecode/AVPlayerDemo/Introduction/Intro.html
__weak RCTVideo *weakSelf = self;
_timeObserver = [_player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(progressUpdateIntervalMS, NSEC_PER_SEC)
queue:NULL
usingBlock:^(CMTime time) { [weakSelf sendProgressUpdate]; }
];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//Perform on next run loop, otherwise onVideoLoadStart is nil //Perform on next run loop, otherwise onVideoLoadStart is nil
@ -487,6 +497,8 @@ static NSString *const timedMetadata = @"timedMetadata";
AVPlayerItem *item = [notification object]; AVPlayerItem *item = [notification object];
[item seekToTime:kCMTimeZero]; [item seekToTime:kCMTimeZero];
[self applyModifiers]; [self applyModifiers];
} else {
[self removePlayerTimeObserver];
} }
} }
@ -566,7 +578,12 @@ static NSString *const timedMetadata = @"timedMetadata";
if (CMTimeCompare(current, cmSeekTime) != 0) { if (CMTimeCompare(current, cmSeekTime) != 0) {
if (!wasPaused) [_player pause]; if (!wasPaused) [_player pause];
[_player seekToTime:cmSeekTime toleranceBefore:tolerance toleranceAfter:tolerance completionHandler:^(BOOL finished) { [_player seekToTime:cmSeekTime toleranceBefore:tolerance toleranceAfter:tolerance completionHandler:^(BOOL finished) {
if (!wasPaused) [_player play]; if (!_timeObserver) {
[self addPlayerTimeObserver];
}
if (!wasPaused) {
[self setPaused:false];
}
if(self.onVideoSeek) { if(self.onVideoSeek) {
self.onVideoSeek(@{@"currentTime": [NSNumber numberWithFloat:CMTimeGetSeconds(item.currentTime)], self.onVideoSeek(@{@"currentTime": [NSNumber numberWithFloat:CMTimeGetSeconds(item.currentTime)],
@"seekTime": [NSNumber numberWithFloat:seekTime], @"seekTime": [NSNumber numberWithFloat:seekTime],

View File

@ -62,4 +62,9 @@ RCT_EXPORT_VIEW_PROPERTY(onPlaybackRateChange, RCTBubblingEventBlock);
}; };
} }
+ (BOOL)requiresMainQueueSetup
{
return YES;
}
@end @end

View File

@ -1,11 +1,15 @@
{ {
"name": "react-native-video", "name": "react-native-video",
"version": "2.0.0", "version": "2.1.1",
"description": "A <Video /> element for react-native", "description": "A <Video /> element for react-native",
"main": "Video.js", "main": "Video.js",
"license": "MIT", "license": "MIT",
"author": "Brent Vatne <brentvatne@gmail.com> (https://github.com/brentvatne)", "author": "Brent Vatne <brentvatne@gmail.com> (https://github.com/brentvatne)",
"contributors": [ "contributors": [
{
"name": "Isaiah Grey",
"email": "isaiahgrey@gmail.com"
},
{ {
"name": "Johannes Lumpe", "name": "Johannes Lumpe",
"email": "johannes@lum.pe" "email": "johannes@lum.pe"
@ -13,6 +17,10 @@
{ {
"name": "Baris Sencan", "name": "Baris Sencan",
"email": "baris.sncn@gmail.com" "email": "baris.sncn@gmail.com"
},
{
"name": "Hampton Maxwell",
"email": "me@hamptonmaxwell.com"
} }
], ],
"repository": { "repository": {

View File

@ -328,7 +328,7 @@ namespace ReactNativeVideo
private readonly JObject _eventData; private readonly JObject _eventData;
public ReactVideoEvent(string eventName, int viewTag, JObject eventData) public ReactVideoEvent(string eventName, int viewTag, JObject eventData)
: base(viewTag, TimeSpan.FromTicks(Environment.TickCount)) : base(viewTag)
{ {
_eventName = eventName; _eventName = eventName;
_eventData = eventData; _eventData = eventData;

View File

@ -334,7 +334,7 @@ namespace ReactNativeVideo
private readonly JObject _eventData; private readonly JObject _eventData;
public ReactVideoEvent(string eventName, int viewTag, JObject eventData) public ReactVideoEvent(string eventName, int viewTag, JObject eventData)
: base(viewTag, TimeSpan.FromTicks(Environment.TickCount)) : base(viewTag)
{ {
_eventName = eventName; _eventName = eventName;
_eventData = eventData; _eventData = eventData;