Merge branch 'master' into maximumBitRate-adaptive-streaming
This commit is contained in:
commit
a43f9c7ce1
@ -1,5 +1,14 @@
|
||||
## Changelog
|
||||
|
||||
### Version 4.1.1
|
||||
* Don't initialize filters on iOS unless a filter is set. This was causing a startup performance regression [#1360](https://github.com/react-native-community/react-native-video/pull/1360)
|
||||
|
||||
### Version 4.1.0
|
||||
* Generate onSeek on Android ExoPlayer & MediaPlayer after seek completes [#1351](https://github.com/react-native-community/react-native-video/pull/1351)
|
||||
* Remove unneeded onVideoSaved event [#1350](https://github.com/react-native-community/react-native-video/pull/1350)
|
||||
* Disable AirPlay if sidecar text tracks are enabled [#1304](https://github.com/react-native-community/react-native-video/pull/1304)
|
||||
* Add possibility to remove black screen while video is loading in Exoplayer [#1355](https://github.com/react-native-community/react-native-video/pull/1355)
|
||||
|
||||
### Version 4.0.1
|
||||
* Add missing files to package.json [#1342](https://github.com/react-native-community/react-native-video/pull/1342)
|
||||
|
||||
|
37
README.md
37
README.md
@ -264,6 +264,7 @@ var styles = StyleSheet.create({
|
||||
* [fullscreenAutorotate](#fullscreenautorotate)
|
||||
* [fullscreenOrientation](#fullscreenorientation)
|
||||
* [headers](#headers)
|
||||
* [hideShutterView](#hideshutterview)
|
||||
* [id](#id)
|
||||
* [ignoreSilentSwitch](#ignoresilentswitch)
|
||||
* [maxBitRate](#maxbitrate)
|
||||
@ -296,6 +297,7 @@ var styles = StyleSheet.create({
|
||||
* [onLoad](#onload)
|
||||
* [onLoadStart](#onloadstart)
|
||||
* [onProgress](#onprogress)
|
||||
* [onSeek](#onseek)
|
||||
* [onTimedMetadata](#ontimedmetadata)
|
||||
|
||||
### Methods
|
||||
@ -417,6 +419,14 @@ headers={{
|
||||
|
||||
Platforms: Android ExoPlayer
|
||||
|
||||
#### hideShutterView
|
||||
Controls whether the ExoPlayer shutter view (black screen while loading) is enabled.
|
||||
|
||||
* **false (default)** - Show shutter view
|
||||
* **true** - Hide shutter view
|
||||
|
||||
Platforms: Android ExoPlayer
|
||||
|
||||
#### id
|
||||
Set the DOM id element so you can use document.getElementById on web platforms. Accepts string values.
|
||||
|
||||
@ -667,6 +677,8 @@ uri | URL for the text track. Currently, only tracks hosted on a webserver are s
|
||||
|
||||
On iOS, sidecar text tracks are only supported for individual files, not HLS playlists. For HLS, you should include the text tracks as part of the playlist.
|
||||
|
||||
Note: Due to iOS limitations, sidecar text tracks are not compatible with Airplay. If textTracks are specified, AirPlay support will be automatically disabled.
|
||||
|
||||
Example:
|
||||
```
|
||||
import { TextTrackType }, Video from 'react-native-video';
|
||||
@ -860,6 +872,29 @@ Example:
|
||||
|
||||
Platforms: all
|
||||
|
||||
#### onSeek
|
||||
Callback function that is called when a seek completes.
|
||||
|
||||
Payload:
|
||||
|
||||
Property | Type | Description
|
||||
--- | --- | ---
|
||||
currentTime | number | The current time after the seek
|
||||
seekTime | number | The requested time
|
||||
|
||||
Example:
|
||||
```
|
||||
{
|
||||
currentTime: 100.5
|
||||
seekTime: 100
|
||||
}
|
||||
```
|
||||
|
||||
Both the currentTime & seekTime are reported because the video player may not seek to the exact requested position in order to improve seek performance.
|
||||
|
||||
|
||||
Platforms: Android ExoPlayer, Android MediaPlayer, iOS, Windows UWP
|
||||
|
||||
#### onTimedMetadata
|
||||
Callback function that is called when timed metadata becomes available
|
||||
|
||||
@ -953,7 +988,7 @@ Platforms: iOS
|
||||
|
||||
Seek to the specified position represented by seconds. seconds is a float value.
|
||||
|
||||
`seek()` can only be called after the `onLoad` event has fired.
|
||||
`seek()` can only be called after the `onLoad` event has fired. Once completed, the [onSeek](#onseek) event will be called.
|
||||
|
||||
Example:
|
||||
```
|
||||
|
1
Video.js
1
Video.js
@ -384,6 +384,7 @@ Video.propTypes = {
|
||||
fullscreenOrientation: PropTypes.oneOf(['all','landscape','portrait']),
|
||||
progressUpdateInterval: PropTypes.number,
|
||||
useTextureView: PropTypes.bool,
|
||||
hideShutterView: PropTypes.bool,
|
||||
onLoadStart: PropTypes.func,
|
||||
onLoad: PropTypes.func,
|
||||
onBuffer: PropTypes.func,
|
||||
|
@ -39,6 +39,7 @@ public final class ExoPlayerView extends FrameLayout {
|
||||
private ViewGroup.LayoutParams layoutParams;
|
||||
|
||||
private boolean useTextureView = false;
|
||||
private boolean hideShutterView = false;
|
||||
|
||||
public ExoPlayerView(Context context) {
|
||||
this(context, null);
|
||||
@ -106,6 +107,10 @@ public final class ExoPlayerView extends FrameLayout {
|
||||
}
|
||||
}
|
||||
|
||||
private void updateShutterViewVisibility() {
|
||||
shutterView.setVisibility(this.hideShutterView ? View.INVISIBLE : View.VISIBLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link SimpleExoPlayer} to use. The {@link SimpleExoPlayer#setTextOutput} and
|
||||
* {@link SimpleExoPlayer#setVideoListener} method of the player will be called and previous
|
||||
@ -161,6 +166,11 @@ public final class ExoPlayerView extends FrameLayout {
|
||||
updateSurfaceView();
|
||||
}
|
||||
|
||||
public void setHideShutterView(boolean hideShutterView) {
|
||||
this.hideShutterView = hideShutterView;
|
||||
updateShutterViewVisibility();
|
||||
}
|
||||
|
||||
private final Runnable measureAndLayout = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
@ -111,6 +111,7 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
private float rate = 1f;
|
||||
private float audioVolume = 1f;
|
||||
private int maxBitRate = 0;
|
||||
private long seekTime = C.TIME_UNSET;
|
||||
|
||||
private int minBufferMs = DefaultLoadControl.DEFAULT_MIN_BUFFER_MS;
|
||||
private int maxBufferMs = DefaultLoadControl.DEFAULT_MAX_BUFFER_MS;
|
||||
@ -131,6 +132,7 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
private float mProgressUpdateInterval = 250.0f;
|
||||
private boolean playInBackground = false;
|
||||
private boolean useTextureView = false;
|
||||
private boolean hideShutterView = false;
|
||||
private Map<String, String> requestHeaders;
|
||||
// \ End props
|
||||
|
||||
@ -609,7 +611,8 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
|
||||
@Override
|
||||
public void onSeekProcessed() {
|
||||
// Do nothing.
|
||||
eventEmitter.seek(player.getCurrentPosition(), seekTime);
|
||||
seekTime = C.TIME_UNSET;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -896,7 +899,7 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
|
||||
public void seekTo(long positionMs) {
|
||||
if (player != null) {
|
||||
eventEmitter.seek(player.getCurrentPosition(), positionMs);
|
||||
seekTime = positionMs;
|
||||
player.seekTo(positionMs);
|
||||
}
|
||||
}
|
||||
@ -966,6 +969,10 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
exoPlayerView.setUseTextureView(useTextureView);
|
||||
}
|
||||
|
||||
public void setHideShutterView(boolean hideShutterView) {
|
||||
exoPlayerView.setHideShutterView(hideShutterView);
|
||||
}
|
||||
|
||||
public void setBufferConfig(int newMinBufferMs, int newMaxBufferMs, int newBufferForPlaybackMs, int newBufferForPlaybackAfterRebufferMs) {
|
||||
minBufferMs = newMinBufferMs;
|
||||
maxBufferMs = newMaxBufferMs;
|
||||
|
@ -52,6 +52,7 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
|
||||
private static final String PROP_DISABLE_FOCUS = "disableFocus";
|
||||
private static final String PROP_FULLSCREEN = "fullscreen";
|
||||
private static final String PROP_USE_TEXTURE_VIEW = "useTextureView";
|
||||
private static final String PROP_HIDE_SHUTTER_VIEW = "hideShutterView";
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
@ -226,6 +227,11 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
|
||||
videoView.setUseTextureView(useTextureView);
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_HIDE_SHUTTER_VIEW, defaultBoolean = false)
|
||||
public void setHideShutterView(final ReactExoplayerView videoView, final boolean hideShutterView) {
|
||||
videoView.setHideShutterView(hideShutterView);
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_BUFFER_CONFIG)
|
||||
public void setBufferConfig(final ReactExoplayerView videoView, @Nullable ReadableMap bufferConfig) {
|
||||
int minBufferMs = DefaultLoadControl.DEFAULT_MIN_BUFFER_MS;
|
||||
|
@ -47,6 +47,7 @@ public class ReactVideoView extends ScalableVideoView implements
|
||||
MediaPlayer.OnPreparedListener,
|
||||
MediaPlayer.OnErrorListener,
|
||||
MediaPlayer.OnBufferingUpdateListener,
|
||||
MediaPlayer.OnSeekCompleteListener,
|
||||
MediaPlayer.OnCompletionListener,
|
||||
MediaPlayer.OnInfoListener,
|
||||
LifecycleEventListener,
|
||||
@ -127,6 +128,7 @@ public class ReactVideoView extends ScalableVideoView implements
|
||||
private float mProgressUpdateInterval = 250.0f;
|
||||
private float mRate = 1.0f;
|
||||
private float mActiveRate = 1.0f;
|
||||
private long mSeekTime = 0;
|
||||
private boolean mPlayInBackground = false;
|
||||
private boolean mBackgroundPaused = false;
|
||||
private boolean mIsFullscreen = false;
|
||||
@ -213,6 +215,7 @@ public class ReactVideoView extends ScalableVideoView implements
|
||||
mMediaPlayer.setOnErrorListener(this);
|
||||
mMediaPlayer.setOnPreparedListener(this);
|
||||
mMediaPlayer.setOnBufferingUpdateListener(this);
|
||||
mMediaPlayer.setOnSeekCompleteListener(this);
|
||||
mMediaPlayer.setOnCompletionListener(this);
|
||||
mMediaPlayer.setOnInfoListener(this);
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
@ -606,15 +609,18 @@ public class ReactVideoView extends ScalableVideoView implements
|
||||
mVideoBufferedDuration = (int) Math.round((double) (mVideoDuration * percent) / 100.0);
|
||||
}
|
||||
|
||||
public void onSeekComplete(MediaPlayer mp) {
|
||||
WritableMap event = Arguments.createMap();
|
||||
event.putDouble(EVENT_PROP_CURRENT_TIME, getCurrentPosition() / 1000.0);
|
||||
event.putDouble(EVENT_PROP_SEEK_TIME, mSeekTime / 1000.0);
|
||||
mEventEmitter.receiveEvent(getId(), Events.EVENT_SEEK.toString(), event);
|
||||
mSeekTime = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seekTo(int msec) {
|
||||
|
||||
if (mMediaPlayerValid) {
|
||||
WritableMap event = Arguments.createMap();
|
||||
event.putDouble(EVENT_PROP_CURRENT_TIME, getCurrentPosition() / 1000.0);
|
||||
event.putDouble(EVENT_PROP_SEEK_TIME, msec / 1000.0);
|
||||
mEventEmitter.receiveEvent(getId(), Events.EVENT_SEEK.toString(), event);
|
||||
|
||||
mSeekTime = msec;
|
||||
super.seekTo(msec);
|
||||
if (isCompleted && mVideoDuration != 0 && msec < mVideoDuration) {
|
||||
isCompleted = false;
|
||||
|
@ -404,11 +404,14 @@ static int const RCTVideoUnset = -1;
|
||||
|
||||
- (void)playerItemPrepareText:(AVAsset *)asset assetOptions:(NSDictionary * __nullable)assetOptions withCallback:(void(^)(AVPlayerItem *))handler
|
||||
{
|
||||
if (!_textTracks) {
|
||||
if (!_textTracks || _textTracks.count==0) {
|
||||
handler([AVPlayerItem playerItemWithAsset:asset]);
|
||||
return;
|
||||
}
|
||||
|
||||
// AVPlayer can't airplay AVMutableCompositions
|
||||
_allowsExternalPlayback = NO;
|
||||
|
||||
// sideload text tracks
|
||||
AVMutableComposition *mixComposition = [[AVMutableComposition alloc] init];
|
||||
|
||||
@ -1276,39 +1279,29 @@ static int const RCTVideoUnset = -1;
|
||||
}
|
||||
|
||||
- (void)setFilter:(NSString *)filterName {
|
||||
|
||||
_filterName = filterName;
|
||||
|
||||
AVAsset *asset = _playerItem.asset;
|
||||
|
||||
if (asset != nil) {
|
||||
|
||||
CIFilter *filter = [CIFilter filterWithName:filterName];
|
||||
|
||||
_playerItem.videoComposition = [AVVideoComposition
|
||||
videoCompositionWithAsset:asset
|
||||
applyingCIFiltersWithHandler:^(AVAsynchronousCIImageFilteringRequest *_Nonnull request) {
|
||||
|
||||
if (filter == nil) {
|
||||
|
||||
[request finishWithImage:request.sourceImage context:nil];
|
||||
|
||||
} else {
|
||||
|
||||
CIImage *image = request.sourceImage.imageByClampingToExtent;
|
||||
|
||||
[filter setValue:image forKey:kCIInputImageKey];
|
||||
|
||||
CIImage *output = [filter.outputImage imageByCroppingToRect:request.sourceImage.extent];
|
||||
|
||||
[request finishWithImage:output context:nil];
|
||||
|
||||
}
|
||||
|
||||
}];
|
||||
|
||||
if (!asset) {
|
||||
return;
|
||||
} else if (!_playerItem.videoComposition && (filterName == nil || [filterName isEqualToString:@""])) {
|
||||
return; // Setting up an empty filter has a cost so avoid whenever possible
|
||||
}
|
||||
// TODO: filters don't work for HLS, check & return
|
||||
|
||||
CIFilter *filter = [CIFilter filterWithName:filterName];
|
||||
_playerItem.videoComposition = [AVVideoComposition
|
||||
videoCompositionWithAsset:asset
|
||||
applyingCIFiltersWithHandler:^(AVAsynchronousCIImageFilteringRequest *_Nonnull request) {
|
||||
if (filter == nil) {
|
||||
[request finishWithImage:request.sourceImage context:nil];
|
||||
} else {
|
||||
CIImage *image = request.sourceImage.imageByClampingToExtent;
|
||||
[filter setValue:image forKey:kCIInputImageKey];
|
||||
CIImage *output = [filter.outputImage imageByCroppingToRect:request.sourceImage.extent];
|
||||
[request finishWithImage:output context:nil];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - React View Management
|
||||
|
@ -60,7 +60,6 @@ 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(onVideoSaved, RCTBubblingEventBlock);
|
||||
RCT_REMAP_METHOD(save,
|
||||
options:(NSDictionary *)options
|
||||
reactTag:(nonnull NSNumber *)reactTag
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-native-video",
|
||||
"version": "4.0.1",
|
||||
"version": "4.1.0",
|
||||
"description": "A <Video /> element for react-native",
|
||||
"main": "Video.js",
|
||||
"license": "MIT",
|
||||
|
Loading…
Reference in New Issue
Block a user