Implement Interactive Media Ads (IMA) SDK
This commit is contained in:
parent
0df667692b
commit
519355ea58
@ -26,6 +26,7 @@ dependencies {
|
|||||||
implementation('com.google.android.exoplayer:exoplayer:2.10.5') {
|
implementation('com.google.android.exoplayer:exoplayer:2.10.5') {
|
||||||
exclude group: 'com.android.support'
|
exclude group: 'com.android.support'
|
||||||
}
|
}
|
||||||
|
implementation 'com.google.android.exoplayer:extension-ima:2.10.5'
|
||||||
|
|
||||||
// All support libs must use the same version
|
// All support libs must use the same version
|
||||||
implementation "androidx.annotation:annotation:1.1.0"
|
implementation "androidx.annotation:annotation:1.1.0"
|
||||||
|
@ -18,16 +18,19 @@ import com.google.android.exoplayer2.ExoPlayer;
|
|||||||
import com.google.android.exoplayer2.PlaybackParameters;
|
import com.google.android.exoplayer2.PlaybackParameters;
|
||||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||||
import com.google.android.exoplayer2.Timeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
|
import com.google.android.exoplayer2.source.ads.AdsLoader;
|
||||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||||
import com.google.android.exoplayer2.text.Cue;
|
import com.google.android.exoplayer2.text.Cue;
|
||||||
import com.google.android.exoplayer2.text.TextRenderer;
|
import com.google.android.exoplayer2.text.TextRenderer;
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||||
import com.google.android.exoplayer2.ui.SubtitleView;
|
import com.google.android.exoplayer2.ui.SubtitleView;
|
||||||
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
@TargetApi(16)
|
@TargetApi(16)
|
||||||
public final class ExoPlayerView extends FrameLayout {
|
public final class ExoPlayerView extends FrameLayout implements AdsLoader.AdViewProvider {
|
||||||
|
|
||||||
private View surfaceView;
|
private View surfaceView;
|
||||||
private final View shutterView;
|
private final View shutterView;
|
||||||
@ -37,6 +40,7 @@ public final class ExoPlayerView extends FrameLayout {
|
|||||||
private SimpleExoPlayer player;
|
private SimpleExoPlayer player;
|
||||||
private Context context;
|
private Context context;
|
||||||
private ViewGroup.LayoutParams layoutParams;
|
private ViewGroup.LayoutParams layoutParams;
|
||||||
|
private final FrameLayout adOverlayFrameLayout;
|
||||||
|
|
||||||
private boolean useTextureView = true;
|
private boolean useTextureView = true;
|
||||||
private boolean hideShutterView = false;
|
private boolean hideShutterView = false;
|
||||||
@ -81,7 +85,11 @@ public final class ExoPlayerView extends FrameLayout {
|
|||||||
layout.addView(shutterView, 1, layoutParams);
|
layout.addView(shutterView, 1, layoutParams);
|
||||||
layout.addView(subtitleLayout, 2, layoutParams);
|
layout.addView(subtitleLayout, 2, layoutParams);
|
||||||
|
|
||||||
|
adOverlayFrameLayout = new FrameLayout(context);
|
||||||
|
|
||||||
addViewInLayout(layout, 0, aspectRatioParams);
|
addViewInLayout(layout, 0, aspectRatioParams);
|
||||||
|
addViewInLayout(adOverlayFrameLayout, 1, layoutParams);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setVideoView() {
|
private void setVideoView() {
|
||||||
@ -111,6 +119,31 @@ public final class ExoPlayerView extends FrameLayout {
|
|||||||
shutterView.setVisibility(this.hideShutterView ? View.INVISIBLE : View.VISIBLE);
|
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);
|
||||||
|
}
|
||||||
|
// if (controller != null) {
|
||||||
|
// overlayViews.add(controller);
|
||||||
|
// }
|
||||||
|
return overlayViews.toArray(new View[0]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the {@link SimpleExoPlayer} to use. The {@link SimpleExoPlayer#setTextOutput} and
|
* Set the {@link SimpleExoPlayer} to use. The {@link SimpleExoPlayer#setTextOutput} and
|
||||||
* {@link SimpleExoPlayer#setVideoListener} method of the player will be called and previous
|
* {@link SimpleExoPlayer#setVideoListener} method of the player will be called and previous
|
||||||
|
@ -63,9 +63,13 @@ import com.google.android.exoplayer2.upstream.DefaultAllocator;
|
|||||||
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
|
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.ext.ima.ImaAdsLoader;
|
||||||
|
import com.google.android.exoplayer2.source.ads.AdsMediaSource;
|
||||||
|
|
||||||
import java.net.CookieHandler;
|
import java.net.CookieHandler;
|
||||||
import java.net.CookieManager;
|
import java.net.CookieManager;
|
||||||
import java.net.CookiePolicy;
|
import java.net.CookiePolicy;
|
||||||
|
import java.net.URI;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -77,7 +81,8 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
BandwidthMeter.EventListener,
|
BandwidthMeter.EventListener,
|
||||||
BecomingNoisyListener,
|
BecomingNoisyListener,
|
||||||
AudioManager.OnAudioFocusChangeListener,
|
AudioManager.OnAudioFocusChangeListener,
|
||||||
MetadataOutput {
|
MetadataOutput,
|
||||||
|
AdsMediaSource.MediaSourceFactory {
|
||||||
|
|
||||||
private static final String TAG = "ReactExoplayerView";
|
private static final String TAG = "ReactExoplayerView";
|
||||||
|
|
||||||
@ -97,6 +102,7 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
private Player.EventListener eventListener;
|
private Player.EventListener eventListener;
|
||||||
|
|
||||||
private ExoPlayerView exoPlayerView;
|
private ExoPlayerView exoPlayerView;
|
||||||
|
private ImaAdsLoader adsLoader;
|
||||||
|
|
||||||
private DataSource.Factory mediaDataSourceFactory;
|
private DataSource.Factory mediaDataSourceFactory;
|
||||||
private SimpleExoPlayer player;
|
private SimpleExoPlayer player;
|
||||||
@ -139,6 +145,7 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
private Map<String, String> requestHeaders;
|
private Map<String, String> requestHeaders;
|
||||||
private boolean mReportBandwidth = false;
|
private boolean mReportBandwidth = false;
|
||||||
private boolean controls;
|
private boolean controls;
|
||||||
|
private Uri adTagUrl;
|
||||||
// \ End props
|
// \ End props
|
||||||
|
|
||||||
// React
|
// React
|
||||||
@ -155,6 +162,9 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
&& player.getPlaybackState() == Player.STATE_READY
|
&& player.getPlaybackState() == Player.STATE_READY
|
||||||
&& player.getPlayWhenReady()
|
&& player.getPlayWhenReady()
|
||||||
) {
|
) {
|
||||||
|
if (isPlayingAd()) {
|
||||||
|
playerControlView.hide();
|
||||||
|
}
|
||||||
long pos = player.getCurrentPosition();
|
long pos = player.getCurrentPosition();
|
||||||
long bufferedDuration = player.getBufferedPercentage() * player.getDuration() / 100;
|
long bufferedDuration = player.getBufferedPercentage() * player.getDuration() / 100;
|
||||||
eventEmitter.progressChanged(pos, bufferedDuration, player.getDuration());
|
eventEmitter.progressChanged(pos, bufferedDuration, player.getDuration());
|
||||||
@ -173,6 +183,8 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
this.config = config;
|
this.config = config;
|
||||||
this.bandwidthMeter = config.getBandwidthMeter();
|
this.bandwidthMeter = config.getBandwidthMeter();
|
||||||
|
|
||||||
|
adsLoader = new ImaAdsLoader(this.themedReactContext, Uri.EMPTY);
|
||||||
|
|
||||||
createViews();
|
createViews();
|
||||||
|
|
||||||
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
|
||||||
@ -182,6 +194,10 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
initializePlayer();
|
initializePlayer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isPlayingAd() {
|
||||||
|
return player != null && player.isPlayingAd() && player.getPlayWhenReady();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setId(int id) {
|
public void setId(int id) {
|
||||||
@ -288,8 +304,10 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
exoPlayerView.setOnClickListener(new OnClickListener() {
|
exoPlayerView.setOnClickListener(new OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
|
if (!isPlayingAd()) {
|
||||||
togglePlayerControlVisibility();
|
togglePlayerControlVisibility();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Invoking onPlayerStateChanged event for Player
|
// Invoking onPlayerStateChanged event for Player
|
||||||
@ -360,6 +378,7 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
trackSelector, defaultLoadControl, null, bandwidthMeter);
|
trackSelector, defaultLoadControl, null, bandwidthMeter);
|
||||||
player.addListener(self);
|
player.addListener(self);
|
||||||
player.addMetadataOutput(self);
|
player.addMetadataOutput(self);
|
||||||
|
adsLoader.setPlayer(player);
|
||||||
exoPlayerView.setPlayer(player);
|
exoPlayerView.setPlayer(player);
|
||||||
audioBecomingNoisyReceiver.setListener(self);
|
audioBecomingNoisyReceiver.setListener(self);
|
||||||
bandwidthMeter.addEventListener(new Handler(), self);
|
bandwidthMeter.addEventListener(new Handler(), self);
|
||||||
@ -372,11 +391,12 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
if (playerNeedsSource && srcUri != null) {
|
if (playerNeedsSource && srcUri != null) {
|
||||||
ArrayList<MediaSource> mediaSourceList = buildTextSources();
|
ArrayList<MediaSource> mediaSourceList = buildTextSources();
|
||||||
MediaSource videoSource = buildMediaSource(srcUri, extension);
|
MediaSource videoSource = buildMediaSource(srcUri, extension);
|
||||||
|
MediaSource mediaSourceWithAds = new AdsMediaSource(videoSource, mediaDataSourceFactory, adsLoader, exoPlayerView);
|
||||||
MediaSource mediaSource;
|
MediaSource mediaSource;
|
||||||
if (mediaSourceList.size() == 0) {
|
if (mediaSourceList.size() == 0) {
|
||||||
mediaSource = videoSource;
|
mediaSource = mediaSourceWithAds;
|
||||||
} else {
|
} else {
|
||||||
mediaSourceList.add(0, videoSource);
|
mediaSourceList.add(0, mediaSourceWithAds);
|
||||||
MediaSource[] textSourceArray = mediaSourceList.toArray(
|
MediaSource[] textSourceArray = mediaSourceList.toArray(
|
||||||
new MediaSource[mediaSourceList.size()]
|
new MediaSource[mediaSourceList.size()]
|
||||||
);
|
);
|
||||||
@ -402,6 +422,19 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
}, 1);
|
}, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AdsMediaSource.MediaSourceFactory implementation.
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MediaSource createMediaSource(Uri uri) {
|
||||||
|
return buildMediaSource(uri, extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int[] getSupportedTypes() {
|
||||||
|
// IMA does not support Smooth Streaming ads.
|
||||||
|
return new int[] {C.TYPE_DASH, C.TYPE_HLS, C.TYPE_OTHER};
|
||||||
|
}
|
||||||
|
|
||||||
private MediaSource buildMediaSource(Uri uri, String overrideExtension) {
|
private MediaSource buildMediaSource(Uri uri, String overrideExtension) {
|
||||||
int type = Util.inferContentType(!TextUtils.isEmpty(overrideExtension) ? "." + overrideExtension
|
int type = Util.inferContentType(!TextUtils.isEmpty(overrideExtension) ? "." + overrideExtension
|
||||||
: uri.getLastPathSegment());
|
: uri.getLastPathSegment());
|
||||||
@ -473,6 +506,7 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
trackSelector = null;
|
trackSelector = null;
|
||||||
player = null;
|
player = null;
|
||||||
}
|
}
|
||||||
|
adsLoader.release();
|
||||||
progressHandler.removeMessages(SHOW_PROGRESS);
|
progressHandler.removeMessages(SHOW_PROGRESS);
|
||||||
themedReactContext.removeLifecycleEventListener(this);
|
themedReactContext.removeLifecycleEventListener(this);
|
||||||
audioBecomingNoisyReceiver.removeListener();
|
audioBecomingNoisyReceiver.removeListener();
|
||||||
@ -914,6 +948,11 @@ class ReactExoplayerView extends FrameLayout implements
|
|||||||
mReportBandwidth = reportBandwidth;
|
mReportBandwidth = reportBandwidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setAdTagUrl(final Uri uri) {
|
||||||
|
adTagUrl = uri;
|
||||||
|
adsLoader = new ImaAdsLoader(this.themedReactContext, adTagUrl);
|
||||||
|
}
|
||||||
|
|
||||||
public void setRawSrc(final Uri uri, final String extension) {
|
public void setRawSrc(final Uri uri, final String extension) {
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
boolean isOriginalSourceNull = srcUri == null;
|
boolean isOriginalSourceNull = srcUri == null;
|
||||||
|
@ -25,6 +25,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_SRC_HEADERS = "requestHeaders";
|
private static final String PROP_SRC_HEADERS = "requestHeaders";
|
||||||
private static final String PROP_RESIZE_MODE = "resizeMode";
|
private static final String PROP_RESIZE_MODE = "resizeMode";
|
||||||
@ -140,6 +141,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));
|
||||||
|
@ -14,6 +14,11 @@ import {
|
|||||||
|
|
||||||
import Video from 'react-native-video';
|
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 {
|
class VideoPlayer extends Component {
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
@ -119,6 +124,7 @@ class VideoPlayer extends Component {
|
|||||||
onAudioBecomingNoisy={this.onAudioBecomingNoisy}
|
onAudioBecomingNoisy={this.onAudioBecomingNoisy}
|
||||||
onAudioFocusChanged={this.onAudioFocusChanged}
|
onAudioFocusChanged={this.onAudioFocusChanged}
|
||||||
repeat={false}
|
repeat={false}
|
||||||
|
adTagUrl={adTagUrl}
|
||||||
/>
|
/>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
@ -15,6 +15,11 @@ import {
|
|||||||
|
|
||||||
import Video,{FilterType} from 'react-native-video';
|
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 = [
|
const filterTypes = [
|
||||||
FilterType.NONE,
|
FilterType.NONE,
|
||||||
FilterType.INVERT,
|
FilterType.INVERT,
|
||||||
@ -266,6 +271,7 @@ class VideoPlayer extends Component {
|
|||||||
controls={this.state.controls}
|
controls={this.state.controls}
|
||||||
filter={this.state.filter}
|
filter={this.state.filter}
|
||||||
filterEnabled={this.state.filterEnabled}
|
filterEnabled={this.state.filterEnabled}
|
||||||
|
adTagUrl={adTagUrl}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.controls}>
|
<View style={styles.controls}>
|
||||||
|
48
examples/basic/ios/Podfile
Normal file
48
examples/basic/ios/Podfile
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# Uncomment the next line to define a global platform for your project
|
||||||
|
platform :ios, '9.0'
|
||||||
|
|
||||||
|
target 'VideoPlayer' do
|
||||||
|
# Comment the next line if you don't want to use dynamic frameworks
|
||||||
|
use_frameworks!
|
||||||
|
pod 'FBLazyVector', :path => "../node_modules/react-native/Libraries/FBLazyVector"
|
||||||
|
pod 'FBReactNativeSpec', :path => "../node_modules/react-native/Libraries/FBReactNativeSpec"
|
||||||
|
pod 'RCTRequired', :path => "../node_modules/react-native/Libraries/RCTRequired"
|
||||||
|
pod 'RCTTypeSafety', :path => "../node_modules/react-native/Libraries/TypeSafety"
|
||||||
|
pod 'React', :path => '../node_modules/react-native/'
|
||||||
|
pod 'React-Core', :path => '../node_modules/react-native/'
|
||||||
|
pod 'React-CoreModules', :path => '../node_modules/react-native/React/CoreModules'
|
||||||
|
pod 'React-Core/DevSupport', :path => '../node_modules/react-native/'
|
||||||
|
pod 'React-RCTActionSheet', :path => '../node_modules/react-native/Libraries/ActionSheetIOS'
|
||||||
|
pod 'React-RCTAnimation', :path => '../node_modules/react-native/Libraries/NativeAnimation'
|
||||||
|
pod 'React-RCTBlob', :path => '../node_modules/react-native/Libraries/Blob'
|
||||||
|
pod 'React-RCTImage', :path => '../node_modules/react-native/Libraries/Image'
|
||||||
|
pod 'React-RCTLinking', :path => '../node_modules/react-native/Libraries/LinkingIOS'
|
||||||
|
pod 'React-RCTNetwork', :path => '../node_modules/react-native/Libraries/Network'
|
||||||
|
pod 'React-RCTSettings', :path => '../node_modules/react-native/Libraries/Settings'
|
||||||
|
pod 'React-RCTText', :path => '../node_modules/react-native/Libraries/Text'
|
||||||
|
pod 'React-RCTVibration', :path => '../node_modules/react-native/Libraries/Vibration'
|
||||||
|
pod 'React-Core/RCTWebSocket', :path => '../node_modules/react-native/'
|
||||||
|
|
||||||
|
pod 'React-cxxreact', :path => '../node_modules/react-native/ReactCommon/cxxreact'
|
||||||
|
pod 'React-jsi', :path => '../node_modules/react-native/ReactCommon/jsi'
|
||||||
|
pod 'React-jsiexecutor', :path => '../node_modules/react-native/ReactCommon/jsiexecutor'
|
||||||
|
pod 'React-jsinspector', :path => '../node_modules/react-native/ReactCommon/jsinspector'
|
||||||
|
pod 'ReactCommon/jscallinvoker', :path => "../node_modules/react-native/ReactCommon"
|
||||||
|
pod 'ReactCommon/turbomodule/core', :path => "../node_modules/react-native/ReactCommon"
|
||||||
|
pod 'Yoga', :path => '../node_modules/react-native/ReactCommon/yoga'
|
||||||
|
|
||||||
|
pod 'DoubleConversion', :podspec => '../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec'
|
||||||
|
pod 'glog', :podspec => '../node_modules/react-native/third-party-podspecs/glog.podspec'
|
||||||
|
pod 'Folly', :podspec => '../node_modules/react-native/third-party-podspecs/Folly.podspec'
|
||||||
|
|
||||||
|
# Pods for VideoPlayer
|
||||||
|
pod 'react-native-video', :path => '../node_modules/react-native-video'
|
||||||
|
end
|
||||||
|
|
||||||
|
target 'VideoPlayer-tvOS' do
|
||||||
|
# Comment the next line if you don't want to use dynamic frameworks
|
||||||
|
use_frameworks!
|
||||||
|
|
||||||
|
# Pods for VideoPlayer-tvOS
|
||||||
|
|
||||||
|
end
|
@ -10,7 +10,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react": "16.9.0",
|
"react": "16.9.0",
|
||||||
"react-native": "0.60.5",
|
"react-native": "0.61.5",
|
||||||
"react-native-video": "file:../.."
|
"react-native-video": "file:../.."
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
#import "RCTVideoPlayerViewControllerDelegate.h"
|
#import "RCTVideoPlayerViewControllerDelegate.h"
|
||||||
#import <React/RCTComponent.h>
|
#import <React/RCTComponent.h>
|
||||||
#import <React/RCTBridgeModule.h>
|
#import <React/RCTBridgeModule.h>
|
||||||
|
@import GoogleInteractiveMediaAds;
|
||||||
|
|
||||||
#if __has_include(<react-native-video/RCTVideoCache.h>)
|
#if __has_include(<react-native-video/RCTVideoCache.h>)
|
||||||
#import <react-native-video/RCTVideoCache.h>
|
#import <react-native-video/RCTVideoCache.h>
|
||||||
@ -14,11 +15,11 @@
|
|||||||
|
|
||||||
@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, IMAAdsLoaderDelegate, IMAAdsManagerDelegate>
|
||||||
#elif TARGET_OS_TV
|
#elif TARGET_OS_TV
|
||||||
@interface RCTVideo : UIView <RCTVideoPlayerViewControllerDelegate>
|
@interface RCTVideo : UIView <RCTVideoPlayerViewControllerDelegate>
|
||||||
#else
|
#else
|
||||||
@interface RCTVideo : UIView <RCTVideoPlayerViewControllerDelegate, AVPictureInPictureControllerDelegate>
|
@interface RCTVideo : UIView <RCTVideoPlayerViewControllerDelegate, AVPictureInPictureControllerDelegate, IMAAdsLoaderDelegate, IMAAdsManagerDelegate>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@property (nonatomic, copy) RCTDirectEventBlock onVideoLoadStart;
|
@property (nonatomic, copy) RCTDirectEventBlock onVideoLoadStart;
|
||||||
@ -42,6 +43,12 @@
|
|||||||
@property (nonatomic, copy) RCTDirectEventBlock onVideoExternalPlaybackChange;
|
@property (nonatomic, copy) RCTDirectEventBlock onVideoExternalPlaybackChange;
|
||||||
@property (nonatomic, copy) RCTDirectEventBlock onPictureInPictureStatusChanged;
|
@property (nonatomic, copy) RCTDirectEventBlock onPictureInPictureStatusChanged;
|
||||||
@property (nonatomic, copy) RCTDirectEventBlock onRestoreUserInterfaceForPictureInPictureStop;
|
@property (nonatomic, copy) RCTDirectEventBlock onRestoreUserInterfaceForPictureInPictureStop;
|
||||||
|
/// 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;
|
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
|
||||||
|
|
||||||
|
@ -39,6 +39,7 @@ static int const RCTVideoUnset = -1;
|
|||||||
BOOL _playbackRateObserverRegistered;
|
BOOL _playbackRateObserverRegistered;
|
||||||
BOOL _isExternalPlaybackActiveObserverRegistered;
|
BOOL _isExternalPlaybackActiveObserverRegistered;
|
||||||
BOOL _videoLoadStarted;
|
BOOL _videoLoadStarted;
|
||||||
|
BOOL _isRequestAds;
|
||||||
|
|
||||||
bool _pendingSeek;
|
bool _pendingSeek;
|
||||||
float _pendingSeekTime;
|
float _pendingSeekTime;
|
||||||
@ -73,6 +74,7 @@ static int const RCTVideoUnset = -1;
|
|||||||
NSString * _fullscreenOrientation;
|
NSString * _fullscreenOrientation;
|
||||||
BOOL _fullscreenPlayerPresented;
|
BOOL _fullscreenPlayerPresented;
|
||||||
NSString *_filterName;
|
NSString *_filterName;
|
||||||
|
NSString * _adTagUrl;
|
||||||
BOOL _filterEnabled;
|
BOOL _filterEnabled;
|
||||||
UIViewController * _presentingViewController;
|
UIViewController * _presentingViewController;
|
||||||
#if __has_include(<react-native-video/RCTVideoCache.h>)
|
#if __has_include(<react-native-video/RCTVideoCache.h>)
|
||||||
@ -107,6 +109,7 @@ static int const RCTVideoUnset = -1;
|
|||||||
_allowsExternalPlayback = YES;
|
_allowsExternalPlayback = YES;
|
||||||
_playWhenInactive = false;
|
_playWhenInactive = false;
|
||||||
_pictureInPicture = false;
|
_pictureInPicture = false;
|
||||||
|
_isRequestAds = false;
|
||||||
_ignoreSilentSwitch = @"inherit"; // inherit, ignore, obey
|
_ignoreSilentSwitch = @"inherit"; // inherit, ignore, obey
|
||||||
#if TARGET_OS_IOS
|
#if TARGET_OS_IOS
|
||||||
_restoreUserInterfaceForPIPStopCompletionHandler = NULL;
|
_restoreUserInterfaceForPIPStopCompletionHandler = NULL;
|
||||||
@ -144,6 +147,8 @@ static int const RCTVideoUnset = -1;
|
|||||||
viewController.showsPlaybackControls = YES;
|
viewController.showsPlaybackControls = YES;
|
||||||
viewController.rctDelegate = self;
|
viewController.rctDelegate = self;
|
||||||
viewController.preferredOrientation = _fullscreenOrientation;
|
viewController.preferredOrientation = _fullscreenOrientation;
|
||||||
|
self.contentPlayhead = [[IMAAVPlayerContentPlayhead alloc] initWithAVPlayer:player];
|
||||||
|
[self setupAdsLoader];
|
||||||
|
|
||||||
viewController.view.frame = self.bounds;
|
viewController.view.frame = self.bounds;
|
||||||
viewController.player = player;
|
viewController.player = player;
|
||||||
@ -269,6 +274,10 @@ static int const RCTVideoUnset = -1;
|
|||||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"RCTVideo_progress" object:nil userInfo:@{@"progress": [NSNumber numberWithDouble: currentTimeSecs / duration]}];
|
[[NSNotificationCenter defaultCenter] postNotificationName:@"RCTVideo_progress" object:nil userInfo:@{@"progress": [NSNumber numberWithDouble: currentTimeSecs / duration]}];
|
||||||
|
|
||||||
if( currentTimeSecs >= 0 && self.onVideoProgress) {
|
if( currentTimeSecs >= 0 && self.onVideoProgress) {
|
||||||
|
if(!_isRequestAds && !_paused) {
|
||||||
|
[self requestAds];
|
||||||
|
_isRequestAds = true;
|
||||||
|
}
|
||||||
self.onVideoProgress(@{
|
self.onVideoProgress(@{
|
||||||
@"currentTime": [NSNumber numberWithFloat:CMTimeGetSeconds(currentTime)],
|
@"currentTime": [NSNumber numberWithFloat:CMTimeGetSeconds(currentTime)],
|
||||||
@"playableDuration": [self calculatePlayableDuration],
|
@"playableDuration": [self calculatePlayableDuration],
|
||||||
@ -659,7 +668,6 @@ static int const RCTVideoUnset = -1;
|
|||||||
@"target": self.reactTag});
|
@"target": self.reactTag});
|
||||||
}
|
}
|
||||||
_videoLoadStarted = NO;
|
_videoLoadStarted = NO;
|
||||||
|
|
||||||
[self attachListeners];
|
[self attachListeners];
|
||||||
[self applyModifiers];
|
[self applyModifiers];
|
||||||
} else if (_playerItem.status == AVPlayerItemStatusFailed && self.onVideoError) {
|
} else if (_playerItem.status == AVPlayerItemStatusFailed && self.onVideoError) {
|
||||||
@ -719,6 +727,74 @@ static int const RCTVideoUnset = -1;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)setupAdsLoader {
|
||||||
|
// Re-use this IMAAdsLoader instance for the entire lifecycle of your app.
|
||||||
|
self.adsLoader = [[IMAAdsLoader alloc] initWithSettings:nil];
|
||||||
|
// NOTE: This line will cause a warning until the next step, "Get the Ads Manager".
|
||||||
|
self.adsLoader.delegate = self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)requestAds {
|
||||||
|
// Create an ad display container for ad rendering.
|
||||||
|
IMAAdDisplayContainer *adDisplayContainer =
|
||||||
|
[[IMAAdDisplayContainer alloc] initWithAdContainer:self companionSlots:nil];
|
||||||
|
// Create an ad request with our ad tag, display container, and optional user context.
|
||||||
|
IMAAdsRequest *request = [[IMAAdsRequest alloc] initWithAdTagUrl:_adTagUrl
|
||||||
|
adDisplayContainer:adDisplayContainer
|
||||||
|
contentPlayhead:self.contentPlayhead
|
||||||
|
userContext:nil];
|
||||||
|
[self.adsLoader requestAdsWithRequest:request];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark AdsLoader Delegates
|
||||||
|
|
||||||
|
- (void)adsLoader:(IMAAdsLoader *)loader adsLoadedWithData:(IMAAdsLoadedData *)adsLoadedData {
|
||||||
|
// Grab the instance of the IMAAdsManager and set ourselves as the delegate.
|
||||||
|
self.adsManager = adsLoadedData.adsManager;
|
||||||
|
|
||||||
|
// NOTE: This line will cause a warning until the next step, "Display Ads".
|
||||||
|
self.adsManager.delegate = self;
|
||||||
|
|
||||||
|
// Create ads rendering settings and tell the SDK to use the in-app browser.
|
||||||
|
IMAAdsRenderingSettings *adsRenderingSettings = [[IMAAdsRenderingSettings alloc] init];
|
||||||
|
adsRenderingSettings.webOpenerPresentingController = _playerViewController;
|
||||||
|
|
||||||
|
// Initialize the ads manager.
|
||||||
|
[self.adsManager initializeWithAdsRenderingSettings:adsRenderingSettings];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)adsLoader:(IMAAdsLoader *)loader failedWithErrorData:(IMAAdLoadingErrorData *)adErrorData {
|
||||||
|
// Something went wrong loading ads. Log the error and play the content.
|
||||||
|
NSLog(@"Error loading ads: %@", adErrorData.adError.message);
|
||||||
|
[_player play];
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark AdsManager Delegates
|
||||||
|
|
||||||
|
- (void)adsManager:(IMAAdsManager *)adsManager didReceiveAdEvent:(IMAAdEvent *)event {
|
||||||
|
if (event.type == kIMAAdEvent_LOADED) {
|
||||||
|
// When the SDK notifies us that ads have been loaded, play them.
|
||||||
|
[adsManager start];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)adsManager:(IMAAdsManager *)adsManager didReceiveAdError:(IMAAdError *)error {
|
||||||
|
// Something went wrong with the ads manager after ads were loaded. Log the error and play the
|
||||||
|
// content.
|
||||||
|
NSLog(@"AdsManager error: %@", error.message);
|
||||||
|
[_player play];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)adsManagerDidRequestContentPause:(IMAAdsManager *)adsManager {
|
||||||
|
// The SDK is going to play ads, so pause the content.
|
||||||
|
[_player pause];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)adsManagerDidRequestContentResume:(IMAAdsManager *)adsManager {
|
||||||
|
// The SDK is done playing ads (at least for now), so resume the content.
|
||||||
|
[_player play];
|
||||||
|
}
|
||||||
|
|
||||||
- (void)attachListeners
|
- (void)attachListeners
|
||||||
{
|
{
|
||||||
// listen for end of file
|
// listen for end of file
|
||||||
@ -769,6 +845,9 @@ static int const RCTVideoUnset = -1;
|
|||||||
|
|
||||||
- (void)playerItemDidReachEnd:(NSNotification *)notification
|
- (void)playerItemDidReachEnd:(NSNotification *)notification
|
||||||
{
|
{
|
||||||
|
if (notification.object == _player.currentItem) {
|
||||||
|
[self.adsLoader contentComplete];
|
||||||
|
}
|
||||||
if(self.onVideoEnd) {
|
if(self.onVideoEnd) {
|
||||||
self.onVideoEnd(@{@"target": self.reactTag});
|
self.onVideoEnd(@{@"target": self.reactTag});
|
||||||
}
|
}
|
||||||
@ -1434,6 +1513,10 @@ static int const RCTVideoUnset = -1;
|
|||||||
_filterEnabled = filterEnabled;
|
_filterEnabled = filterEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)setAdTagUrl:(NSString *)adTagUrl {
|
||||||
|
_adTagUrl = adTagUrl;
|
||||||
|
}
|
||||||
|
|
||||||
#pragma mark - React View Management
|
#pragma mark - React View Management
|
||||||
|
|
||||||
- (void)insertReactSubview:(UIView *)view atIndex:(NSInteger)atIndex
|
- (void)insertReactSubview:(UIView *)view atIndex:(NSInteger)atIndex
|
||||||
|
@ -19,6 +19,7 @@ RCT_EXPORT_MODULE();
|
|||||||
}
|
}
|
||||||
|
|
||||||
RCT_EXPORT_VIEW_PROPERTY(src, NSDictionary);
|
RCT_EXPORT_VIEW_PROPERTY(src, 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);
|
||||||
|
@ -12,10 +12,12 @@ Pod::Spec.new do |s|
|
|||||||
s.homepage = 'https://github.com/react-native-community/react-native-video'
|
s.homepage = 'https://github.com/react-native-community/react-native-video'
|
||||||
s.source = { :git => "https://github.com/react-native-community/react-native-video.git", :tag => "#{s.version}" }
|
s.source = { :git => "https://github.com/react-native-community/react-native-video.git", :tag => "#{s.version}" }
|
||||||
|
|
||||||
s.ios.deployment_target = "8.0"
|
s.ios.deployment_target = "9.0"
|
||||||
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}"
|
ss.source_files = "ios/Video/*.{h,m}"
|
||||||
s.static_framework = true
|
s.static_framework = true
|
||||||
end
|
end
|
||||||
@ -29,7 +31,6 @@ Pod::Spec.new do |s|
|
|||||||
s.static_framework = true
|
s.static_framework = true
|
||||||
end
|
end
|
||||||
|
|
||||||
s.dependency "React"
|
s.dependency 'React'
|
||||||
|
|
||||||
s.default_subspec = "Video"
|
s.default_subspec = "Video"
|
||||||
end
|
end
|
||||||
|
Loading…
Reference in New Issue
Block a user