Merge branch 'master' into allow-progress-update-interval-to-work-on-ios
This commit is contained in:
commit
fd8ebbd74a
139
README.md
139
README.md
@ -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.
|
||||
|
||||
### 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.
|
||||
|
||||
@ -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.
|
||||
</details>
|
||||
|
||||
#### tvOS
|
||||
|
||||
<details>
|
||||
<summary>tvOS</summary>
|
||||
|
||||
Run `react-native link` to link the react-native-video library.
|
||||
|
||||
`react-native link` don’t works properly with the tvOS target so we need to add the library manually.
|
||||
@ -50,10 +68,10 @@ Scroll to « Linked Frameworks and Libraries » and tap on the + button
|
||||
Select RCTVideo-tvOS
|
||||
|
||||
<img src="./docs/tvOS-step-4.jpg" width="40%">
|
||||
</details>
|
||||
|
||||
That’s all, you can use react-native-video for your tvOS application
|
||||
|
||||
#### Android
|
||||
<details>
|
||||
<summary>Android</summary>
|
||||
|
||||
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**
|
||||
|
||||
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
|
||||
include ':react-native-video'
|
||||
project(':react-native-video').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-video/android')
|
||||
```
|
||||
|
||||
|
||||
**android/app/build.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:
|
||||
|
||||
@ -104,19 +134,17 @@ Make the following additions to the given files manually:
|
||||
Add the `ReactNativeVideo` project to your solution.
|
||||
|
||||
1. Open the solution in Visual Studio 2015
|
||||
2. Right-click Solution icon in Solution Explorer > Add > Existing Project...
|
||||
3.
|
||||
UWP: Select `node_modules\react-native-video\windows\ReactNativeVideo\ReactNativeVideo.csproj`
|
||||
WPF: Select `node_modules\react-native-video\windows\ReactNativeVideo.Net46\ReactNativeVideo.Net46.csproj`
|
||||
2. Right-click Solution icon in Solution Explorer > Add > Existing Project
|
||||
* 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**
|
||||
|
||||
Add a reference to `ReactNativeVideo` to your main application project. From Visual Studio 2015:
|
||||
|
||||
1. Right-click main application project > Add > Reference...
|
||||
2.
|
||||
UWP: Check `ReactNativeVideo` from Solution Projects.
|
||||
WPF: Check `ReactNativeVideo.Net46` from Solution Projects.
|
||||
* UWP: Check `ReactNativeVideo` from Solution Projects.
|
||||
* WPF: Check `ReactNativeVideo.Net46` from Solution Projects.
|
||||
|
||||
**MainPage.cs**
|
||||
|
||||
@ -143,6 +171,7 @@ using System.Collections.Generic;
|
||||
|
||||
...
|
||||
```
|
||||
</details>
|
||||
|
||||
## Usage
|
||||
|
||||
@ -152,31 +181,39 @@ using System.Collections.Generic;
|
||||
// on a single screen if you like.
|
||||
|
||||
<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) => {
|
||||
this.player = ref
|
||||
}} // Store reference
|
||||
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.
|
||||
muted={true|false} // Mutes the audio entirely. Default false
|
||||
paused={true|false} // Pauses playback entirely. Default false
|
||||
resizeMode="cover" // Fill the whole screen at aspect ratio.*
|
||||
repeat={true} // Repeat forever.
|
||||
playInBackground={false} // Audio continues to play when app entering background.
|
||||
playWhenInactive={false} // [iOS] Video continues to play when control or notification center are shown.
|
||||
repeat={true|false} // Repeat forever. Default false
|
||||
playInBackground={true|false} // Audio continues to play when app entering background. Default false
|
||||
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.
|
||||
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
|
||||
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
|
||||
onBuffer={this.onBuffer} // Callback when remote video is buffering
|
||||
onTimedMetadata={this.onTimedMetadata} // Callback when the stream receive some metadata
|
||||
style={styles.backgroundVideo} />
|
||||
|
||||
// Later to trigger fullscreen
|
||||
this.player.presentFullscreenPlayer()
|
||||
|
||||
// Disable fullscreen
|
||||
this.player.dismissFullscreenPlayer()
|
||||
|
||||
// To set video position in seconds (seek)
|
||||
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
|
||||
// Within your render function, assuming you have a file called
|
||||
// "background.mp4" in your expansion file. Just add your main and (if applicable) patch version
|
||||
<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} />
|
||||
For more detailed info check this [article](https://cocoacasts.com/how-to-add-app-transport-security-exception-domains)
|
||||
</details>
|
||||
|
||||
// Later on in your styles..
|
||||
var styles = Stylesheet.create({
|
||||
backgroundVideo: {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
},
|
||||
});
|
||||
### Android Expansion File Usage
|
||||
Within your render function, assuming you have a file called
|
||||
"background.mp4" in your expansion file. Just add your main and (if applicable) patch version
|
||||
```
|
||||
<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
|
||||
|
||||
@ -231,10 +253,7 @@ The asset system [introduced in RN `0.14`](http://www.reactnative.com/react-nati
|
||||
|
||||
```
|
||||
<Video
|
||||
repeat
|
||||
resizeMode='cover'
|
||||
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) )
|
||||
|
||||
## 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
|
||||
|
||||
- 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.*
|
||||
|
4
Video.js
4
Video.js
@ -1,6 +1,6 @@
|
||||
import React, {Component} from 'react';
|
||||
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 VideoResizeMode from './VideoResizeMode.js';
|
||||
|
||||
@ -308,7 +308,7 @@ Video.propTypes = {
|
||||
translateX: PropTypes.number,
|
||||
translateY: PropTypes.number,
|
||||
rotation: PropTypes.number,
|
||||
...View.propTypes,
|
||||
...ViewPropTypes,
|
||||
};
|
||||
|
||||
const RCTVideo = requireNativeComponent('RCTVideo', Video, {
|
||||
|
1
ViewPropTypes
Normal file
1
ViewPropTypes
Normal file
@ -0,0 +1 @@
|
||||
M Video.js
|
@ -12,9 +12,9 @@ android {
|
||||
|
||||
dependencies {
|
||||
provided 'com.facebook.react:react-native:+'
|
||||
compile 'com.google.android.exoplayer:exoplayer:r2.4.0'
|
||||
compile('com.google.android.exoplayer:extension-okhttp:r2.4.0') {
|
||||
compile 'com.google.android.exoplayer:exoplayer:2.7.3'
|
||||
compile('com.google.android.exoplayer:extension-okhttp:2.7.3') {
|
||||
exclude group: 'com.squareup.okhttp3', module: 'okhttp'
|
||||
}
|
||||
compile 'com.squareup.okhttp3:okhttp:3.4.2'
|
||||
compile 'com.squareup.okhttp3:okhttp:3.9.1'
|
||||
}
|
||||
|
@ -18,8 +18,6 @@ import com.google.android.exoplayer2.ExoPlayer;
|
||||
import com.google.android.exoplayer2.PlaybackParameters;
|
||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.metadata.Metadata;
|
||||
import com.google.android.exoplayer2.metadata.MetadataRenderer;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.text.Cue;
|
||||
import com.google.android.exoplayer2.text.TextRenderer;
|
||||
@ -27,6 +25,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||
import com.google.android.exoplayer2.ui.SubtitleView;
|
||||
|
||||
import java.util.List;
|
||||
import java.lang.Object;
|
||||
|
||||
@TargetApi(16)
|
||||
public final class ExoPlayerView extends FrameLayout {
|
||||
@ -100,7 +99,6 @@ public final class ExoPlayerView extends FrameLayout {
|
||||
this.player.setVideoListener(null);
|
||||
this.player.removeListener(componentListener);
|
||||
this.player.setVideoSurface(null);
|
||||
this.player.setMetadataOutput(componentListener);
|
||||
}
|
||||
this.player = player;
|
||||
shutterView.setVisibility(VISIBLE);
|
||||
@ -113,7 +111,6 @@ public final class ExoPlayerView extends FrameLayout {
|
||||
player.setVideoListener(componentListener);
|
||||
player.addListener(componentListener);
|
||||
player.setTextOutput(componentListener);
|
||||
player.setMetadataOutput(componentListener);
|
||||
}
|
||||
}
|
||||
|
||||
@ -167,7 +164,7 @@ public final class ExoPlayerView extends FrameLayout {
|
||||
}
|
||||
|
||||
private final class ComponentListener implements SimpleExoPlayer.VideoListener,
|
||||
TextRenderer.Output, ExoPlayer.EventListener, MetadataRenderer.Output {
|
||||
TextRenderer.Output, ExoPlayer.EventListener {
|
||||
|
||||
// TextRenderer.Output implementation
|
||||
|
||||
@ -212,12 +209,12 @@ public final class ExoPlayerView extends FrameLayout {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPositionDiscontinuity() {
|
||||
public void onPositionDiscontinuity(int reason) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTimelineChanged(Timeline timeline, Object manifest) {
|
||||
public void onTimelineChanged(Timeline timeline, Object manifest, int reason) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@ -232,8 +229,18 @@ public final class ExoPlayerView extends FrameLayout {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMetadata(Metadata metadata) {
|
||||
Log.d("onMetadata", "onMetadata");
|
||||
public void onSeekProcessed() {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRepeatModeChanged(int repeatMode) {
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.brentvatne.exoplayer;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.media.AudioManager;
|
||||
import android.net.Uri;
|
||||
@ -8,6 +9,8 @@ import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import com.brentvatne.react.R;
|
||||
@ -22,6 +25,7 @@ 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;
|
||||
@ -52,6 +56,7 @@ import java.net.CookieHandler;
|
||||
import java.net.CookieManager;
|
||||
import java.net.CookiePolicy;
|
||||
import java.lang.Math;
|
||||
import java.lang.Object;
|
||||
|
||||
@SuppressLint("ViewConstructor")
|
||||
class ReactExoplayerView extends FrameLayout implements
|
||||
@ -85,6 +90,7 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
private int resumeWindow;
|
||||
private long resumePosition;
|
||||
private boolean loadVideoStarted;
|
||||
private boolean isFullscreen;
|
||||
private boolean isPaused = true;
|
||||
private boolean isBuffering;
|
||||
private float rate = 1f;
|
||||
@ -113,7 +119,8 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
&& player.getPlayWhenReady()
|
||||
) {
|
||||
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);
|
||||
sendMessageDelayed(msg, Math.round(mProgressUpdateInterval));
|
||||
}
|
||||
@ -330,6 +337,9 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
}
|
||||
|
||||
private void onStopPlayback() {
|
||||
if (isFullscreen) {
|
||||
setFullscreen(false);
|
||||
}
|
||||
setKeepScreenOn(false);
|
||||
audioManager.abandonAudioFocus(this);
|
||||
}
|
||||
@ -455,17 +465,38 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPositionDiscontinuity() {
|
||||
public void onPositionDiscontinuity(int reason) {
|
||||
if (playerNeedsSource) {
|
||||
// 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
|
||||
// which they seeked.
|
||||
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
|
||||
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.
|
||||
}
|
||||
|
||||
@ -482,6 +513,7 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
@Override
|
||||
public void onPlayerError(ExoPlaybackException e) {
|
||||
String errorString = null;
|
||||
Exception ex = e;
|
||||
if (e.type == ExoPlaybackException.TYPE_RENDERER) {
|
||||
Exception cause = e.getRendererException();
|
||||
if (cause instanceof MediaCodecRenderer.DecoderInitializationException) {
|
||||
@ -504,8 +536,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) {
|
||||
eventEmitter.error(errorString, e);
|
||||
eventEmitter.error(errorString, ex);
|
||||
}
|
||||
playerNeedsSource = true;
|
||||
if (isBehindLiveWindow(e)) {
|
||||
@ -581,6 +617,13 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@ -632,4 +675,37 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
public void setDisableFocus(boolean 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
|
||||
private static final String PROP_RATE = "rate";
|
||||
private static final String PROP_PLAY_IN_BACKGROUND = "playInBackground";
|
||||
private static final String PROP_DISABLE_FOCUS = "disableFocus";
|
||||
private static final String PROP_FULLSCREEN = "fullscreen";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
@ -155,6 +156,11 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
|
||||
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) {
|
||||
return uriString.startsWith("http://")
|
||||
|| uriString.startsWith("https://")
|
||||
|
@ -31,6 +31,11 @@ class VideoEventEmitter {
|
||||
private static final String EVENT_PROGRESS = "onVideoProgress";
|
||||
private static final String EVENT_SEEK = "onVideoSeek";
|
||||
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_RESUME = "onPlaybackResume";
|
||||
private static final String EVENT_READY = "onReadyForDisplay";
|
||||
@ -48,6 +53,10 @@ class VideoEventEmitter {
|
||||
EVENT_PROGRESS,
|
||||
EVENT_SEEK,
|
||||
EVENT_END,
|
||||
EVENT_FULLSCREEN_WILL_PRESENT,
|
||||
EVENT_FULLSCREEN_DID_PRESENT,
|
||||
EVENT_FULLSCREEN_WILL_DISMISS,
|
||||
EVENT_FULLSCREEN_DID_DISMISS,
|
||||
EVENT_STALLED,
|
||||
EVENT_RESUME,
|
||||
EVENT_READY,
|
||||
@ -67,6 +76,10 @@ class VideoEventEmitter {
|
||||
EVENT_PROGRESS,
|
||||
EVENT_SEEK,
|
||||
EVENT_END,
|
||||
EVENT_FULLSCREEN_WILL_PRESENT,
|
||||
EVENT_FULLSCREEN_DID_PRESENT,
|
||||
EVENT_FULLSCREEN_WILL_DISMISS,
|
||||
EVENT_FULLSCREEN_DID_DISMISS,
|
||||
EVENT_STALLED,
|
||||
EVENT_RESUME,
|
||||
EVENT_READY,
|
||||
@ -89,6 +102,7 @@ class VideoEventEmitter {
|
||||
|
||||
private static final String EVENT_PROP_DURATION = "duration";
|
||||
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_SEEK_TIME = "seekTime";
|
||||
private static final String EVENT_PROP_NATURAL_SIZE = "naturalSize";
|
||||
@ -141,10 +155,11 @@ class VideoEventEmitter {
|
||||
receiveEvent(EVENT_LOAD, event);
|
||||
}
|
||||
|
||||
void progressChanged(double currentPosition, double bufferedDuration) {
|
||||
void progressChanged(double currentPosition, double bufferedDuration, double seekableDuration) {
|
||||
WritableMap event = Arguments.createMap();
|
||||
event.putDouble(EVENT_PROP_CURRENT_TIME, currentPosition / 1000D);
|
||||
event.putDouble(EVENT_PROP_PLAYABLE_DURATION, bufferedDuration / 1000D);
|
||||
event.putDouble(EVENT_PROP_SEEKABLE_DURATION, seekableDuration / 1000D);
|
||||
receiveEvent(EVENT_PROGRESS, event);
|
||||
}
|
||||
|
||||
@ -173,6 +188,22 @@ class VideoEventEmitter {
|
||||
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) {
|
||||
WritableMap error = Arguments.createMap();
|
||||
error.putString(EVENT_PROP_ERROR_STRING, errorString);
|
||||
|
@ -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="unrecognized_media_format">Unrecognized media format</string>
|
||||
|
||||
</resources>
|
||||
|
@ -5,6 +5,7 @@ import android.content.res.AssetFileDescriptor;
|
||||
import android.graphics.Matrix;
|
||||
import android.media.MediaPlayer;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import android.view.MotionEvent;
|
||||
@ -64,6 +65,7 @@ public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnP
|
||||
|
||||
public static final String EVENT_PROP_DURATION = "duration";
|
||||
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_SEEK_TIME = "seekTime";
|
||||
public static final String EVENT_PROP_NATURALSIZE = "naturalSize";
|
||||
@ -95,6 +97,7 @@ public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnP
|
||||
private float mVolume = 1.0f;
|
||||
private float mProgressUpdateInterval = 250.0f;
|
||||
private float mRate = 1.0f;
|
||||
private float mActiveRate = 1.0f;
|
||||
private boolean mPlayInBackground = false;
|
||||
private boolean mActiveStatePauseStatus = false;
|
||||
private boolean mActiveStatePauseStatusInitialized = false;
|
||||
@ -127,6 +130,7 @@ public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnP
|
||||
WritableMap event = Arguments.createMap();
|
||||
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_SEEKABLE_DURATION, mVideoDuration / 1000.0);
|
||||
mEventEmitter.receiveEvent(getId(), Events.EVENT_PROGRESS.toString(), event);
|
||||
|
||||
// Check for update after an interval
|
||||
@ -297,6 +301,7 @@ public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnP
|
||||
WritableMap event = Arguments.createMap();
|
||||
event.putMap(ReactVideoViewManager.PROP_SRC, src);
|
||||
mEventEmitter.receiveEvent(getId(), Events.EVENT_LOAD_START.toString(), event);
|
||||
isCompleted = false;
|
||||
|
||||
try {
|
||||
prepareAsync(this);
|
||||
@ -343,6 +348,10 @@ public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnP
|
||||
} else {
|
||||
if (!mMediaPlayer.isPlaying()) {
|
||||
start();
|
||||
// Setting the rate unpauses, so we have to wait for an unpause
|
||||
if (mRate != mActiveRate) {
|
||||
setRateModifier(mRate);
|
||||
}
|
||||
|
||||
// Also Start the Progress Update Handler
|
||||
mProgressUpdateHandler.post(mProgressUpdateRunnable);
|
||||
@ -377,8 +386,14 @@ public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnP
|
||||
mRate = rate;
|
||||
|
||||
if (mMediaPlayerValid) {
|
||||
// TODO: Implement this.
|
||||
Log.e(ReactVideoViewManager.REACT_CLASS, "Setting playback rate is not yet supported on Android");
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -388,7 +403,7 @@ public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnP
|
||||
setPausedModifier(mPaused);
|
||||
setMutedModifier(mMuted);
|
||||
setProgressUpdateInterval(mProgressUpdateInterval);
|
||||
// setRateModifier(mRate);
|
||||
setRateModifier(mRate);
|
||||
}
|
||||
|
||||
public void setPlayInBackground(final boolean playInBackground) {
|
||||
|
BIN
docs/AppTransportSecuritySetting.png
Normal file
BIN
docs/AppTransportSecuritySetting.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
@ -93,7 +93,7 @@ static NSString *const timedMetadata = @"timedMetadata";
|
||||
playerLayer.showsPlaybackControls = NO;
|
||||
playerLayer.rctDelegate = self;
|
||||
playerLayer.view.frame = self.bounds;
|
||||
playerLayer.player = _player;
|
||||
playerLayer.player = player;
|
||||
playerLayer.view.frame = self.bounds;
|
||||
return playerLayer;
|
||||
}
|
||||
@ -127,7 +127,8 @@ static NSString *const timedMetadata = @"timedMetadata";
|
||||
-(void)addPlayerTimeObserver
|
||||
{
|
||||
const Float64 progressUpdateIntervalMS = _progressUpdateInterval / 1000;
|
||||
// @see endScrubbing in AVPlayerDemoPlaybackViewController.m of https://developer.apple.com/library/ios/samplecode/AVPlayerDemo/Introduction/Intro.html
|
||||
// @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
|
||||
@ -198,6 +199,9 @@ static NSString *const timedMetadata = @"timedMetadata";
|
||||
CMTime currentTime = _player.currentTime;
|
||||
const Float64 duration = CMTimeGetSeconds(playerDuration);
|
||||
const Float64 currentTimeSecs = CMTimeGetSeconds(currentTime);
|
||||
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"RCTVideo_progress" object:nil userInfo:@{@"progress": [NSNumber numberWithDouble: currentTimeSecs / duration]}];
|
||||
|
||||
if( currentTimeSecs >= 0 && self.onVideoProgress) {
|
||||
self.onVideoProgress(@{
|
||||
@"currentTime": [NSNumber numberWithFloat:CMTimeGetSeconds(currentTime)],
|
||||
@ -259,6 +263,9 @@ static NSString *const timedMetadata = @"timedMetadata";
|
||||
* observer set */
|
||||
- (void)removePlayerItemObservers
|
||||
{
|
||||
if (_playerLayer) {
|
||||
[_playerLayer removeObserver:self forKeyPath:readyForDisplayKeyPath];
|
||||
}
|
||||
if (_playerItemObserversSet) {
|
||||
[_playerItem removeObserver:self forKeyPath:statusKeyPath];
|
||||
[_playerItem removeObserver:self forKeyPath:playbackBufferEmptyKeyPath];
|
||||
@ -486,6 +493,8 @@ static NSString *const timedMetadata = @"timedMetadata";
|
||||
AVPlayerItem *item = [notification object];
|
||||
[item seekToTime:kCMTimeZero];
|
||||
[self applyModifiers];
|
||||
} else {
|
||||
[self removePlayerTimeObserver];
|
||||
}
|
||||
}
|
||||
|
||||
@ -565,7 +574,12 @@ static NSString *const timedMetadata = @"timedMetadata";
|
||||
if (CMTimeCompare(current, cmSeekTime) != 0) {
|
||||
if (!wasPaused) [_player pause];
|
||||
[_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) {
|
||||
self.onVideoSeek(@{@"currentTime": [NSNumber numberWithFloat:CMTimeGetSeconds(item.currentTime)],
|
||||
@"seekTime": [NSNumber numberWithFloat:seekTime],
|
||||
|
@ -62,4 +62,9 @@ RCT_EXPORT_VIEW_PROPERTY(onPlaybackRateChange, RCTBubblingEventBlock);
|
||||
};
|
||||
}
|
||||
|
||||
+ (BOOL)requiresMainQueueSetup
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
|
10
package.json
10
package.json
@ -1,11 +1,15 @@
|
||||
{
|
||||
"name": "react-native-video",
|
||||
"version": "2.0.0",
|
||||
"version": "2.1.1",
|
||||
"description": "A <Video /> element for react-native",
|
||||
"main": "Video.js",
|
||||
"license": "MIT",
|
||||
"author": "Brent Vatne <brentvatne@gmail.com> (https://github.com/brentvatne)",
|
||||
"contributors": [
|
||||
{
|
||||
"name": "Isaiah Grey",
|
||||
"email": "isaiahgrey@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Johannes Lumpe",
|
||||
"email": "johannes@lum.pe"
|
||||
@ -13,6 +17,10 @@
|
||||
{
|
||||
"name": "Baris Sencan",
|
||||
"email": "baris.sncn@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Hampton Maxwell",
|
||||
"email": "me@hamptonmaxwell.com"
|
||||
}
|
||||
],
|
||||
"repository": {
|
||||
|
@ -328,7 +328,7 @@ namespace ReactNativeVideo
|
||||
private readonly JObject _eventData;
|
||||
|
||||
public ReactVideoEvent(string eventName, int viewTag, JObject eventData)
|
||||
: base(viewTag, TimeSpan.FromTicks(Environment.TickCount))
|
||||
: base(viewTag)
|
||||
{
|
||||
_eventName = eventName;
|
||||
_eventData = eventData;
|
||||
|
@ -334,7 +334,7 @@ namespace ReactNativeVideo
|
||||
private readonly JObject _eventData;
|
||||
|
||||
public ReactVideoEvent(string eventName, int viewTag, JObject eventData)
|
||||
: base(viewTag, TimeSpan.FromTicks(Environment.TickCount))
|
||||
: base(viewTag)
|
||||
{
|
||||
_eventName = eventName;
|
||||
_eventData = eventData;
|
||||
|
Loading…
Reference in New Issue
Block a user