Merge remote-tracking branch 'olgunkaya/master'
This commit is contained in:
commit
d5c245e675
15
README.md
15
README.md
@ -1,4 +1,17 @@
|
|||||||
# react-native-video
|
## react-native-video-inc-ads
|
||||||
|
an addon property (**adTagUrl**) to support google ima on react-native-video.
|
||||||
|
Thanks to https://github.com/RobbyWH for his great work. I just merged his ima branch with latest react-native-video branch.
|
||||||
|
|
||||||
|
const adTagUrl = "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/"
|
||||||
|
+ "ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp"
|
||||||
|
+ "&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite"
|
||||||
|
+ "%26sample_ar%3Dpremidpost&cmsid=496&vid=short_onecue&correlator=";
|
||||||
|
|
||||||
|
In case of any issue, one can follow RobbyWH's comments on the issue : https://github.com/react-native-video/react-native-video/issues/488 :)
|
||||||
|
|
||||||
|
A new function property `onReceiveAdEvent` added. this is used to notify ad events from native component to react component.
|
||||||
|
|
||||||
|
## react-native-video
|
||||||
|
|
||||||
> :warning: **Version 6 Alpha**: The following documentation may refer to features only available through the v6.0.0 alpha releases, [please see version 5.2.x](https://github.com/react-native-video/react-native-video/blob/v5.2.0/README.md) for the current documentation!
|
> :warning: **Version 6 Alpha**: The following documentation may refer to features only available through the v6.0.0 alpha releases, [please see version 5.2.x](https://github.com/react-native-video/react-native-video/blob/v5.2.0/README.md) for the current documentation!
|
||||||
|
|
||||||
|
9
Video.js
9
Video.js
@ -261,6 +261,13 @@ export default class Video extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_onReceiveAdEvent = (event) => {
|
||||||
|
if (this.props.onReceiveAdEvent) {
|
||||||
|
this.props.onReceiveAdEvent(event.nativeEvent);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
getViewManagerConfig = viewManagerName => {
|
getViewManagerConfig = viewManagerName => {
|
||||||
if (!UIManager.getViewManagerConfig) {
|
if (!UIManager.getViewManagerConfig) {
|
||||||
return UIManager[viewManagerName];
|
return UIManager[viewManagerName];
|
||||||
@ -343,6 +350,7 @@ export default class Video extends Component {
|
|||||||
onGetLicense: nativeProps.drm && nativeProps.drm.getLicense && this._onGetLicense,
|
onGetLicense: nativeProps.drm && nativeProps.drm.getLicense && this._onGetLicense,
|
||||||
onPictureInPictureStatusChanged: this._onPictureInPictureStatusChanged,
|
onPictureInPictureStatusChanged: this._onPictureInPictureStatusChanged,
|
||||||
onRestoreUserInterfaceForPictureInPictureStop: this._onRestoreUserInterfaceForPictureInPictureStop,
|
onRestoreUserInterfaceForPictureInPictureStop: this._onRestoreUserInterfaceForPictureInPictureStop,
|
||||||
|
onReceiveAdEvent: this._onReceiveAdEvent,
|
||||||
});
|
});
|
||||||
|
|
||||||
const posterStyle = {
|
const posterStyle = {
|
||||||
@ -527,6 +535,7 @@ Video.propTypes = {
|
|||||||
onPictureInPictureStatusChanged: PropTypes.func,
|
onPictureInPictureStatusChanged: PropTypes.func,
|
||||||
needsToRestoreUserInterfaceForPictureInPictureStop: PropTypes.func,
|
needsToRestoreUserInterfaceForPictureInPictureStop: PropTypes.func,
|
||||||
onExternalPlaybackChange: PropTypes.func,
|
onExternalPlaybackChange: PropTypes.func,
|
||||||
|
onReceiveAdEvents: PropTypes.func,
|
||||||
|
|
||||||
/* Required by react-native */
|
/* Required by react-native */
|
||||||
scaleX: PropTypes.number,
|
scaleX: PropTypes.number,
|
||||||
|
77
android-exoplayer/build.gradle
Normal file
77
android-exoplayer/build.gradle
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
apply plugin: 'com.android.library'
|
||||||
|
|
||||||
|
def safeExtGet(prop, fallback) {
|
||||||
|
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion safeExtGet('compileSdkVersion', 28)
|
||||||
|
buildToolsVersion safeExtGet('buildToolsVersion', '28.0.3')
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion safeExtGet('minSdkVersion', 16)
|
||||||
|
targetSdkVersion safeExtGet('targetSdkVersion', 28)
|
||||||
|
versionCode 1
|
||||||
|
versionName "1.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation "com.facebook.react:react-native:${safeExtGet('reactNativeVersion', '+')}"
|
||||||
|
implementation('com.google.android.exoplayer:exoplayer:2.11.4') {
|
||||||
|
exclude group: 'com.android.support'
|
||||||
|
}
|
||||||
|
|
||||||
|
// All support libs must use the same version
|
||||||
|
implementation "androidx.annotation:annotation:1.1.0"
|
||||||
|
implementation "androidx.core:core:1.1.0"
|
||||||
|
implementation "androidx.media:media:1.1.0"
|
||||||
|
|
||||||
|
implementation('com.google.android.exoplayer:extension-okhttp:2.11.4') {
|
||||||
|
exclude group: 'com.squareup.okhttp3', module: 'okhttp'
|
||||||
|
}
|
||||||
|
implementation 'com.google.android.exoplayer:extension-ima:2.11.4'
|
||||||
|
|
||||||
|
implementation 'com.squareup.okhttp3:okhttp:${OKHTTP_VERSION}'
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* If one wants to open this module in Android studio. Uncomment these repositories and buildscript parts*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
repositories {
|
||||||
|
maven {
|
||||||
|
url 'https://maven.google.com/'
|
||||||
|
name 'Google'
|
||||||
|
}
|
||||||
|
jcenter()
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
maven {
|
||||||
|
url 'https://maven.google.com/'
|
||||||
|
name 'Google'
|
||||||
|
}
|
||||||
|
jcenter()
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.android.tools.build:gradle:3.5.3'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
@ -0,0 +1,319 @@
|
|||||||
|
package com.brentvatne.exoplayer;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.content.Context;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.SurfaceView;
|
||||||
|
import android.view.TextureView;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.C;
|
||||||
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
|
import com.google.android.exoplayer2.ExoPlayer;
|
||||||
|
import com.google.android.exoplayer2.PlaybackParameters;
|
||||||
|
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||||
|
import com.google.android.exoplayer2.Timeline;
|
||||||
|
import com.google.android.exoplayer2.source.ads.AdsLoader;
|
||||||
|
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||||
|
import com.google.android.exoplayer2.text.Cue;
|
||||||
|
import com.google.android.exoplayer2.text.TextRenderer;
|
||||||
|
import com.google.android.exoplayer2.text.TextOutput;
|
||||||
|
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||||
|
import com.google.android.exoplayer2.ui.SubtitleView;
|
||||||
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
@TargetApi(16)
|
||||||
|
public final class ExoPlayerView extends FrameLayout implements AdsLoader.AdViewProvider {
|
||||||
|
|
||||||
|
private View surfaceView;
|
||||||
|
private final View shutterView;
|
||||||
|
private final SubtitleView subtitleLayout;
|
||||||
|
private final AspectRatioFrameLayout layout;
|
||||||
|
private final ComponentListener componentListener;
|
||||||
|
private SimpleExoPlayer player;
|
||||||
|
private Context context;
|
||||||
|
private ViewGroup.LayoutParams layoutParams;
|
||||||
|
private final FrameLayout adOverlayFrameLayout;
|
||||||
|
|
||||||
|
private boolean useTextureView = true;
|
||||||
|
private boolean hideShutterView = false;
|
||||||
|
|
||||||
|
public ExoPlayerView(Context context) {
|
||||||
|
this(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExoPlayerView(Context context, AttributeSet attrs) {
|
||||||
|
this(context, attrs, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExoPlayerView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
|
||||||
|
this.context = context;
|
||||||
|
|
||||||
|
layoutParams = new ViewGroup.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT);
|
||||||
|
|
||||||
|
componentListener = new ComponentListener();
|
||||||
|
|
||||||
|
FrameLayout.LayoutParams aspectRatioParams = new FrameLayout.LayoutParams(
|
||||||
|
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
FrameLayout.LayoutParams.MATCH_PARENT);
|
||||||
|
aspectRatioParams.gravity = Gravity.CENTER;
|
||||||
|
layout = new AspectRatioFrameLayout(context);
|
||||||
|
layout.setLayoutParams(aspectRatioParams);
|
||||||
|
|
||||||
|
shutterView = new View(getContext());
|
||||||
|
shutterView.setLayoutParams(layoutParams);
|
||||||
|
shutterView.setBackgroundColor(ContextCompat.getColor(context, android.R.color.black));
|
||||||
|
|
||||||
|
subtitleLayout = new SubtitleView(context);
|
||||||
|
subtitleLayout.setLayoutParams(layoutParams);
|
||||||
|
subtitleLayout.setUserDefaultStyle();
|
||||||
|
subtitleLayout.setUserDefaultTextSize();
|
||||||
|
|
||||||
|
updateSurfaceView();
|
||||||
|
|
||||||
|
layout.addView(shutterView, 1, layoutParams);
|
||||||
|
layout.addView(subtitleLayout, 2, layoutParams);
|
||||||
|
|
||||||
|
adOverlayFrameLayout = new FrameLayout(context);
|
||||||
|
|
||||||
|
addViewInLayout(layout, 0, aspectRatioParams);
|
||||||
|
addViewInLayout(adOverlayFrameLayout, 1, layoutParams);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setVideoView() {
|
||||||
|
if (surfaceView instanceof TextureView) {
|
||||||
|
player.setVideoTextureView((TextureView) surfaceView);
|
||||||
|
} else if (surfaceView instanceof SurfaceView) {
|
||||||
|
player.setVideoSurfaceView((SurfaceView) surfaceView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateSurfaceView() {
|
||||||
|
View view = useTextureView ? new TextureView(context) : new SurfaceView(context);
|
||||||
|
view.setLayoutParams(layoutParams);
|
||||||
|
|
||||||
|
surfaceView = view;
|
||||||
|
if (layout.getChildAt(0) != null) {
|
||||||
|
layout.removeViewAt(0);
|
||||||
|
}
|
||||||
|
layout.addView(surfaceView, 0, layoutParams);
|
||||||
|
|
||||||
|
if (this.player != null) {
|
||||||
|
setVideoView();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateShutterViewVisibility() {
|
||||||
|
shutterView.setVisibility(this.hideShutterView ? View.INVISIBLE : View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void requestLayout() {
|
||||||
|
super.requestLayout();
|
||||||
|
post(measureAndLayout);
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdsLoader.AdViewProvider implementation.
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ViewGroup getAdViewGroup() {
|
||||||
|
return Assertions.checkNotNull(adOverlayFrameLayout, "exo_ad_overlay must be present for ad playback");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View[] getAdOverlayViews() {
|
||||||
|
ArrayList<View> overlayViews = new ArrayList<>();
|
||||||
|
if (adOverlayFrameLayout != null) {
|
||||||
|
overlayViews.add(adOverlayFrameLayout);
|
||||||
|
}
|
||||||
|
return overlayViews.toArray(new View[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the {@link SimpleExoPlayer} to use. The {@link SimpleExoPlayer#setTextOutput} and
|
||||||
|
* {@link SimpleExoPlayer#setVideoListener} method of the player will be called and previous
|
||||||
|
* assignments are overridden.
|
||||||
|
*
|
||||||
|
* @param player The {@link SimpleExoPlayer} to use.
|
||||||
|
*/
|
||||||
|
public void setPlayer(SimpleExoPlayer player) {
|
||||||
|
if (this.player == player) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.player != null) {
|
||||||
|
this.player.setTextOutput(null);
|
||||||
|
this.player.setVideoListener(null);
|
||||||
|
this.player.removeListener(componentListener);
|
||||||
|
this.player.setVideoSurface(null);
|
||||||
|
}
|
||||||
|
this.player = player;
|
||||||
|
shutterView.setVisibility(VISIBLE);
|
||||||
|
if (player != null) {
|
||||||
|
setVideoView();
|
||||||
|
player.setVideoListener(componentListener);
|
||||||
|
player.addListener(componentListener);
|
||||||
|
player.setTextOutput(componentListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the resize mode which can be of value {@link ResizeMode.Mode}
|
||||||
|
*
|
||||||
|
* @param resizeMode The resize mode.
|
||||||
|
*/
|
||||||
|
public void setResizeMode(@ResizeMode.Mode int resizeMode) {
|
||||||
|
if (layout.getResizeMode() != resizeMode) {
|
||||||
|
layout.setResizeMode(resizeMode);
|
||||||
|
post(measureAndLayout);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the view onto which video is rendered. This is either a {@link SurfaceView} (default)
|
||||||
|
* or a {@link TextureView} if the {@code use_texture_view} view attribute has been set to true.
|
||||||
|
*
|
||||||
|
* @return either a {@link SurfaceView} or a {@link TextureView}.
|
||||||
|
*/
|
||||||
|
public View getVideoSurfaceView() {
|
||||||
|
return surfaceView;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUseTextureView(boolean useTextureView) {
|
||||||
|
if (useTextureView != this.useTextureView) {
|
||||||
|
this.useTextureView = useTextureView;
|
||||||
|
updateSurfaceView();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHideShutterView(boolean hideShutterView) {
|
||||||
|
this.hideShutterView = hideShutterView;
|
||||||
|
updateShutterViewVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Runnable measureAndLayout = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
measure(
|
||||||
|
MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY),
|
||||||
|
MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY));
|
||||||
|
layout(getLeft(), getTop(), getRight(), getBottom());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private void updateForCurrentTrackSelections() {
|
||||||
|
if (player == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TrackSelectionArray selections = player.getCurrentTrackSelections();
|
||||||
|
for (int i = 0; i < selections.length; i++) {
|
||||||
|
if (player.getRendererType(i) == C.TRACK_TYPE_VIDEO && selections.get(i) != null) {
|
||||||
|
// Video enabled so artwork must be hidden. If the shutter is closed, it will be opened in
|
||||||
|
// onRenderedFirstFrame().
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Video disabled so the shutter must be closed.
|
||||||
|
shutterView.setVisibility(VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void invalidateAspectRatio() {
|
||||||
|
// Resetting aspect ratio will force layout refresh on next video size changed
|
||||||
|
layout.invalidateAspectRatio();
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class ComponentListener implements SimpleExoPlayer.VideoListener,
|
||||||
|
TextOutput, ExoPlayer.EventListener {
|
||||||
|
|
||||||
|
// TextRenderer.Output implementation
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCues(List<Cue> cues) {
|
||||||
|
subtitleLayout.onCues(cues);
|
||||||
|
}
|
||||||
|
|
||||||
|
// SimpleExoPlayer.VideoListener implementation
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {
|
||||||
|
boolean isInitialRatio = layout.getAspectRatio() == 0;
|
||||||
|
layout.setAspectRatio(height == 0 ? 1 : (width * pixelWidthHeightRatio) / height);
|
||||||
|
|
||||||
|
// React native workaround for measuring and layout on initial load.
|
||||||
|
if (isInitialRatio) {
|
||||||
|
post(measureAndLayout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRenderedFirstFrame() {
|
||||||
|
shutterView.setVisibility(INVISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExoPlayer.EventListener implementation
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoadingChanged(boolean isLoading) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPlayerError(ExoPlaybackException e) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPositionDiscontinuity(int reason) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTimelineChanged(Timeline timeline, Object manifest, int reason) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
|
||||||
|
updateForCurrentTrackSelections();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPlaybackParametersChanged(PlaybackParameters params) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSeekProcessed() {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRepeatModeChanged(int repeatMode) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -31,6 +31,7 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
|
|||||||
|
|
||||||
private static final String PROP_SRC = "src";
|
private static final String PROP_SRC = "src";
|
||||||
private static final String PROP_SRC_URI = "uri";
|
private static final String PROP_SRC_URI = "uri";
|
||||||
|
private static final String PROP_AD_TAG_URL = "adTagUrl";
|
||||||
private static final String PROP_SRC_TYPE = "type";
|
private static final String PROP_SRC_TYPE = "type";
|
||||||
private static final String PROP_DRM = "drm";
|
private static final String PROP_DRM = "drm";
|
||||||
private static final String PROP_DRM_TYPE = "type";
|
private static final String PROP_DRM_TYPE = "type";
|
||||||
@ -189,6 +190,23 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ReactProp(name = PROP_AD_TAG_URL)
|
||||||
|
public void setAdTagUrl(final ReactExoplayerView videoView, final String uriString) {
|
||||||
|
if (TextUtils.isEmpty(uriString)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startsWithValidScheme(uriString)) {
|
||||||
|
Uri adTagUrl = Uri.parse(uriString);
|
||||||
|
|
||||||
|
if (adTagUrl != null) {
|
||||||
|
videoView.setAdTagUrl(adTagUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@ReactProp(name = PROP_RESIZE_MODE)
|
@ReactProp(name = PROP_RESIZE_MODE)
|
||||||
public void setResizeMode(final ReactExoplayerView videoView, final String resizeModeOrdinalString) {
|
public void setResizeMode(final ReactExoplayerView videoView, final String resizeModeOrdinalString) {
|
||||||
videoView.setResizeModeModifier(convertToIntDef(resizeModeOrdinalString));
|
videoView.setResizeModeModifier(convertToIntDef(resizeModeOrdinalString));
|
||||||
|
@ -50,6 +50,7 @@ class VideoEventEmitter {
|
|||||||
private static final String EVENT_AUDIO_BECOMING_NOISY = "onVideoAudioBecomingNoisy";
|
private static final String EVENT_AUDIO_BECOMING_NOISY = "onVideoAudioBecomingNoisy";
|
||||||
private static final String EVENT_AUDIO_FOCUS_CHANGE = "onAudioFocusChanged";
|
private static final String EVENT_AUDIO_FOCUS_CHANGE = "onAudioFocusChanged";
|
||||||
private static final String EVENT_PLAYBACK_RATE_CHANGE = "onPlaybackRateChange";
|
private static final String EVENT_PLAYBACK_RATE_CHANGE = "onPlaybackRateChange";
|
||||||
|
private static final String EVENT_ON_RECEIVE_AD_EVENT = "onReceiveAdEvent";
|
||||||
|
|
||||||
static final String[] Events = {
|
static final String[] Events = {
|
||||||
EVENT_LOAD_START,
|
EVENT_LOAD_START,
|
||||||
@ -73,6 +74,7 @@ class VideoEventEmitter {
|
|||||||
EVENT_AUDIO_FOCUS_CHANGE,
|
EVENT_AUDIO_FOCUS_CHANGE,
|
||||||
EVENT_PLAYBACK_RATE_CHANGE,
|
EVENT_PLAYBACK_RATE_CHANGE,
|
||||||
EVENT_BANDWIDTH,
|
EVENT_BANDWIDTH,
|
||||||
|
EVENT_ON_RECEIVE_AD_EVENT
|
||||||
};
|
};
|
||||||
|
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
@ -98,6 +100,7 @@ class VideoEventEmitter {
|
|||||||
EVENT_AUDIO_FOCUS_CHANGE,
|
EVENT_AUDIO_FOCUS_CHANGE,
|
||||||
EVENT_PLAYBACK_RATE_CHANGE,
|
EVENT_PLAYBACK_RATE_CHANGE,
|
||||||
EVENT_BANDWIDTH,
|
EVENT_BANDWIDTH,
|
||||||
|
EVENT_ON_RECEIVE_AD_EVENT
|
||||||
})
|
})
|
||||||
@interface VideoEvents {
|
@interface VideoEvents {
|
||||||
}
|
}
|
||||||
@ -330,6 +333,13 @@ class VideoEventEmitter {
|
|||||||
receiveEvent(EVENT_AUDIO_BECOMING_NOISY, null);
|
receiveEvent(EVENT_AUDIO_BECOMING_NOISY, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void receiveAdEvent(String event) {
|
||||||
|
WritableMap map = Arguments.createMap();
|
||||||
|
map.putString("event", event);
|
||||||
|
|
||||||
|
receiveEvent(EVENT_ON_RECEIVE_AD_EVENT, map);
|
||||||
|
}
|
||||||
|
|
||||||
private void receiveEvent(@VideoEvents String type, WritableMap event) {
|
private void receiveEvent(@VideoEvents String type, WritableMap event) {
|
||||||
eventEmitter.receiveEvent(viewId, type, event);
|
eventEmitter.receiveEvent(viewId, type, event);
|
||||||
}
|
}
|
||||||
|
236
examples/basic/index.android.js
Normal file
236
examples/basic/index.android.js
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
import React, {
|
||||||
|
Component
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
AppRegistry,
|
||||||
|
StyleSheet,
|
||||||
|
Text,
|
||||||
|
TouchableOpacity,
|
||||||
|
View,
|
||||||
|
} from 'react-native';
|
||||||
|
|
||||||
|
import Video from 'react-native-video';
|
||||||
|
|
||||||
|
const adTagUrl = "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/"
|
||||||
|
+ "ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp"
|
||||||
|
+ "&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite"
|
||||||
|
+ "%26sample_ar%3Dpremidpost&cmsid=496&vid=short_onecue&correlator=";
|
||||||
|
|
||||||
|
class VideoPlayer extends Component {
|
||||||
|
|
||||||
|
state = {
|
||||||
|
rate: 1,
|
||||||
|
volume: 1,
|
||||||
|
muted: false,
|
||||||
|
resizeMode: 'contain',
|
||||||
|
duration: 0.0,
|
||||||
|
currentTime: 0.0,
|
||||||
|
paused: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
video: Video;
|
||||||
|
|
||||||
|
onLoad = (data) => {
|
||||||
|
this.setState({ duration: data.duration });
|
||||||
|
};
|
||||||
|
|
||||||
|
onProgress = (data) => {
|
||||||
|
this.setState({ currentTime: data.currentTime });
|
||||||
|
};
|
||||||
|
|
||||||
|
onEnd = () => {
|
||||||
|
this.setState({ paused: true })
|
||||||
|
this.video.seek(0)
|
||||||
|
};
|
||||||
|
|
||||||
|
onAudioBecomingNoisy = () => {
|
||||||
|
this.setState({ paused: true })
|
||||||
|
};
|
||||||
|
|
||||||
|
onAudioFocusChanged = (event: { hasAudioFocus: boolean }) => {
|
||||||
|
this.setState({ paused: !event.hasAudioFocus })
|
||||||
|
};
|
||||||
|
|
||||||
|
getCurrentTimePercentage() {
|
||||||
|
if (this.state.currentTime > 0) {
|
||||||
|
return parseFloat(this.state.currentTime) / parseFloat(this.state.duration);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
renderRateControl(rate) {
|
||||||
|
const isSelected = (this.state.rate === rate);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TouchableOpacity onPress={() => { this.setState({ 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 }) }}>
|
||||||
|
<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 }) }}>
|
||||||
|
<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
|
||||||
|
ref={(ref: Video) => { this.video = ref }}
|
||||||
|
/* For ExoPlayer */
|
||||||
|
/* source={{ uri: 'http://www.youtube.com/api/manifest/dash/id/bf5bb2419360daf1/source/youtube?as=fmp4_audio_clear,fmp4_sd_hd_clear&sparams=ip,ipbits,expire,source,id,as&ip=0.0.0.0&ipbits=0&expire=19000000000&signature=51AF5F39AB0CEC3E5497CD9C900EBFEAECCCB5C7.8506521BFC350652163895D4C26DEE124209AA9E&key=ik0', type: 'mpd' }} */
|
||||||
|
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={this.onEnd}
|
||||||
|
onAudioBecomingNoisy={this.onAudioBecomingNoisy}
|
||||||
|
onAudioFocusChanged={this.onAudioFocusChanged}
|
||||||
|
repeat={false}
|
||||||
|
adTagUrl={adTagUrl}
|
||||||
|
/>
|
||||||
|
</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);
|
455
examples/basic/index.ios.js
Normal file
455
examples/basic/index.ios.js
Normal file
@ -0,0 +1,455 @@
|
|||||||
|
'use strict';
|
||||||
|
import React, {
|
||||||
|
Component
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Alert,
|
||||||
|
AppRegistry,
|
||||||
|
Platform,
|
||||||
|
StyleSheet,
|
||||||
|
Text,
|
||||||
|
TouchableOpacity,
|
||||||
|
View,
|
||||||
|
} from 'react-native';
|
||||||
|
|
||||||
|
import Video,{FilterType} from 'react-native-video';
|
||||||
|
|
||||||
|
const adTagUrl = "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/"
|
||||||
|
+ "ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp"
|
||||||
|
+ "&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite"
|
||||||
|
+ "%26sample_ar%3Dpremidpost&cmsid=496&vid=short_onecue&correlator=";
|
||||||
|
|
||||||
|
const filterTypes = [
|
||||||
|
FilterType.NONE,
|
||||||
|
FilterType.INVERT,
|
||||||
|
FilterType.MONOCHROME,
|
||||||
|
FilterType.POSTERIZE,
|
||||||
|
FilterType.FALSE,
|
||||||
|
FilterType.MAXIMUMCOMPONENT,
|
||||||
|
FilterType.MINIMUMCOMPONENT,
|
||||||
|
FilterType.CHROME,
|
||||||
|
FilterType.FADE,
|
||||||
|
FilterType.INSTANT,
|
||||||
|
FilterType.MONO,
|
||||||
|
FilterType.NOIR,
|
||||||
|
FilterType.PROCESS,
|
||||||
|
FilterType.TONAL,
|
||||||
|
FilterType.TRANSFER,
|
||||||
|
FilterType.SEPIA
|
||||||
|
];
|
||||||
|
|
||||||
|
class VideoPlayer extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.onLoad = this.onLoad.bind(this);
|
||||||
|
this.onProgress = this.onProgress.bind(this);
|
||||||
|
this.onBuffer = this.onBuffer.bind(this);
|
||||||
|
}
|
||||||
|
state = {
|
||||||
|
rate: 1,
|
||||||
|
volume: 1,
|
||||||
|
muted: false,
|
||||||
|
resizeMode: 'contain',
|
||||||
|
duration: 0.0,
|
||||||
|
currentTime: 0.0,
|
||||||
|
controls: false,
|
||||||
|
paused: true,
|
||||||
|
skin: 'custom',
|
||||||
|
ignoreSilentSwitch: null,
|
||||||
|
mixWithOthers: null,
|
||||||
|
isBuffering: false,
|
||||||
|
filter: FilterType.NONE,
|
||||||
|
filterEnabled: true
|
||||||
|
};
|
||||||
|
|
||||||
|
onLoad(data) {
|
||||||
|
console.log('On load fired!');
|
||||||
|
this.setState({duration: data.duration});
|
||||||
|
}
|
||||||
|
|
||||||
|
onProgress(data) {
|
||||||
|
this.setState({currentTime: data.currentTime});
|
||||||
|
}
|
||||||
|
|
||||||
|
onBuffer({ isBuffering }: { isBuffering: boolean }) {
|
||||||
|
this.setState({ isBuffering });
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentTimePercentage() {
|
||||||
|
if (this.state.currentTime > 0) {
|
||||||
|
return parseFloat(this.state.currentTime) / parseFloat(this.state.duration);
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFilter(step) {
|
||||||
|
let index = filterTypes.indexOf(this.state.filter) + step;
|
||||||
|
|
||||||
|
if (index === filterTypes.length) {
|
||||||
|
index = 0;
|
||||||
|
} else if (index === -1) {
|
||||||
|
index = filterTypes.length - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
filter: filterTypes[index]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
renderSkinControl(skin) {
|
||||||
|
const isSelected = this.state.skin == skin;
|
||||||
|
const selectControls = skin == 'native' || skin == 'embed';
|
||||||
|
return (
|
||||||
|
<TouchableOpacity onPress={() => { this.setState({
|
||||||
|
controls: selectControls,
|
||||||
|
skin: skin
|
||||||
|
}) }}>
|
||||||
|
<Text style={[styles.controlOption, {fontWeight: isSelected ? "bold" : "normal"}]}>
|
||||||
|
{skin}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderIgnoreSilentSwitchControl(ignoreSilentSwitch) {
|
||||||
|
const isSelected = (this.state.ignoreSilentSwitch == ignoreSilentSwitch);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TouchableOpacity onPress={() => { this.setState({ignoreSilentSwitch: ignoreSilentSwitch}) }}>
|
||||||
|
<Text style={[styles.controlOption, {fontWeight: isSelected ? "bold" : "normal"}]}>
|
||||||
|
{ignoreSilentSwitch}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderMixWithOthersControl(mixWithOthers) {
|
||||||
|
const isSelected = (this.state.mixWithOthers == mixWithOthers);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TouchableOpacity onPress={() => { this.setState({mixWithOthers: mixWithOthers}) }}>
|
||||||
|
<Text style={[styles.controlOption, {fontWeight: isSelected ? "bold" : "normal"}]}>
|
||||||
|
{mixWithOthers}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
renderCustomSkin() {
|
||||||
|
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}
|
||||||
|
ignoreSilentSwitch={this.state.ignoreSilentSwitch}
|
||||||
|
mixWithOthers={this.state.mixWithOthers}
|
||||||
|
resizeMode={this.state.resizeMode}
|
||||||
|
onLoad={this.onLoad}
|
||||||
|
onBuffer={this.onBuffer}
|
||||||
|
onProgress={this.onProgress}
|
||||||
|
onEnd={() => { Alert.alert('Done!') }}
|
||||||
|
repeat={true}
|
||||||
|
filter={this.state.filter}
|
||||||
|
filterEnabled={this.state.filterEnabled}
|
||||||
|
/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<View style={styles.controls}>
|
||||||
|
<View style={styles.generalControls}>
|
||||||
|
<View style={styles.skinControl}>
|
||||||
|
{this.renderSkinControl('custom')}
|
||||||
|
{this.renderSkinControl('native')}
|
||||||
|
{this.renderSkinControl('embed')}
|
||||||
|
</View>
|
||||||
|
{
|
||||||
|
(this.state.filterEnabled) ?
|
||||||
|
<View style={styles.skinControl}>
|
||||||
|
<TouchableOpacity onPress={() => {
|
||||||
|
this.setFilter(-1)
|
||||||
|
}}>
|
||||||
|
<Text style={styles.controlOption}>Previous Filter</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity onPress={() => {
|
||||||
|
this.setFilter(1)
|
||||||
|
}}>
|
||||||
|
<Text style={styles.controlOption}>Next Filter</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View> : null
|
||||||
|
}
|
||||||
|
</View>
|
||||||
|
<View style={styles.generalControls}>
|
||||||
|
<View style={styles.rateControl}>
|
||||||
|
{this.renderRateControl(0.5)}
|
||||||
|
{this.renderRateControl(1.0)}
|
||||||
|
{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.generalControls}>
|
||||||
|
{
|
||||||
|
(Platform.OS === 'ios') ?
|
||||||
|
<>
|
||||||
|
<View style={styles.ignoreSilentSwitchControl}>
|
||||||
|
{this.renderIgnoreSilentSwitchControl('ignore')}
|
||||||
|
{this.renderIgnoreSilentSwitchControl('obey')}
|
||||||
|
</View>
|
||||||
|
<View style={styles.mixWithOthersControl}>
|
||||||
|
{this.renderMixWithOthersControl('mix')}
|
||||||
|
{this.renderMixWithOthersControl('duck')}
|
||||||
|
</View>
|
||||||
|
</> : null
|
||||||
|
}
|
||||||
|
</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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderNativeSkin() {
|
||||||
|
const videoStyle = this.state.skin == 'embed' ? styles.nativeVideoControls : styles.fullScreen;
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<View style={styles.fullScreen}>
|
||||||
|
<Video
|
||||||
|
source={require('./broadchurch.mp4')}
|
||||||
|
style={videoStyle}
|
||||||
|
rate={this.state.rate}
|
||||||
|
paused={this.state.paused}
|
||||||
|
volume={this.state.volume}
|
||||||
|
muted={this.state.muted}
|
||||||
|
ignoreSilentSwitch={this.state.ignoreSilentSwitch}
|
||||||
|
mixWithOthers={this.state.mixWithOthers}
|
||||||
|
resizeMode={this.state.resizeMode}
|
||||||
|
onLoad={this.onLoad}
|
||||||
|
onBuffer={this.onBuffer}
|
||||||
|
onProgress={this.onProgress}
|
||||||
|
onEnd={() => { Alert.alert('Done!') }}
|
||||||
|
repeat={true}
|
||||||
|
controls={this.state.controls}
|
||||||
|
filter={this.state.filter}
|
||||||
|
filterEnabled={this.state.filterEnabled}
|
||||||
|
adTagUrl={adTagUrl}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View style={styles.controls}>
|
||||||
|
<View style={styles.generalControls}>
|
||||||
|
<View style={styles.skinControl}>
|
||||||
|
{this.renderSkinControl('custom')}
|
||||||
|
{this.renderSkinControl('native')}
|
||||||
|
{this.renderSkinControl('embed')}
|
||||||
|
</View>
|
||||||
|
{
|
||||||
|
(this.state.filterEnabled) ?
|
||||||
|
<View style={styles.skinControl}>
|
||||||
|
<TouchableOpacity onPress={() => {
|
||||||
|
this.setFilter(-1)
|
||||||
|
}}>
|
||||||
|
<Text style={styles.controlOption}>Previous Filter</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity onPress={() => {
|
||||||
|
this.setFilter(1)
|
||||||
|
}}>
|
||||||
|
<Text style={styles.controlOption}>Next Filter</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View> : null
|
||||||
|
}
|
||||||
|
</View>
|
||||||
|
<View style={styles.generalControls}>
|
||||||
|
<View style={styles.rateControl}>
|
||||||
|
{this.renderRateControl(0.5)}
|
||||||
|
{this.renderRateControl(1.0)}
|
||||||
|
{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.generalControls}>
|
||||||
|
{
|
||||||
|
(Platform.OS === 'ios') ?
|
||||||
|
<>
|
||||||
|
<View style={styles.ignoreSilentSwitchControl}>
|
||||||
|
{this.renderIgnoreSilentSwitchControl('ignore')}
|
||||||
|
{this.renderIgnoreSilentSwitchControl('obey')}
|
||||||
|
</View>
|
||||||
|
<View style={styles.mixWithOthersControl}>
|
||||||
|
{this.renderMixWithOthersControl('mix')}
|
||||||
|
{this.renderMixWithOthersControl('duck')}
|
||||||
|
</View>
|
||||||
|
</> : null
|
||||||
|
}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return this.state.controls ? this.renderNativeSkin() : this.renderCustomSkin();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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: 44,
|
||||||
|
left: 4,
|
||||||
|
right: 4,
|
||||||
|
},
|
||||||
|
progress: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
borderRadius: 3,
|
||||||
|
overflow: 'hidden',
|
||||||
|
},
|
||||||
|
innerProgressCompleted: {
|
||||||
|
height: 20,
|
||||||
|
backgroundColor: '#cccccc',
|
||||||
|
},
|
||||||
|
innerProgressRemaining: {
|
||||||
|
height: 20,
|
||||||
|
backgroundColor: '#2C2C2C',
|
||||||
|
},
|
||||||
|
generalControls: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
overflow: 'hidden',
|
||||||
|
paddingBottom: 10,
|
||||||
|
},
|
||||||
|
skinControl: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
rateControl: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
volumeControl: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
resizeModeControl: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center'
|
||||||
|
},
|
||||||
|
ignoreSilentSwitchControl: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center'
|
||||||
|
},
|
||||||
|
mixWithOthersControl: {
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center'
|
||||||
|
},
|
||||||
|
controlOption: {
|
||||||
|
alignSelf: 'center',
|
||||||
|
fontSize: 11,
|
||||||
|
color: "white",
|
||||||
|
paddingLeft: 2,
|
||||||
|
paddingRight: 2,
|
||||||
|
lineHeight: 12,
|
||||||
|
},
|
||||||
|
nativeVideoControls: {
|
||||||
|
top: 184,
|
||||||
|
height: 300
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
AppRegistry.registerComponent('VideoPlayer', () => VideoPlayer);
|
@ -319,6 +319,7 @@
|
|||||||
developmentRegion = en;
|
developmentRegion = en;
|
||||||
hasScannedForEncodings = 0;
|
hasScannedForEncodings = 0;
|
||||||
knownRegions = (
|
knownRegions = (
|
||||||
|
English,
|
||||||
en,
|
en,
|
||||||
Base,
|
Base,
|
||||||
);
|
);
|
||||||
|
15
examples/basic/react-native.config.js
Normal file
15
examples/basic/react-native.config.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
module.exports = {
|
||||||
|
reactNativePath: fs.realpathSync(path.resolve(require.resolve('react-native-windows/package.json'), '..')),
|
||||||
|
dependencies: {
|
||||||
|
'react-native-video-inc-ads': {
|
||||||
|
platforms: {
|
||||||
|
android: {
|
||||||
|
sourceDir:
|
||||||
|
'../node_modules/react-native-video-inc-ads/android-exoplayer',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
@ -237,7 +237,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 7.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = YES;
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
@ -271,7 +271,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 7.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
VALIDATE_PRODUCT = YES;
|
VALIDATE_PRODUCT = YES;
|
||||||
|
77
ios/Video/RCTVideo.h
Normal file
77
ios/Video/RCTVideo.h
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
#import <AVFoundation/AVFoundation.h>
|
||||||
|
#import "AVKit/AVKit.h"
|
||||||
|
#import "UIView+FindUIViewController.h"
|
||||||
|
#import "RCTVideoPlayerViewController.h"
|
||||||
|
#import "RCTVideoPlayerViewControllerDelegate.h"
|
||||||
|
#import <React/RCTComponent.h>
|
||||||
|
#import <React/RCTBridgeModule.h>
|
||||||
|
@import GoogleInteractiveMediaAds;
|
||||||
|
|
||||||
|
#if __has_include(<react-native-video/RCTVideoCache.h>)
|
||||||
|
#import <react-native-video/RCTVideoCache.h>
|
||||||
|
#import <DVAssetLoaderDelegate/DVURLAsset.h>
|
||||||
|
#import <DVAssetLoaderDelegate/DVAssetLoaderDelegate.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
@class RCTEventDispatcher;
|
||||||
|
#if __has_include(<react-native-video/RCTVideoCache.h>)
|
||||||
|
@interface RCTVideo : UIView <RCTVideoPlayerViewControllerDelegate, DVAssetLoaderDelegatesDelegate, AVAssetResourceLoaderDelegate, IMAAdsLoaderDelegate, IMAAdsManagerDelegate>
|
||||||
|
#elif TARGET_OS_TV
|
||||||
|
@interface RCTVideo : UIView <RCTVideoPlayerViewControllerDelegate, AVAssetResourceLoaderDelegate>
|
||||||
|
#else
|
||||||
|
@interface RCTVideo : UIView <RCTVideoPlayerViewControllerDelegate, AVPictureInPictureControllerDelegate, AVAssetResourceLoaderDelegate, IMAAdsLoaderDelegate, IMAAdsManagerDelegate>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
@property (nonatomic, copy) RCTDirectEventBlock onVideoLoadStart;
|
||||||
|
@property (nonatomic, copy) RCTDirectEventBlock onVideoLoad;
|
||||||
|
@property (nonatomic, copy) RCTDirectEventBlock onVideoBuffer;
|
||||||
|
@property (nonatomic, copy) RCTDirectEventBlock onVideoError;
|
||||||
|
@property (nonatomic, copy) RCTDirectEventBlock onVideoProgress;
|
||||||
|
@property (nonatomic, copy) RCTDirectEventBlock onBandwidthUpdate;
|
||||||
|
@property (nonatomic, copy) RCTDirectEventBlock onVideoSeek;
|
||||||
|
@property (nonatomic, copy) RCTDirectEventBlock onVideoEnd;
|
||||||
|
@property (nonatomic, copy) RCTDirectEventBlock onTimedMetadata;
|
||||||
|
@property (nonatomic, copy) RCTDirectEventBlock onVideoAudioBecomingNoisy;
|
||||||
|
@property (nonatomic, copy) RCTDirectEventBlock onVideoFullscreenPlayerWillPresent;
|
||||||
|
@property (nonatomic, copy) RCTDirectEventBlock onVideoFullscreenPlayerDidPresent;
|
||||||
|
@property (nonatomic, copy) RCTDirectEventBlock onVideoFullscreenPlayerWillDismiss;
|
||||||
|
@property (nonatomic, copy) RCTDirectEventBlock onVideoFullscreenPlayerDidDismiss;
|
||||||
|
@property (nonatomic, copy) RCTDirectEventBlock onReadyForDisplay;
|
||||||
|
@property (nonatomic, copy) RCTDirectEventBlock onPlaybackStalled;
|
||||||
|
@property (nonatomic, copy) RCTDirectEventBlock onPlaybackResume;
|
||||||
|
@property (nonatomic, copy) RCTDirectEventBlock onPlaybackRateChange;
|
||||||
|
@property (nonatomic, copy) RCTDirectEventBlock onVideoExternalPlaybackChange;
|
||||||
|
@property (nonatomic, copy) RCTDirectEventBlock onPictureInPictureStatusChanged;
|
||||||
|
@property (nonatomic, copy) RCTDirectEventBlock onRestoreUserInterfaceForPictureInPictureStop;
|
||||||
|
@property (nonatomic, copy) RCTDirectEventBlock onGetLicense;
|
||||||
|
@property (nonatomic, copy) RCTDirectEventBlock onReceiveAdEvent;
|
||||||
|
|
||||||
|
typedef NS_ENUM(NSInteger, RCTVideoError) {
|
||||||
|
RCTVideoErrorFromJSPart,
|
||||||
|
RCTVideoErrorLicenseRequestNotOk,
|
||||||
|
RCTVideoErrorNoDataFromLicenseRequest,
|
||||||
|
RCTVideoErrorNoSPC,
|
||||||
|
RCTVideoErrorNoDataRequest,
|
||||||
|
RCTVideoErrorNoCertificateData,
|
||||||
|
RCTVideoErrorNoCertificateURL,
|
||||||
|
RCTVideoErrorNoFairplayDRM,
|
||||||
|
RCTVideoErrorNoDRMData
|
||||||
|
};
|
||||||
|
/// Playhead used by the SDK to track content video progress and insert mid-rolls.
|
||||||
|
@property(nonatomic, strong) IMAAVPlayerContentPlayhead *contentPlayhead;
|
||||||
|
/// Entry point for the SDK. Used to make ad requests.
|
||||||
|
@property(nonatomic, strong) IMAAdsLoader *adsLoader;
|
||||||
|
/// Main point of interaction with the SDK. Created by the SDK as the result of an ad request.
|
||||||
|
@property(nonatomic, strong) IMAAdsManager *adsManager;
|
||||||
|
|
||||||
|
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
|
||||||
|
|
||||||
|
- (AVPlayerViewController*)createPlayerViewController:(AVPlayer*)player withPlayerItem:(AVPlayerItem*)playerItem;
|
||||||
|
|
||||||
|
- (void)save:(NSDictionary *)options resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject;
|
||||||
|
- (void)setLicenseResult:(NSString * )license;
|
||||||
|
- (BOOL)setLicenseResultError:(NSString * )error;
|
||||||
|
|
||||||
|
+ (NSString *)convertEventToString:(IMAAdEventType)event;
|
||||||
|
|
||||||
|
@end
|
2174
ios/Video/RCTVideo.m
Normal file
2174
ios/Video/RCTVideo.m
Normal file
File diff suppressed because it is too large
Load Diff
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
RCT_EXPORT_VIEW_PROPERTY(src, NSDictionary);
|
RCT_EXPORT_VIEW_PROPERTY(src, NSDictionary);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(drm, NSDictionary);
|
RCT_EXPORT_VIEW_PROPERTY(drm, NSDictionary);
|
||||||
|
RCT_EXPORT_VIEW_PROPERTY(adTagUrl, NSString);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(maxBitRate, float);
|
RCT_EXPORT_VIEW_PROPERTY(maxBitRate, float);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(resizeMode, NSString);
|
RCT_EXPORT_VIEW_PROPERTY(resizeMode, NSString);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(repeat, BOOL);
|
RCT_EXPORT_VIEW_PROPERTY(repeat, BOOL);
|
||||||
@ -59,6 +60,7 @@ RCT_EXPORT_VIEW_PROPERTY(onVideoExternalPlaybackChange, RCTDirectEventBlock);
|
|||||||
RCT_EXPORT_VIEW_PROPERTY(onGetLicense, RCTDirectEventBlock);
|
RCT_EXPORT_VIEW_PROPERTY(onGetLicense, RCTDirectEventBlock);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(onPictureInPictureStatusChanged, RCTDirectEventBlock);
|
RCT_EXPORT_VIEW_PROPERTY(onPictureInPictureStatusChanged, RCTDirectEventBlock);
|
||||||
RCT_EXPORT_VIEW_PROPERTY(onRestoreUserInterfaceForPictureInPictureStop, RCTDirectEventBlock);
|
RCT_EXPORT_VIEW_PROPERTY(onRestoreUserInterfaceForPictureInPictureStop, RCTDirectEventBlock);
|
||||||
|
RCT_EXPORT_VIEW_PROPERTY(onReceiveAdEvent, RCTDirectEventBlock);
|
||||||
|
|
||||||
RCT_EXTERN_METHOD(save:(NSDictionary *)options
|
RCT_EXTERN_METHOD(save:(NSDictionary *)options
|
||||||
reactTag:(nonnull NSNumber *)reactTag
|
reactTag:(nonnull NSNumber *)reactTag
|
||||||
|
@ -16,6 +16,8 @@ Pod::Spec.new do |s|
|
|||||||
s.tvos.deployment_target = "9.0"
|
s.tvos.deployment_target = "9.0"
|
||||||
|
|
||||||
s.subspec "Video" do |ss|
|
s.subspec "Video" do |ss|
|
||||||
|
ss.dependency "GoogleAds-IMA-iOS-SDK", "~> 3.9"
|
||||||
|
|
||||||
ss.source_files = "ios/Video/**/*.{h,m,swift}"
|
ss.source_files = "ios/Video/**/*.{h,m,swift}"
|
||||||
ss.dependency "PromisesSwift"
|
ss.dependency "PromisesSwift"
|
||||||
end
|
end
|
||||||
|
Loading…
Reference in New Issue
Block a user