Merge branch 'master' into bugfix/android-disablefocus-audio

This commit is contained in:
Kurt Johnson 2019-03-15 15:57:00 -04:00 committed by GitHub
commit 3d80bfd236
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 83658 additions and 14 deletions

View File

@ -2,7 +2,18 @@
### next ### next
* Fix Android videos being able to play with background music/audio from other apps. * Fix Android videos being able to play with background music/audio from other apps.
### Version 4.4.0
* Fix runtime warning by replacing `UIManager.RCTVideo` with `UIManager.getViewManagerConfig('RCTVideo')` (and ensuring backwards compat) [#1487](https://github.com/react-native-community/react-native-video/pull/1487)
* Fix loading package resolved videos when using video-caching [#1438](https://github.com/react-native-community/react-native-video/pull/1438) * Fix loading package resolved videos when using video-caching [#1438](https://github.com/react-native-community/react-native-video/pull/1438)
* Fix "message sent to deallocated instance" crash on ios [#1482](https://github.com/react-native-community/react-native-video/pull/1482)
* Display a warning when source is empty [#1478](https://github.com/react-native-community/react-native-video/pull/1478)
* Don't crash on iOS for an empty source [#1246](https://github.com/react-native-community/react-native-video/pull/1246)
* Recover from from transient internet failures when loading on ExoPlayer [#1448](https://github.com/react-native-community/react-native-video/pull/1448)
* Add controls support for ExoPlayer [#1414](https://github.com/react-native-community/react-native-video/pull/1414)
* Fix check for text tracks when iOS caching enabled [#1387](https://github.com/react-native-community/react-native-video/pull/1387)
* Add support for Picture in Picture on iOS [#1325](https://github.com/react-native-community/react-native-video/pull/1325)
* Fix UIManager undefined variable [#1488](https://github.com/react-native-community/react-native-video/pull/1488)
### Version 4.3.0 ### Version 4.3.0
* Fix iOS video not displaying after switching source [#1395](https://github.com/react-native-community/react-native-video/pull/1395) * Fix iOS video not displaying after switching source [#1395](https://github.com/react-native-community/react-native-video/pull/1395)
@ -10,6 +21,7 @@
* Fix text not appearing in release builds of Android apps [#1373](https://github.com/react-native-community/react-native-video/pull/1373) * Fix text not appearing in release builds of Android apps [#1373](https://github.com/react-native-community/react-native-video/pull/1373)
* Update to ExoPlayer 2.9.3 [#1406](https://github.com/react-native-community/react-native-video/pull/1406) * Update to ExoPlayer 2.9.3 [#1406](https://github.com/react-native-community/react-native-video/pull/1406)
* Add video track selection & onBandwidthUpdate [#1199](https://github.com/react-native-community/react-native-video/pull/1199) * Add video track selection & onBandwidthUpdate [#1199](https://github.com/react-native-community/react-native-video/pull/1199)
* Recovery from transient internet failures and props to configure the custom retry count [#1448](https://github.com/react-native-community/react-native-video/pull/1448)
### Version 4.2.0 ### Version 4.2.0
* 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) * 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)

View File

@ -269,8 +269,10 @@ var styles = StyleSheet.create({
* [id](#id) * [id](#id)
* [ignoreSilentSwitch](#ignoresilentswitch) * [ignoreSilentSwitch](#ignoresilentswitch)
* [maxBitRate](#maxbitrate) * [maxBitRate](#maxbitrate)
* [minLoadRetryCount](#minLoadRetryCount)
* [muted](#muted) * [muted](#muted)
* [paused](#paused) * [paused](#paused)
* [pictureInPicture](#pictureinpicture)
* [playInBackground](#playinbackground) * [playInBackground](#playinbackground)
* [playWhenInactive](#playwheninactive) * [playWhenInactive](#playwheninactive)
* [poster](#poster) * [poster](#poster)
@ -300,14 +302,17 @@ var styles = StyleSheet.create({
* [onFullscreenPlayerDidDismiss](#onfullscreenplayerdiddismiss) * [onFullscreenPlayerDidDismiss](#onfullscreenplayerdiddismiss)
* [onLoad](#onload) * [onLoad](#onload)
* [onLoadStart](#onloadstart) * [onLoadStart](#onloadstart)
* [onPictureInPictureStatusChanged](#onpictureinpicturestatuschanged)
* [onProgress](#onprogress) * [onProgress](#onprogress)
* [onSeek](#onseek) * [onSeek](#onseek)
* [onRestoreUserInterfaceForPictureInPictureStop](#onrestoreuserinterfaceforpictureinpicturestop)
* [onTimedMetadata](#ontimedmetadata) * [onTimedMetadata](#ontimedmetadata)
### Methods ### Methods
* [dismissFullscreenPlayer](#dismissfullscreenplayer) * [dismissFullscreenPlayer](#dismissfullscreenplayer)
* [presentFullscreenPlayer](#presentfullscreenplayer) * [presentFullscreenPlayer](#presentfullscreenplayer)
* [save](#save) * [save](#save)
* [restoreUserInterfaceForPictureInPictureStop](#restoreuserinterfaceforpictureinpicturestop)
* [seek](#seek) * [seek](#seek)
### Configurable props ### Configurable props
@ -359,9 +364,9 @@ Determines whether to show player controls.
Note on iOS, controls are always shown when in fullscreen mode. Note on iOS, controls are always shown when in fullscreen mode.
Controls are not available Android because the system does not provide a stock set of controls. You will need to build your own or use a package like [react-native-video-controls](https://github.com/itsnubix/react-native-video-controls) or [react-native-video-player](https://github.com/cornedor/react-native-video-player). For Android MediaPlayer, you will need to build your own controls or use a package like [react-native-video-controls](https://github.com/itsnubix/react-native-video-controls) or [react-native-video-player](https://github.com/cornedor/react-native-video-player).
Platforms: iOS, react-native-dom Platforms: Android ExoPlayer, iOS, react-native-dom
#### disableFocus #### disableFocus
Determines whether video audio should override background music/audio in Android devices. Determines whether video audio should override background music/audio in Android devices.
@ -482,6 +487,18 @@ maxBitRate={2000000} // 2 megabits
Platforms: Android ExoPlayer, iOS Platforms: Android ExoPlayer, iOS
#### minLoadRetryCount
Sets the minimum number of times to retry loading data before failing and reporting an error to the application. Useful to recover from transient internet failures.
Default: 3. Retry 3 times.
Example:
```
minLoadRetryCount={5} // retry 5 times
```
Platforms: Android ExoPlayer
#### muted #### muted
Controls whether the audio is muted Controls whether the audio is muted
* **false (default)** - Don't mute audio * **false (default)** - Don't mute audio
@ -496,6 +513,13 @@ Controls whether the media is paused
Platforms: all Platforms: all
#### pictureInPicture
Determine whether the media should played as picture in picture.
* **false (default)** - Don't not play as picture in picture
* **true** - Play the media as picture in picture
Platforms: iOS
#### playInBackground #### playInBackground
Determine whether the media should continue playing while the app is in the background. This allows customers to continue listening to the audio. Determine whether the media should continue playing while the app is in the background. This allows customers to continue listening to the audio.
* **false (default)** - Don't continue playing the media * **false (default)** - Don't continue playing the media
@ -936,6 +960,22 @@ Example:
Platforms: all Platforms: all
#### onPictureInPictureStatusChanged
Callback function that is called when picture in picture becomes active or inactive.
Property | Type | Description
--- | --- | ---
isActive | boolean | Boolean indicating whether picture in picture is active
Example:
```
{
isActive: true
}
```
Platforms: iOS
#### onProgress #### onProgress
Callback function that is called every progressUpdateInterval seconds with info about which position the media is currently playing. Callback function that is called every progressUpdateInterval seconds with info about which position the media is currently playing.
@ -979,6 +1019,13 @@ Both the currentTime & seekTime are reported because the video player may not se
Platforms: Android ExoPlayer, Android MediaPlayer, iOS, Windows UWP Platforms: Android ExoPlayer, Android MediaPlayer, iOS, Windows UWP
#### onRestoreUserInterfaceForPictureInPictureStop
Callback function that corresponds to Apple's [`restoreUserInterfaceForPictureInPictureStopWithCompletionHandler`](https://developer.apple.com/documentation/avkit/avpictureinpicturecontrollerdelegate/1614703-pictureinpicturecontroller?language=objc). Call `restoreUserInterfaceForPictureInPictureStopCompleted` inside of this function when done restoring the user interface.
Payload: none
Platforms: iOS
#### onTimedMetadata #### onTimedMetadata
Callback function that is called when timed metadata becomes available Callback function that is called when timed metadata becomes available
@ -1067,6 +1114,18 @@ Future:
Platforms: iOS Platforms: iOS
#### restoreUserInterfaceForPictureInPictureStopCompleted
`restoreUserInterfaceForPictureInPictureStopCompleted(restored)`
This function corresponds to the completion handler in Apple's [restoreUserInterfaceForPictureInPictureStop](https://developer.apple.com/documentation/avkit/avpictureinpicturecontrollerdelegate/1614703-pictureinpicturecontroller?language=objc). IMPORTANT: This function must be called after `onRestoreUserInterfaceForPictureInPictureStop` is called.
Example:
```
this.player.restoreUserInterfaceForPictureInPictureStopCompleted(true);
```
Platforms: iOS
#### seek() #### seek()
`seek(seconds)` `seek(seconds)`

View File

@ -78,6 +78,10 @@ export default class Video extends Component {
return await NativeModules.VideoManager.save(options, findNodeHandle(this._root)); return await NativeModules.VideoManager.save(options, findNodeHandle(this._root));
} }
restoreUserInterfaceForPictureInPictureStopCompleted = (restored) => {
this.setNativeProps({ restoreUserInterfaceForPIPStopCompletionHandler: restored });
};
_assignRoot = (component) => { _assignRoot = (component) => {
this._root = component; this._root = component;
}; };
@ -198,6 +202,18 @@ export default class Video extends Component {
} }
}; };
_onPictureInPictureStatusChanged = (event) => {
if (this.props.onPictureInPictureStatusChanged) {
this.props.onPictureInPictureStatusChanged(event.nativeEvent);
}
};
_onRestoreUserInterfaceForPictureInPictureStop = (event) => {
if (this.props.onRestoreUserInterfaceForPictureInPictureStop) {
this.props.onRestoreUserInterfaceForPictureInPictureStop();
}
};
_onAudioFocusChanged = (event) => { _onAudioFocusChanged = (event) => {
if (this.props.onAudioFocusChanged) { if (this.props.onAudioFocusChanged) {
this.props.onAudioFocusChanged(event.nativeEvent); this.props.onAudioFocusChanged(event.nativeEvent);
@ -210,6 +226,13 @@ export default class Video extends Component {
} }
}; };
getViewManagerConfig = viewManagerName => {
if (!NativeModules.UIManager.getViewManagerConfig) {
return NativeModules.UIManager[viewManagerName];
}
return NativeModules.UIManager.getViewManagerConfig(viewManagerName);
};
render() { render() {
const resizeMode = this.props.resizeMode; const resizeMode = this.props.resizeMode;
const source = resolveAssetSource(this.props.source) || {}; const source = resolveAssetSource(this.props.source) || {};
@ -220,18 +243,24 @@ export default class Video extends Component {
uri = `file://${uri}`; uri = `file://${uri}`;
} }
if (!uri) {
console.warn('Trying to load empty source.');
}
const isNetwork = !!(uri && uri.match(/^https?:/)); const isNetwork = !!(uri && uri.match(/^https?:/));
const isAsset = !!(uri && uri.match(/^(assets-library|ipod-library|file|content|ms-appx|ms-appdata):/)); const isAsset = !!(uri && uri.match(/^(assets-library|ipod-library|file|content|ms-appx|ms-appdata):/));
let nativeResizeMode; let nativeResizeMode;
const RCTVideoInstance = this.getViewManagerConfig('RCTVideo');
if (resizeMode === VideoResizeMode.stretch) { if (resizeMode === VideoResizeMode.stretch) {
nativeResizeMode = NativeModules.UIManager.RCTVideo.Constants.ScaleToFill; nativeResizeMode = RCTVideoInstance.Constants.ScaleToFill;
} else if (resizeMode === VideoResizeMode.contain) { } else if (resizeMode === VideoResizeMode.contain) {
nativeResizeMode = NativeModules.UIManager.RCTVideo.Constants.ScaleAspectFit; nativeResizeMode = RCTVideoInstance.Constants.ScaleAspectFit;
} else if (resizeMode === VideoResizeMode.cover) { } else if (resizeMode === VideoResizeMode.cover) {
nativeResizeMode = NativeModules.UIManager.RCTVideo.Constants.ScaleAspectFill; nativeResizeMode = RCTVideoInstance.Constants.ScaleAspectFill;
} else { } else {
nativeResizeMode = NativeModules.UIManager.RCTVideo.Constants.ScaleNone; nativeResizeMode = RCTVideoInstance.Constants.ScaleNone;
} }
const nativeProps = Object.assign({}, this.props); const nativeProps = Object.assign({}, this.props);
@ -269,6 +298,8 @@ export default class Video extends Component {
onPlaybackRateChange: this._onPlaybackRateChange, onPlaybackRateChange: this._onPlaybackRateChange,
onAudioFocusChanged: this._onAudioFocusChanged, onAudioFocusChanged: this._onAudioFocusChanged,
onAudioBecomingNoisy: this._onAudioBecomingNoisy, onAudioBecomingNoisy: this._onAudioBecomingNoisy,
onPictureInPictureStatusChanged: this._onPictureInPictureStatusChanged,
onRestoreUserInterfaceForPictureInPictureStop: this._onRestoreUserInterfaceForPictureInPictureStop,
}); });
const posterStyle = { const posterStyle = {
@ -341,6 +372,7 @@ Video.propTypes = {
// Opaque type returned by require('./video.mp4') // Opaque type returned by require('./video.mp4')
PropTypes.number PropTypes.number
]), ]),
minLoadRetryCount: PropTypes.number,
maxBitRate: PropTypes.number, maxBitRate: PropTypes.number,
resizeMode: PropTypes.string, resizeMode: PropTypes.string,
poster: PropTypes.string, poster: PropTypes.string,
@ -391,6 +423,7 @@ Video.propTypes = {
}), }),
stereoPan: PropTypes.number, stereoPan: PropTypes.number,
rate: PropTypes.number, rate: PropTypes.number,
pictureInPicture: PropTypes.bool,
playInBackground: PropTypes.bool, playInBackground: PropTypes.bool,
playWhenInactive: PropTypes.bool, playWhenInactive: PropTypes.bool,
ignoreSilentSwitch: PropTypes.oneOf(['ignore', 'obey']), ignoreSilentSwitch: PropTypes.oneOf(['ignore', 'obey']),
@ -422,6 +455,8 @@ Video.propTypes = {
onPlaybackRateChange: PropTypes.func, onPlaybackRateChange: PropTypes.func,
onAudioFocusChanged: PropTypes.func, onAudioFocusChanged: PropTypes.func,
onAudioBecomingNoisy: PropTypes.func, onAudioBecomingNoisy: PropTypes.func,
onPictureInPictureStatusChanged: PropTypes.func,
needsToRestoreUserInterfaceForPictureInPictureStop: PropTypes.func,
onExternalPlaybackChange: PropTypes.func, onExternalPlaybackChange: PropTypes.func,
/* Required by react-native */ /* Required by react-native */

View File

@ -8,6 +8,11 @@ android {
compileSdkVersion safeExtGet('compileSdkVersion', 27) compileSdkVersion safeExtGet('compileSdkVersion', 27)
buildToolsVersion safeExtGet('buildToolsVersion', '27.0.3') buildToolsVersion safeExtGet('buildToolsVersion', '27.0.3')
compileOptions {
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_1_8
}
defaultConfig { defaultConfig {
minSdkVersion safeExtGet('minSdkVersion', 16) minSdkVersion safeExtGet('minSdkVersion', 16)
targetSdkVersion safeExtGet('targetSdkVersion', 27) targetSdkVersion safeExtGet('targetSdkVersion', 27)

View File

@ -64,6 +64,7 @@ import com.google.android.exoplayer2.upstream.BandwidthMeter;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.ui.PlayerControlView;
import java.net.CookieHandler; import java.net.CookieHandler;
import java.net.CookieManager; import java.net.CookieManager;
@ -96,6 +97,9 @@ class ReactExoplayerView extends FrameLayout implements
} }
private final VideoEventEmitter eventEmitter; private final VideoEventEmitter eventEmitter;
private PlayerControlView playerControlView;
private View playPauseControlContainer;
private Player.EventListener eventListener;
private Handler mainHandler; private Handler mainHandler;
private ExoPlayerView exoPlayerView; private ExoPlayerView exoPlayerView;
@ -114,6 +118,7 @@ class ReactExoplayerView extends FrameLayout implements
private boolean isBuffering; private boolean isBuffering;
private float rate = 1f; private float rate = 1f;
private float audioVolume = 1f; private float audioVolume = 1f;
private int minLoadRetryCount = 3;
private int maxBitRate = 0; private int maxBitRate = 0;
private long seekTime = C.TIME_UNSET; private long seekTime = C.TIME_UNSET;
@ -257,6 +262,76 @@ class ReactExoplayerView extends FrameLayout implements
} }
// Internal methods // Internal methods
/**
* Toggling the visibility of the player control view
*/
private void togglePlayerControlVisibility() {
reLayout(playerControlView);
if (playerControlView.isVisible()) {
playerControlView.hide();
} else {
playerControlView.show();
}
}
/**
* Initializing Player control
*/
private void initializePlayerControl() {
if (playerControlView == null) {
playerControlView = new PlayerControlView(getContext());
}
// Setting the player for the playerControlView
playerControlView.setPlayer(player);
playerControlView.show();
playPauseControlContainer = playerControlView.findViewById(R.id.exo_play_pause_container);
// Invoking onClick event for exoplayerView
exoPlayerView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
togglePlayerControlVisibility();
}
});
// Invoking onPlayerStateChanged event for Player
eventListener = new Player.EventListener() {
@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
reLayout(playPauseControlContainer);
//Remove this eventListener once its executed. since UI will work fine once after the reLayout is done
player.removeListener(eventListener);
}
};
player.addListener(eventListener);
}
/**
* Adding Player control to the frame layout
*/
private void addPlayerControl() {
LayoutParams layoutParams = new LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
playerControlView.setLayoutParams(layoutParams);
addView(playerControlView, 1, layoutParams);
}
/**
* Update the layout
* @param view view needs to update layout
*
* This is a workaround for the open bug in react-native: https://github.com/facebook/react-native/issues/17968
*/
private void reLayout(View view) {
if (view == null) return;
view.measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
view.layout(view.getLeft(), view.getTop(), view.getMeasuredWidth(), view.getMeasuredHeight());
}
private void initializePlayer() { private void initializePlayer() {
if (player == null) { if (player == null) {
TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory(BANDWIDTH_METER); TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory(BANDWIDTH_METER);
@ -302,6 +377,9 @@ class ReactExoplayerView extends FrameLayout implements
eventEmitter.loadStart(); eventEmitter.loadStart();
loadVideoStarted = true; loadVideoStarted = true;
} }
// Initializing the playerControlView
initializePlayerControl();
} }
private MediaSource buildMediaSource(Uri uri, String overrideExtension) { private MediaSource buildMediaSource(Uri uri, String overrideExtension) {
@ -310,12 +388,17 @@ class ReactExoplayerView extends FrameLayout implements
switch (type) { switch (type) {
case C.TYPE_SS: case C.TYPE_SS:
return new SsMediaSource(uri, buildDataSourceFactory(false), return new SsMediaSource(uri, buildDataSourceFactory(false),
new DefaultSsChunkSource.Factory(mediaDataSourceFactory), mainHandler, null); new DefaultSsChunkSource.Factory(mediaDataSourceFactory),
minLoadRetryCount, SsMediaSource.DEFAULT_LIVE_PRESENTATION_DELAY_MS,
mainHandler, null);
case C.TYPE_DASH: case C.TYPE_DASH:
return new DashMediaSource(uri, buildDataSourceFactory(false), return new DashMediaSource(uri, buildDataSourceFactory(false),
new DefaultDashChunkSource.Factory(mediaDataSourceFactory), mainHandler, null); new DefaultDashChunkSource.Factory(mediaDataSourceFactory),
minLoadRetryCount, DashMediaSource.DEFAULT_LIVE_PRESENTATION_DELAY_MS,
mainHandler, null);
case C.TYPE_HLS: case C.TYPE_HLS:
return new HlsMediaSource(uri, mediaDataSourceFactory, mainHandler, null); return new HlsMediaSource(uri, mediaDataSourceFactory,
minLoadRetryCount, mainHandler, null);
case C.TYPE_OTHER: case C.TYPE_OTHER:
return new ExtractorMediaSource(uri, mediaDataSourceFactory, new DefaultExtractorsFactory(), return new ExtractorMediaSource(uri, mediaDataSourceFactory, new DefaultExtractorsFactory(),
mainHandler, null); mainHandler, null);
@ -517,6 +600,10 @@ class ReactExoplayerView extends FrameLayout implements
onBuffering(false); onBuffering(false);
startProgressHandler(); startProgressHandler();
videoLoaded(); videoLoaded();
//Setting the visibility for the playerControlView
if(playerControlView != null) {
playerControlView.show();
}
break; break;
case ExoPlayer.STATE_ENDED: case ExoPlayer.STATE_ENDED:
text += "ended"; text += "ended";
@ -870,7 +957,7 @@ class ReactExoplayerView extends FrameLayout implements
TrackGroup group = groups.get(i); TrackGroup group = groups.get(i);
for (int j = 0; j < group.length; j++) { for (int j = 0; j < group.length; j++) {
Format format = group.getFormat(j); Format format = group.getFormat(j);
if (format.height == value.asInt()) { if (format.height == height) {
groupIndex = i; groupIndex = i;
tracks[0] = j; tracks[0] = j;
break; break;
@ -998,6 +1085,11 @@ class ReactExoplayerView extends FrameLayout implements
} }
} }
public void setMinLoadRetryCountModifier(int newMinLoadRetryCount) {
minLoadRetryCount = newMinLoadRetryCount;
releasePlayer();
initializePlayer();
}
public void setPlayInBackground(boolean playInBackground) { public void setPlayInBackground(boolean playInBackground) {
this.playInBackground = playInBackground; this.playInBackground = playInBackground;
@ -1056,4 +1148,17 @@ class ReactExoplayerView extends FrameLayout implements
releasePlayer(); releasePlayer();
initializePlayer(); initializePlayer();
} }
/**
* Handling controls prop
*
* @param controls Controls prop, if true enable controls, if false disable them
*/
public void setControls(boolean controls) {
if (controls && exoPlayerView != null) {
addPlayerControl();
} else if (getChildAt(1) instanceof PlayerControlView && exoPlayerView != null) {
removeViewAt(1);
}
}
} }

View File

@ -48,6 +48,7 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
private static final String PROP_REPORT_BANDWIDTH = "reportBandwidth"; private static final String PROP_REPORT_BANDWIDTH = "reportBandwidth";
private static final String PROP_SEEK = "seek"; private static final String PROP_SEEK = "seek";
private static final String PROP_RATE = "rate"; private static final String PROP_RATE = "rate";
private static final String PROP_MIN_LOAD_RETRY_COUNT = "minLoadRetryCount";
private static final String PROP_MAXIMUM_BIT_RATE = "maxBitRate"; private static final String PROP_MAXIMUM_BIT_RATE = "maxBitRate";
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";
@ -57,6 +58,7 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
private static final String PROP_SELECTED_VIDEO_TRACK_TYPE = "type"; private static final String PROP_SELECTED_VIDEO_TRACK_TYPE = "type";
private static final String PROP_SELECTED_VIDEO_TRACK_VALUE = "value"; private static final String PROP_SELECTED_VIDEO_TRACK_VALUE = "value";
private static final String PROP_HIDE_SHUTTER_VIEW = "hideShutterView"; private static final String PROP_HIDE_SHUTTER_VIEW = "hideShutterView";
private static final String PROP_CONTROLS = "controls";
@Override @Override
public String getName() { public String getName() {
@ -230,6 +232,11 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
videoView.setMaxBitRateModifier(maxBitRate); videoView.setMaxBitRateModifier(maxBitRate);
} }
@ReactProp(name = PROP_MIN_LOAD_RETRY_COUNT)
public void minLoadRetryCount(final ReactExoplayerView videoView, final int minLoadRetryCount) {
videoView.setMinLoadRetryCountModifier(minLoadRetryCount);
}
@ReactProp(name = PROP_PLAY_IN_BACKGROUND, defaultBoolean = false) @ReactProp(name = PROP_PLAY_IN_BACKGROUND, defaultBoolean = false)
public void setPlayInBackground(final ReactExoplayerView videoView, final boolean playInBackground) { public void setPlayInBackground(final ReactExoplayerView videoView, final boolean playInBackground) {
videoView.setPlayInBackground(playInBackground); videoView.setPlayInBackground(playInBackground);
@ -255,6 +262,11 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
videoView.setHideShutterView(hideShutterView); videoView.setHideShutterView(hideShutterView);
} }
@ReactProp(name = PROP_CONTROLS, defaultBoolean = false)
public void setControls(final ReactExoplayerView videoView, final boolean controls) {
videoView.setControls(controls);
}
@ReactProp(name = PROP_BUFFER_CONFIG) @ReactProp(name = PROP_BUFFER_CONFIG)
public void setBufferConfig(final ReactExoplayerView videoView, @Nullable ReadableMap bufferConfig) { public void setBufferConfig(final ReactExoplayerView videoView, @Nullable ReadableMap bufferConfig) {
int minBufferMs = DefaultLoadControl.DEFAULT_MIN_BUFFER_MS; int minBufferMs = DefaultLoadControl.DEFAULT_MIN_BUFFER_MS;

View File

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layoutDirection="ltr"
android:background="#CC000000"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:paddingTop="4dp"
android:orientation="horizontal">
<ImageButton android:id="@id/exo_prev"
style="@style/ExoMediaButton.Previous"/>
<ImageButton android:id="@id/exo_rew"
style="@style/ExoMediaButton.Rewind"/>
<FrameLayout
android:id="@+id/exo_play_pause_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center">
<ImageButton android:id="@id/exo_play"
style="@style/ExoMediaButton.Play"/>
<ImageButton android:id="@id/exo_pause"
style="@style/ExoMediaButton.Pause"/>
</FrameLayout>
<ImageButton android:id="@id/exo_ffwd"
style="@style/ExoMediaButton.FastForward"/>
<ImageButton android:id="@id/exo_next"
style="@style/ExoMediaButton.Next"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView android:id="@id/exo_position"
android:layout_width="50dp"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textStyle="bold"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:includeFontPadding="false"
android:textColor="#FFBEBEBE"/>
<com.google.android.exoplayer2.ui.DefaultTimeBar
android:id="@id/exo_progress"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="26dp"/>
<TextView android:id="@id/exo_duration"
android:layout_width="50dp"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textStyle="bold"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:includeFontPadding="false"
android:textColor="#FFBEBEBE"/>
</LinearLayout>
</LinearLayout>

View File

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

View File

@ -0,0 +1,6 @@
[android]
target = Google Inc.:Google APIs:23
[maven_repositories]
central = https://repo1.maven.org/maven2

View File

@ -0,0 +1,58 @@
[ignore]
; We fork some components by platform
.*/*[.]android.js
# We fork some components by platform.
.*/*[.]android.js
# Ignore templates with `@flow` in header
.*/local-cli/generator.*
# Ignore malformed json
.*/node_modules/y18n/test/.*\.json
# Ignore the website subdir
<PROJECT_ROOT>/website/.*
# Ignore BUCK generated dirs
<PROJECT_ROOT>/\.buckd/
; Ignore unexpected extra "@providesModule"
.*/node_modules/.*/node_modules/fbjs/.*
; Ignore duplicate module providers
; For RN Apps installed via npm, "Libraries" folder is inside
; "node_modules/react-native" but in the source repo it is in the root
.*/Libraries/react-native/React.js
.*/Libraries/react-native/ReactNative.js
[include]
[libs]
node_modules/react-native/Libraries/react-native/react-native-interface.js
node_modules/react-native/flow
flow/
[options]
module.system=haste
experimental.strict_type_args=true
munge_underscores=true
module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub'
suppress_type=$FlowIssue
suppress_type=$FlowFixMe
suppress_type=$FixMe
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(3[0-6]\\|[1-2][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(3[0-6]\\|1[0-9]\\|[1-2][0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
unsafe.enable_getters_and_setters=true
[version]
^0.36.0

View File

@ -0,0 +1 @@
*.pbxproj -text

View File

@ -0,0 +1,53 @@
# OSX
#
.DS_Store
# Xcode
#
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate
project.xcworkspace
# Android/IntelliJ
#
build/
.idea
.gradle
local.properties
*.iml
# node.js
#
node_modules/
npm-debug.log
# BUCK
buck-out/
\.buckd/
android/app/libs
*.keystore
# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots

View File

@ -0,0 +1,144 @@
// Initialize some variables before react-native code would access them
var onmessage=null, self=global;
// Cache Node's original require as __debug__.require
global.__debug__={require: require};
// avoid Node's GLOBAL deprecation warning
Object.defineProperty(global, "GLOBAL", {
configurable: true,
writable: true,
enumerable: true,
value: global
});
// Prevent leaking process.versions from debugger process to
// worker because pure React Native doesn't do that and some packages as js-md5 rely on this behavior
Object.defineProperty(process, "versions", {
value: undefined
});
var vscodeHandlers = {
'vscode_reloadApp': function () {
try {
global.require('NativeModules').DevMenu.reload();
} catch (err) {
// ignore
}
},
'vscode_showDevMenu': function () {
try {
var DevMenu = global.require('NativeModules').DevMenu.show();
} catch (err) {
// ignore
}
}
};
process.on("message", function (message) {
if (message.data && vscodeHandlers[message.data.method]) {
vscodeHandlers[message.data.method]();
} else if(onmessage) {
onmessage(message);
}
});
var postMessage = function(message){
process.send(message);
};
if (!self.postMessage) {
self.postMessage = postMessage;
}
var importScripts = (function(){
var fs=require('fs'), vm=require('vm');
return function(scriptUrl){
var scriptCode = fs.readFileSync(scriptUrl, "utf8");
vm.runInThisContext(scriptCode, {filename: scriptUrl});
};
})();
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
/* global __fbBatchedBridge, self, importScripts, postMessage, onmessage: true */
/* eslint no-unused-vars: 0 */
'use strict';
onmessage = (function() {
var visibilityState;
var showVisibilityWarning = (function() {
var hasWarned = false;
return function() {
// Wait until `YellowBox` gets initialized before displaying the warning.
if (hasWarned || console.warn.toString().includes('[native code]')) {
return;
}
hasWarned = true;
console.warn(
'Remote debugger is in a background tab which may cause apps to ' +
'perform slowly. Fix this by foregrounding the tab (or opening it in ' +
'a separate window).',
);
};
})();
var messageHandlers = {
executeApplicationScript: function(message, sendReply) {
for (var key in message.inject) {
self[key] = JSON.parse(message.inject[key]);
}
var error;
try {
importScripts(message.url);
} catch (err) {
error = err.message;
}
sendReply(null /* result */, error);
},
setDebuggerVisibility: function(message) {
visibilityState = message.visibilityState;
},
};
return function(message) {
if (visibilityState === 'hidden') {
showVisibilityWarning();
}
var object = message.data;
var sendReply = function(result, error) {
postMessage({replyID: object.id, result: result, error: error});
};
var handler = messageHandlers[object.method];
if (handler) {
// Special cased handlers
handler(object, sendReply);
} else {
// Other methods get called on the bridge
var returnValue = [[], [], [], 0];
var error;
try {
if (typeof __fbBatchedBridge === 'object') {
returnValue = __fbBatchedBridge[object.method].apply(
null,
object.arguments,
);
} else {
error = 'Failed to call function, __fbBatchedBridge is undefined';
}
} catch (err) {
error = err.message;
} finally {
sendReply(JSON.stringify(returnValue), error);
}
}
};
})();
// Notify debugger that we're done with loading
// and started listening for IPC messages
postMessage({workerLoaded:true});

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,79 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Debug Android Simulator",
"program": "${workspaceRoot}/.vscode/launchReactNative.js",
"type": "reactnative",
"request": "launch",
"platform": "android",
"sourceMaps": true,
"trace": "log",
"outDir": "${workspaceRoot}/.vscode/.react"
},
{
"name": "Debug Android Device",
"program": "${workspaceRoot}/.vscode/launchReactNative.js",
"type": "reactnative",
"request": "launch",
"platform": "android",
"target": "device",
"sourceMaps": true,
"outDir": "${workspaceRoot}/.vscode/.react"
},
{
"name": "Debug iPhone 6",
"program": "${workspaceRoot}/.vscode/launchReactNative.js",
"type": "reactnative",
"request": "launch",
"platform": "ios",
"sourceMaps": true,
"trace": "log",
"target": "iPhone 6",
"outDir": "${workspaceRoot}/.vscode/.react"
},
{
"name": "Debug iPhone X",
"program": "${workspaceRoot}/.vscode/launchReactNative.js",
"type": "reactnative",
"request": "launch",
"platform": "ios",
"sourceMaps": true,
"trace": "log",
"target": "iPhone X",
"outDir": "${workspaceRoot}/.vscode/.react"
},
{
"name": "Debug iPad Air",
"program": "${workspaceRoot}/.vscode/launchReactNative.js",
"type": "reactnative",
"request": "launch",
"platform": "ios",
"sourceMaps": true,
"trace": "log",
"target": "iPad Air",
"outDir": "${workspaceRoot}/.vscode/.react"
},
{
"name": "Debug iOS Device",
"program": "${workspaceRoot}/.vscode/launchReactNative.js",
"type": "reactnative",
"request": "launch",
"platform": "ios",
"target": "device",
"sourceMaps": true,
"outDir": "${workspaceRoot}/.vscode/.react"
},
{
"name": "Attach to packager",
"program": "${workspaceRoot}/.vscode/launchReactNative.js",
"type": "reactnative",
"request": "attach",
"sourceMaps": true,
"outDir": "${workspaceRoot}/.vscode/.react"
}
]
}

View File

@ -0,0 +1,5 @@
{
"editor.insertSpaces": true,
"editor.tabSize": 2,
"java.configuration.updateBuildConfiguration": "disabled"
}

View File

@ -0,0 +1 @@
{}

View File

@ -0,0 +1,31 @@
import { Dimensions } from "react-native";
export default function Util() {}
Util.isPortrait = () => {
const dim = Dimensions.get("screen");
return dim.height >= dim.width;
};
/**
* Return a timer for video from< time in seconds
* ~~ is used as faster substitute for Math.floor() function
* https://stackoverflow.com/questions/5971645/what-is-the-double-tilde-operator-in-javascript
* @param time
* @returns {string}
*/
Util.secondToTime = (time) => {
return ~~(time / 60) + ":" + (time % 60 < 10 ? "0" : "") + time % 60;
};
Util.normalizeSeconds = (number) => {
let sec_num = parseInt(number, 10); // don't forget the second param
let hours = Math.floor(sec_num / 3600);
let minutes = Math.floor((sec_num - (hours * 3600)) / 60);
let seconds = sec_num - (hours * 3600) - (minutes * 60);
if (hours < 10) {hours = "0"+hours;}
if (minutes < 10) {minutes = "0"+minutes;}
if (seconds < 10) {seconds = "0"+seconds;}
return hours+':'+minutes+':'+seconds;
};

Binary file not shown.

View File

@ -0,0 +1,81 @@
'use strict';
import React, {
Component
} from 'react';
import {
AppRegistry,
StyleSheet,
View,
Dimensions,
Text,
Button
} from 'react-native';
import Util from './Utils'
import Video from 'react-native-video';
export default class VideoPlayer extends Component {
constructor(props) {
super(props);
this.onLayout = this.onLayout.bind(this);
}
componentWillMount() {
this.resizeVideoPlayer();
}
render() {
return <View
onLayout={this.onLayout}
style={styles.container}>
<Text>Here's some pre-Text</Text>
<Video
ref={p => { this.videoPlayer = p; }}
source={require('./broadchurch.mp4')}
style={{width: this.state.orientationWidth, height: this.state.orientationHeight }}
controls={true}
/>
<Button title="full screen" onPress={ this.onPress.bind(this) }></Button>
</View>
}
onPress() {
if (this.videoPlayer!=null)
this.videoPlayer.presentFullscreenPlayer();
}
resizeVideoPlayer() {
// Always in 16 /9 aspect ratio
let {width, height} = Dimensions.get('window');
if (Util.isPortrait()) {
this.setState({
orientationWidth: width * 0.8,
orientationHeight: width * 0.8 * 0.56,
});
} else {
this.setState({
orientationHeight: height * 0.8,
orientationWidth: height * 0.8 * 1.77
});
}
}
onLayout(e) {
console.log('on layout called');
this.resizeVideoPlayer();
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'white',
},
});
AppRegistry.registerComponent('VideoPlayer', () => VideoPlayer);

View File

@ -0,0 +1,210 @@
'use strict';
import React, {
Component
} from 'react';
import {
AppRegistry,
StyleSheet,
Text,
TouchableOpacity,
View,
} from 'react-native';
import Video from 'react-native-video';
class VideoPlayer extends Component {
constructor(props) {
super(props);
this.onLoad = this.onLoad.bind(this);
this.onProgress = this.onProgress.bind(this);
}
state = {
rate: 1,
volume: 1,
muted: false,
resizeMode: 'contain',
duration: 0.0,
currentTime: 0.0,
};
onLoad(data) {
this.setState({duration: data.duration});
}
onProgress(data) {
this.setState({currentTime: data.currentTime});
}
getCurrentTimePercentage() {
if (this.state.currentTime > 0) {
return parseFloat(this.state.currentTime) / parseFloat(this.state.duration);
} else {
return 0;
}
}
renderRateControl(rate) {
const isSelected = (this.state.rate == rate);
return (
<TouchableOpacity onPress={() => { this.setState({rate: rate}) }}>
<Text style={[styles.controlOption, {fontWeight: isSelected ? "bold" : "normal"}]}>
{rate}x
</Text>
</TouchableOpacity>
)
}
renderResizeModeControl(resizeMode) {
const isSelected = (this.state.resizeMode == resizeMode);
return (
<TouchableOpacity onPress={() => { this.setState({resizeMode: resizeMode}) }}>
<Text style={[styles.controlOption, {fontWeight: isSelected ? "bold" : "normal"}]}>
{resizeMode}
</Text>
</TouchableOpacity>
)
}
renderVolumeControl(volume) {
const isSelected = (this.state.volume == volume);
return (
<TouchableOpacity onPress={() => { this.setState({volume: volume}) }}>
<Text style={[styles.controlOption, {fontWeight: isSelected ? "bold" : "normal"}]}>
{volume * 100}%
</Text>
</TouchableOpacity>
)
}
render() {
const flexCompleted = this.getCurrentTimePercentage() * 100;
const flexRemaining = (1 - this.getCurrentTimePercentage()) * 100;
return (
<View style={styles.container}>
<TouchableOpacity style={styles.fullScreen} onPress={() => {this.setState({paused: !this.state.paused})}}>
<Video source={require('./broadchurch.mp4')}
style={styles.fullScreen}
rate={this.state.rate}
paused={this.state.paused}
volume={this.state.volume}
muted={this.state.muted}
resizeMode={this.state.resizeMode}
onLoad={this.onLoad}
onProgress={this.onProgress}
onEnd={() => { console.log('Done!') }}
repeat={true} />
</TouchableOpacity>
<View style={styles.controls}>
<View style={styles.generalControls}>
<View style={styles.rateControl}>
{this.renderRateControl(0.25)}
{this.renderRateControl(0.5)}
{this.renderRateControl(1.0)}
{this.renderRateControl(1.5)}
{this.renderRateControl(2.0)}
</View>
<View style={styles.volumeControl}>
{this.renderVolumeControl(0.5)}
{this.renderVolumeControl(1)}
{this.renderVolumeControl(1.5)}
</View>
<View style={styles.resizeModeControl}>
{this.renderResizeModeControl('cover')}
{this.renderResizeModeControl('contain')}
{this.renderResizeModeControl('stretch')}
</View>
</View>
<View style={styles.trackingControls}>
<View style={styles.progress}>
<View style={[styles.innerProgressCompleted, {flex: flexCompleted}]} />
<View style={[styles.innerProgressRemaining, {flex: flexRemaining}]} />
</View>
</View>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'black',
},
fullScreen: {
position: 'absolute',
top: 0,
left: 0,
bottom: 0,
right: 0,
},
controls: {
backgroundColor: "transparent",
borderRadius: 5,
position: 'absolute',
bottom: 20,
left: 20,
right: 20,
},
progress: {
flex: 1,
flexDirection: 'row',
borderRadius: 3,
overflow: 'hidden',
},
innerProgressCompleted: {
height: 20,
backgroundColor: '#cccccc',
},
innerProgressRemaining: {
height: 20,
backgroundColor: '#2C2C2C',
},
generalControls: {
flex: 1,
flexDirection: 'row',
borderRadius: 4,
overflow: 'hidden',
paddingBottom: 10,
},
rateControl: {
flex: 1,
flexDirection: 'row',
justifyContent: 'center',
},
volumeControl: {
flex: 1,
flexDirection: 'row',
justifyContent: 'center',
},
resizeModeControl: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
},
controlOption: {
alignSelf: 'center',
fontSize: 11,
color: "white",
paddingLeft: 2,
paddingRight: 2,
lineHeight: 12,
},
});
AppRegistry.registerComponent('VideoPlayer', () => VideoPlayer);

View File

@ -0,0 +1,16 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <UIKit/UIKit.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (nonatomic, strong) UIWindow *window;
@end

View File

@ -0,0 +1,52 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "AppDelegate.h"
#import "RCTRootView.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSURL *jsCodeLocation;
// Loading JavaScript code - uncomment the one you want.
// OPTION 1
// Load from development server. Start the server from the repository root:
//
// $ npm start
//
// To run on device, change `localhost` to the IP address of your computer, and make sure your computer and
// iOS device are on the same Wi-Fi network.
jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.ios.bundle?platform=ios&dev=true"];
// OPTION 2
// Load from pre-bundled file on disk. To re-generate the static bundle, run
//
// $ curl 'http://localhost:8081/index.ios.bundle?dev=false&minify=true' -o iOS/main.jsbundle
//
// and uncomment the next following line
// jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"VideoPlayer"
initialProperties: nil
launchOptions:launchOptions];
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *rootViewController = [[UIViewController alloc] init];
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];
return YES;
}
@end

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="6751" systemVersion="14C1510" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6736"/>
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="iN0-l3-epB">
<rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Powered by React Native" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="8ie-xW-0ye">
<rect key="frame" x="20" y="439" width="441" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="VideoPlayer" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="kId-c2-rCX">
<rect key="frame" x="20" y="140" width="441" height="43"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="36"/>
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="kId-c2-rCX" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="bottom" multiplier="1/3" constant="1" id="5cJ-9S-tgC"/>
<constraint firstAttribute="centerX" secondItem="kId-c2-rCX" secondAttribute="centerX" id="Koa-jz-hwk"/>
<constraint firstAttribute="bottom" secondItem="8ie-xW-0ye" secondAttribute="bottom" constant="20" id="Kzo-t9-V3l"/>
<constraint firstItem="8ie-xW-0ye" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="MfP-vx-nX0"/>
<constraint firstAttribute="centerX" secondItem="8ie-xW-0ye" secondAttribute="centerX" id="ZEH-qu-HZ9"/>
<constraint firstItem="kId-c2-rCX" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="fvb-Df-36g"/>
</constraints>
<nil key="simulatedStatusBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<point key="canvasLocation" x="548" y="455"/>
</view>
</objects>
</document>

View File

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>2</string>
<key>LSApplicationCategoryType</key>
<string></string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>

View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>localhost</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
</dict>
<key>NSLocationWhenInUseUsageDescription</key>
<string></string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,105 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1000"
version = "1.3">
<BuildAction
parallelizeBuildables = "NO"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "2D2A28121D9B038B00D4039D"
BuildableName = "libReact.a"
BlueprintName = "React-tvOS"
ReferencedContainer = "container:../node_modules/react-native/React/React.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "FA8681B6216D5C6D0010C92A"
BuildableName = "VideoPlayer-tvOS.app"
BlueprintName = "VideoPlayer-tvOS"
ReferencedContainer = "container:VideoPlayer.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "FA8681B6216D5C6D0010C92A"
BuildableName = "VideoPlayer-tvOS.app"
BlueprintName = "VideoPlayer-tvOS"
ReferencedContainer = "container:VideoPlayer.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "FA8681B6216D5C6D0010C92A"
BuildableName = "VideoPlayer-tvOS.app"
BlueprintName = "VideoPlayer-tvOS"
ReferencedContainer = "container:VideoPlayer.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "FA8681B6216D5C6D0010C92A"
BuildableName = "VideoPlayer-tvOS.app"
BlueprintName = "VideoPlayer-tvOS"
ReferencedContainer = "container:VideoPlayer.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,105 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0620"
version = "1.3">
<BuildAction
parallelizeBuildables = "NO"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "83CBBA2D1A601D0E00E9B192"
BuildableName = "libReact.a"
BlueprintName = "React"
ReferencedContainer = "container:../node_modules/react-native/React/React.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "VideoPlayer.app"
BlueprintName = "VideoPlayer"
ReferencedContainer = "container:VideoPlayer.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "VideoPlayer.app"
BlueprintName = "VideoPlayer"
ReferencedContainer = "container:VideoPlayer.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "VideoPlayer.app"
BlueprintName = "VideoPlayer"
ReferencedContainer = "container:VideoPlayer.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
BuildableName = "VideoPlayer.app"
BlueprintName = "VideoPlayer"
ReferencedContainer = "container:VideoPlayer.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,16 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <UIKit/UIKit.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (nonatomic, strong) UIWindow *window;
@end

View File

@ -0,0 +1,37 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "AppDelegate.h"
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSURL *jsCodeLocation;
jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil];
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"VideoPlayer"
initialProperties:nil
launchOptions:launchOptions];
rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *rootViewController = [UIViewController new];
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];
return YES;
}
@end

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="7702" systemVersion="14D136" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7701"/>
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="iN0-l3-epB">
<rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Powered by React Native" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="8ie-xW-0ye">
<rect key="frame" x="20" y="439" width="441" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="VideoPlayer" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="kId-c2-rCX">
<rect key="frame" x="20" y="140" width="441" height="43"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="36"/>
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="kId-c2-rCX" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="bottom" multiplier="1/3" constant="1" id="5cJ-9S-tgC"/>
<constraint firstAttribute="centerX" secondItem="kId-c2-rCX" secondAttribute="centerX" id="Koa-jz-hwk"/>
<constraint firstAttribute="bottom" secondItem="8ie-xW-0ye" secondAttribute="bottom" constant="20" id="Kzo-t9-V3l"/>
<constraint firstItem="8ie-xW-0ye" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="MfP-vx-nX0"/>
<constraint firstAttribute="centerX" secondItem="8ie-xW-0ye" secondAttribute="centerX" id="ZEH-qu-HZ9"/>
<constraint firstItem="kId-c2-rCX" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="fvb-Df-36g"/>
</constraints>
<nil key="simulatedStatusBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<point key="canvasLocation" x="548" y="455"/>
</view>
</objects>
</document>

View File

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>NSLocationWhenInUseUsageDescription</key>
<string></string>
<key>NSAppTransportSecurity</key>
<!--See http://ste.vn/2015/06/10/configuring-app-transport-security-ios-9-osx-10-11/ -->
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>localhost</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
</dict>
</dict>
</plist>

View File

@ -0,0 +1,18 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}

View File

@ -0,0 +1,5 @@
// Offline JS
// To re-generate the offline bundle, run this from root of your project
// $ curl 'http://localhost:8081/index.ios.bundle?dev=false&minify=true' -o iOS/main.jsbundle
throw new Error('Offline JS file is empty. See iOS/main.jsbundle for instructions');

View File

@ -0,0 +1,19 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}

View File

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

View File

@ -15,8 +15,10 @@
@class RCTEventDispatcher; @class RCTEventDispatcher;
#if __has_include(<react-native-video/RCTVideoCache.h>) #if __has_include(<react-native-video/RCTVideoCache.h>)
@interface RCTVideo : UIView <RCTVideoPlayerViewControllerDelegate, DVAssetLoaderDelegatesDelegate> @interface RCTVideo : UIView <RCTVideoPlayerViewControllerDelegate, DVAssetLoaderDelegatesDelegate>
#else #elif TARGET_OS_TV
@interface RCTVideo : UIView <RCTVideoPlayerViewControllerDelegate> @interface RCTVideo : UIView <RCTVideoPlayerViewControllerDelegate>
#else
@interface RCTVideo : UIView <RCTVideoPlayerViewControllerDelegate, AVPictureInPictureControllerDelegate>
#endif #endif
@property (nonatomic, copy) RCTBubblingEventBlock onVideoLoadStart; @property (nonatomic, copy) RCTBubblingEventBlock onVideoLoadStart;
@ -38,6 +40,8 @@
@property (nonatomic, copy) RCTBubblingEventBlock onPlaybackResume; @property (nonatomic, copy) RCTBubblingEventBlock onPlaybackResume;
@property (nonatomic, copy) RCTBubblingEventBlock onPlaybackRateChange; @property (nonatomic, copy) RCTBubblingEventBlock onPlaybackRateChange;
@property (nonatomic, copy) RCTBubblingEventBlock onVideoExternalPlaybackChange; @property (nonatomic, copy) RCTBubblingEventBlock onVideoExternalPlaybackChange;
@property (nonatomic, copy) RCTBubblingEventBlock onPictureInPictureStatusChanged;
@property (nonatomic, copy) RCTBubblingEventBlock onRestoreUserInterfaceForPictureInPictureStop;
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER; - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;

View File

@ -64,6 +64,7 @@ static int const RCTVideoUnset = -1;
BOOL _playbackStalled; BOOL _playbackStalled;
BOOL _playInBackground; BOOL _playInBackground;
BOOL _playWhenInactive; BOOL _playWhenInactive;
BOOL _pictureInPicture;
NSString * _ignoreSilentSwitch; NSString * _ignoreSilentSwitch;
NSString * _resizeMode; NSString * _resizeMode;
BOOL _fullscreen; BOOL _fullscreen;
@ -76,6 +77,10 @@ static int const RCTVideoUnset = -1;
#if __has_include(<react-native-video/RCTVideoCache.h>) #if __has_include(<react-native-video/RCTVideoCache.h>)
RCTVideoCache * _videoCache; RCTVideoCache * _videoCache;
#endif #endif
#if TARGET_OS_IOS
void (^__strong _Nonnull _restoreUserInterfaceForPIPStopCompletionHandler)(BOOL);
AVPictureInPictureController *_pipController;
#endif
} }
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
@ -100,7 +105,11 @@ static int const RCTVideoUnset = -1;
_playInBackground = false; _playInBackground = false;
_allowsExternalPlayback = YES; _allowsExternalPlayback = YES;
_playWhenInactive = false; _playWhenInactive = false;
_pictureInPicture = false;
_ignoreSilentSwitch = @"inherit"; // inherit, ignore, obey _ignoreSilentSwitch = @"inherit"; // inherit, ignore, obey
#if TARGET_OS_IOS
_restoreUserInterfaceForPIPStopCompletionHandler = NULL;
#endif
#if __has_include(<react-native-video/RCTVideoCache.h>) #if __has_include(<react-native-video/RCTVideoCache.h>)
_videoCache = [RCTVideoCache sharedInstance]; _videoCache = [RCTVideoCache sharedInstance];
#endif #endif
@ -137,7 +146,6 @@ static int const RCTVideoUnset = -1;
viewController.view.frame = self.bounds; viewController.view.frame = self.bounds;
viewController.player = player; viewController.player = player;
viewController.view.frame = self.bounds;
return viewController; return viewController;
} }
@ -197,6 +205,7 @@ static int const RCTVideoUnset = -1;
[self removePlayerLayer]; [self removePlayerLayer];
[self removePlayerItemObservers]; [self removePlayerItemObservers];
[_player removeObserver:self forKeyPath:playbackRate context:nil]; [_player removeObserver:self forKeyPath:playbackRate context:nil];
[_player removeObserver:self forKeyPath:externalPlaybackActive context: nil];
} }
#pragma mark - App lifecycle handlers #pragma mark - App lifecycle handlers
@ -466,6 +475,10 @@ static int const RCTVideoUnset = -1;
bool shouldCache = [RCTConvert BOOL:[source objectForKey:@"shouldCache"]]; bool shouldCache = [RCTConvert BOOL:[source objectForKey:@"shouldCache"]];
NSString *uri = [source objectForKey:@"uri"]; NSString *uri = [source objectForKey:@"uri"];
NSString *type = [source objectForKey:@"type"]; NSString *type = [source objectForKey:@"type"];
if (!uri || [uri isEqualToString:@""]) {
DebugLog(@"Could not find video URL in source '%@'", source);
return;
}
NSURL *url = isNetwork || isAsset NSURL *url = isNetwork || isAsset
? [NSURL URLWithString:uri] ? [NSURL URLWithString:uri]
@ -565,6 +578,27 @@ static int const RCTVideoUnset = -1;
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{ {
// when controls==true, this is a hack to reset the rootview when rotation happens in fullscreen
if (object == _playerViewController.contentOverlayView) {
if ([keyPath isEqualToString:@"frame"]) {
CGRect oldRect = [change[NSKeyValueChangeOldKey] CGRectValue];
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;
} else
return [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
if (object == _playerItem) { if (object == _playerItem) {
// When timeMetadata is read the event onTimedMetadata is triggered // When timeMetadata is read the event onTimedMetadata is triggered
if ([keyPath isEqualToString:timedMetadata]) { if ([keyPath isEqualToString:timedMetadata]) {
@ -781,6 +815,44 @@ static int const RCTVideoUnset = -1;
_playWhenInactive = playWhenInactive; _playWhenInactive = playWhenInactive;
} }
- (void)setPictureInPicture:(BOOL)pictureInPicture
{
#if TARGET_OS_IOS
if (_pictureInPicture == pictureInPicture) {
return;
}
_pictureInPicture = pictureInPicture;
if (_pipController && _pictureInPicture && ![_pipController isPictureInPictureActive]) {
dispatch_async(dispatch_get_main_queue(), ^{
[_pipController startPictureInPicture];
});
} else if (_pipController && !_pictureInPicture && [_pipController isPictureInPictureActive]) {
dispatch_async(dispatch_get_main_queue(), ^{
[_pipController stopPictureInPicture];
});
}
#endif
}
#if TARGET_OS_IOS
- (void)setRestoreUserInterfaceForPIPStopCompletionHandler:(BOOL)restore
{
if (_restoreUserInterfaceForPIPStopCompletionHandler != NULL) {
_restoreUserInterfaceForPIPStopCompletionHandler(restore);
_restoreUserInterfaceForPIPStopCompletionHandler = NULL;
}
}
- (void)setupPipController {
if (!_pipController && _playerLayer && [AVPictureInPictureController isPictureInPictureSupported]) {
// Create new controller passing reference to the AVPlayerLayer
_pipController = [[AVPictureInPictureController alloc] initWithPlayerLayer:_playerLayer];
_pipController.delegate = self;
}
}
#endif
- (void)setIgnoreSilentSwitch:(NSString *)ignoreSilentSwitch - (void)setIgnoreSilentSwitch:(NSString *)ignoreSilentSwitch
{ {
_ignoreSilentSwitch = ignoreSilentSwitch; _ignoreSilentSwitch = ignoreSilentSwitch;
@ -1215,8 +1287,15 @@ static int const RCTVideoUnset = -1;
// to prevent video from being animated when resizeMode is 'cover' // to prevent video from being animated when resizeMode is 'cover'
// resize mode must be set before subview is added // resize mode must be set before subview is added
[self setResizeMode:_resizeMode]; [self setResizeMode:_resizeMode];
if (_controls) {
UIViewController *viewController = [self reactViewController];
[viewController addChildViewController:_playerViewController];
[self addSubview:_playerViewController.view]; [self addSubview:_playerViewController.view];
} }
[_playerViewController.contentOverlayView addObserver:self forKeyPath:@"frame" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:NULL];
}
} }
- (void)usePlayerLayer - (void)usePlayerLayer
@ -1235,6 +1314,9 @@ static int const RCTVideoUnset = -1;
[self.layer addSublayer:_playerLayer]; [self.layer addSublayer:_playerLayer];
self.layer.needsDisplayOnBoundsChange = YES; self.layer.needsDisplayOnBoundsChange = YES;
#if TARGET_OS_IOS
[self setupPipController];
#endif
} }
} }
@ -1405,6 +1487,7 @@ static int const RCTVideoUnset = -1;
[self removePlayerLayer]; [self removePlayerLayer];
[_playerViewController.contentOverlayView removeObserver:self forKeyPath:@"frame"];
[_playerViewController.view removeFromSuperview]; [_playerViewController.view removeFromSuperview];
_playerViewController = nil; _playerViewController = nil;
@ -1491,4 +1574,44 @@ static int const RCTVideoUnset = -1;
return array[0]; return array[0];
} }
#pragma mark - Picture in Picture
#if TARGET_OS_IOS
- (void)pictureInPictureControllerDidStopPictureInPicture:(AVPictureInPictureController *)pictureInPictureController {
if (self.onPictureInPictureStatusChanged) {
self.onPictureInPictureStatusChanged(@{
@"isActive": [NSNumber numberWithBool:false]
});
}
}
- (void)pictureInPictureControllerDidStartPictureInPicture:(AVPictureInPictureController *)pictureInPictureController {
if (self.onPictureInPictureStatusChanged) {
self.onPictureInPictureStatusChanged(@{
@"isActive": [NSNumber numberWithBool:true]
});
}
}
- (void)pictureInPictureControllerWillStopPictureInPicture:(AVPictureInPictureController *)pictureInPictureController {
}
- (void)pictureInPictureControllerWillStartPictureInPicture:(AVPictureInPictureController *)pictureInPictureController {
}
- (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController failedToStartPictureInPictureWithError:(NSError *)error {
}
- (void)pictureInPictureController:(AVPictureInPictureController *)pictureInPictureController restoreUserInterfaceForPictureInPictureStopWithCompletionHandler:(void (^)(BOOL))completionHandler {
NSAssert(_restoreUserInterfaceForPIPStopCompletionHandler == NULL, @"restoreUserInterfaceForPIPStopCompletionHandler was not called after picture in picture was exited.");
if (self.onRestoreUserInterfaceForPictureInPictureStop) {
self.onRestoreUserInterfaceForPictureInPictureStop(@{});
}
_restoreUserInterfaceForPIPStopCompletionHandler = completionHandler;
}
#endif
@end @end

View File

@ -32,6 +32,7 @@ RCT_EXPORT_VIEW_PROPERTY(controls, BOOL);
RCT_EXPORT_VIEW_PROPERTY(volume, float); RCT_EXPORT_VIEW_PROPERTY(volume, float);
RCT_EXPORT_VIEW_PROPERTY(playInBackground, BOOL); RCT_EXPORT_VIEW_PROPERTY(playInBackground, BOOL);
RCT_EXPORT_VIEW_PROPERTY(playWhenInactive, BOOL); RCT_EXPORT_VIEW_PROPERTY(playWhenInactive, BOOL);
RCT_EXPORT_VIEW_PROPERTY(pictureInPicture, BOOL);
RCT_EXPORT_VIEW_PROPERTY(ignoreSilentSwitch, NSString); RCT_EXPORT_VIEW_PROPERTY(ignoreSilentSwitch, NSString);
RCT_EXPORT_VIEW_PROPERTY(rate, float); RCT_EXPORT_VIEW_PROPERTY(rate, float);
RCT_EXPORT_VIEW_PROPERTY(seek, NSDictionary); RCT_EXPORT_VIEW_PROPERTY(seek, NSDictionary);
@ -42,6 +43,7 @@ RCT_EXPORT_VIEW_PROPERTY(fullscreenOrientation, NSString);
RCT_EXPORT_VIEW_PROPERTY(filter, NSString); RCT_EXPORT_VIEW_PROPERTY(filter, NSString);
RCT_EXPORT_VIEW_PROPERTY(filterEnabled, BOOL); RCT_EXPORT_VIEW_PROPERTY(filterEnabled, BOOL);
RCT_EXPORT_VIEW_PROPERTY(progressUpdateInterval, float); RCT_EXPORT_VIEW_PROPERTY(progressUpdateInterval, float);
RCT_EXPORT_VIEW_PROPERTY(restoreUserInterfaceForPIPStopCompletionHandler, BOOL);
/* Should support: onLoadStart, onLoad, and onError to stay consistent with Image */ /* Should support: onLoadStart, onLoad, and onError to stay consistent with Image */
RCT_EXPORT_VIEW_PROPERTY(onVideoLoadStart, RCTBubblingEventBlock); RCT_EXPORT_VIEW_PROPERTY(onVideoLoadStart, RCTBubblingEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onVideoLoad, RCTBubblingEventBlock); RCT_EXPORT_VIEW_PROPERTY(onVideoLoad, RCTBubblingEventBlock);
@ -77,6 +79,8 @@ RCT_REMAP_METHOD(save,
} }
}]; }];
} }
RCT_EXPORT_VIEW_PROPERTY(onPictureInPictureStatusChanged, RCTBubblingEventBlock);
RCT_EXPORT_VIEW_PROPERTY(onRestoreUserInterfaceForPictureInPictureStop, RCTBubblingEventBlock);
- (NSDictionary *)constantsToExport - (NSDictionary *)constantsToExport
{ {