Merge pull request #2923 from avencat/master
Feature: Add VAST support for AVOD
This commit is contained in:
commit
346d5a1d0d
247
API.md
247
API.md
@ -263,80 +263,82 @@ var styles = StyleSheet.create({
|
||||
```
|
||||
|
||||
### Configurable props
|
||||
| Name |Plateforms Support |
|
||||
|--|--|
|
||||
|[allowsExternalPlayback](#allowsexternalplayback) |iOS |
|
||||
|[audioOnly](#audioonly)|All |
|
||||
|[automaticallyWaitsToMinimizeStalling](#automaticallyWaitsToMinimizeStalling) | iOS|\
|
||||
|[backBufferDurationMs](#backBufferDurationMs)| Android |
|
||||
|[bufferConfig](#bufferconfig)|Android|
|
||||
|[contentStartTime](#contentStartTime)| Android |
|
||||
|[controls](#controls)|Android, iOS|
|
||||
|[currentPlaybackTime](#currentPlaybackTime)|Android|
|
||||
|[disableFocus](#disableFocus)|Android, iOS|
|
||||
|[disableDisconnectError](#disableDisconnectError)|Android|
|
||||
|[filter](#filter)|iOS|
|
||||
|[filterEnabled](#filterEnabled)|iOS|
|
||||
|[focusable](#focusable)|Android|
|
||||
|[fullscreen](#fullscreen)|iOS|
|
||||
|[fullscreenAutorotate](#fullscreenautorotate)|iOS|
|
||||
|[fullscreenOrientation](#fullscreenorientation)|iOS|
|
||||
|[headers](#headers)|Android|
|
||||
|[hideShutterView](#hideshutterview)|Android|
|
||||
|[ignoreSilentSwitch](#ignoresilentswitch)|iOS|
|
||||
|[maxBitRate](#maxbitrate)|Android, iOS|
|
||||
|[minLoadRetryCount](#minLoadRetryCount)|Android|
|
||||
|[mixWithOthers](#mixWithOthers)|iOS|
|
||||
|[muted](#muted)|All|
|
||||
|[paused](#paused)|All|
|
||||
|[pictureInPicture](#pictureinpicture)|iOS|
|
||||
|[playInBackground](#playinbackground)|Android, iOS|
|
||||
|[playWhenInactive](#playwheninactive)|iOS|
|
||||
|[poster](#poster)|All|
|
||||
|[posterResizeMode](#posterresizemode)|All|
|
||||
|[preferredForwardBufferDuration](#preferredForwardBufferDuration)|iOS|
|
||||
|[preventsDisplaySleepDuringVideoPlayback](#preventsDisplaySleepDuringVideoPlayback)|iOS, Android|
|
||||
|[progressUpdateInterval](#progressupdateinterval)|All|
|
||||
|[rate](#rate)|All|
|
||||
|[repeat](#repeat)|All|
|
||||
|[reportBandwidth](#reportbandwidth)|Android|
|
||||
|[resizeMode](#resizemode)|Android, iOS, Windows UWP|
|
||||
|[selectedAudioTrack](#selectedaudiotrack)|Android, iOS|
|
||||
|[selectedTextTrack](#selectedtexttrack)|Android, iOS|
|
||||
|[selectedVideoTrack](#selectedvideotrack)|Android|
|
||||
|[source](#source)|All|
|
||||
|[subtitleStyle](#subtitleStyle)|Android|
|
||||
|[textTracks](#texttracks)|Android, iOS|
|
||||
|[trackId](#trackId)|Android|
|
||||
|[useTextureView](#usetextureview)|Android|
|
||||
|[useSecureView](#useSecureView)|Android|
|
||||
|[volume](#volume)|All|
|
||||
|[localSourceEncryptionKeyScheme](#localSourceEncryptionKeyScheme)|All|
|
||||
| Name | Platforms Support |
|
||||
|-------------------------------------------------------------------------------------|---------------------------|
|
||||
| [adTagUrl](#adTagUrl) | Android, iOS |
|
||||
| [allowsExternalPlayback](#allowsexternalplayback) | iOS |
|
||||
| [audioOnly](#audioonly) | All |
|
||||
| [automaticallyWaitsToMinimizeStalling](#automaticallyWaitsToMinimizeStalling) | iOS |
|
||||
| [backBufferDurationMs](#backBufferDurationMs) | Android |
|
||||
| [bufferConfig](#bufferconfig) | Android |
|
||||
| [contentStartTime](#contentStartTime) | Android |
|
||||
| [controls](#controls) | Android, iOS |
|
||||
| [currentPlaybackTime](#currentPlaybackTime) | Android |
|
||||
| [disableFocus](#disableFocus) | Android, iOS |
|
||||
| [disableDisconnectError](#disableDisconnectError) | Android |
|
||||
| [filter](#filter) | iOS |
|
||||
| [filterEnabled](#filterEnabled) | iOS |
|
||||
| [focusable](#focusable) | Android |
|
||||
| [fullscreen](#fullscreen) | iOS |
|
||||
| [fullscreenAutorotate](#fullscreenautorotate) | iOS |
|
||||
| [fullscreenOrientation](#fullscreenorientation) | iOS |
|
||||
| [headers](#headers) | Android |
|
||||
| [hideShutterView](#hideshutterview) | Android |
|
||||
| [ignoreSilentSwitch](#ignoresilentswitch) | iOS |
|
||||
| [maxBitRate](#maxbitrate) | Android, iOS |
|
||||
| [minLoadRetryCount](#minLoadRetryCount) | Android |
|
||||
| [mixWithOthers](#mixWithOthers) | iOS |
|
||||
| [muted](#muted) | All |
|
||||
| [paused](#paused) | All |
|
||||
| [pictureInPicture](#pictureinpicture) | iOS |
|
||||
| [playInBackground](#playinbackground) | Android, iOS |
|
||||
| [playWhenInactive](#playwheninactive) | iOS |
|
||||
| [poster](#poster) | All |
|
||||
| [posterResizeMode](#posterresizemode) | All |
|
||||
| [preferredForwardBufferDuration](#preferredForwardBufferDuration) | iOS |
|
||||
| [preventsDisplaySleepDuringVideoPlayback](#preventsDisplaySleepDuringVideoPlayback) | iOS, Android |
|
||||
| [progressUpdateInterval](#progressupdateinterval) | All |
|
||||
| [rate](#rate) | All |
|
||||
| [repeat](#repeat) | All |
|
||||
| [reportBandwidth](#reportbandwidth) | Android |
|
||||
| [resizeMode](#resizemode) | Android, iOS, Windows UWP |
|
||||
| [selectedAudioTrack](#selectedaudiotrack) | Android, iOS |
|
||||
| [selectedTextTrack](#selectedtexttrack) | Android, iOS |
|
||||
| [selectedVideoTrack](#selectedvideotrack) | Android |
|
||||
| [source](#source) | All |
|
||||
| [subtitleStyle](#subtitleStyle) | Android |
|
||||
| [textTracks](#texttracks) | Android, iOS |
|
||||
| [trackId](#trackId) | Android |
|
||||
| [useTextureView](#usetextureview) | Android |
|
||||
| [useSecureView](#useSecureView) | Android |
|
||||
| [volume](#volume) | All |
|
||||
| [localSourceEncryptionKeyScheme](#localSourceEncryptionKeyScheme) | All |
|
||||
|
||||
|
||||
### Event props
|
||||
| Name |Plateforms Support |
|
||||
|--|--|
|
||||
|[onAudioBecomingNoisy](#onaudiobecomingnoisy)|Android, iOS|
|
||||
|[onBandwidthUpdate](#onbandwidthupdate)|Android|
|
||||
|[onBuffer](#onbuffer)|Android, iOS|
|
||||
|[onEnd](#onend)|All|
|
||||
|[onError](#onerror)|Android, iOS|
|
||||
|[onExternalPlaybackChange](#onexternalplaybackchange)|iOS|
|
||||
|[onFullscreenPlayerWillPresent](#onfullscreenplayerwillpresent)|Android, iOS|
|
||||
|[onFullscreenPlayerDidPresent](#onfullscreenplayerdidpresent)|Android, iOS|
|
||||
|[onFullscreenPlayerWillDismiss](#onfullscreenplayerwilldismiss)|Android, iOS|
|
||||
|[onFullscreenPlayerDidDismiss](#onfullscreenplayerdiddismiss)|Android, iOS|
|
||||
|[onLoad](#onload)|All|
|
||||
|[onLoadStart](#onloadstart)|All|
|
||||
|[onReadyForDisplay](#onreadyfordisplay)|Android, iOS, Web|
|
||||
|[onPictureInPictureStatusChanged](#onpictureinpicturestatuschanged)|iOS|
|
||||
|[onPlaybackRateChange](#onplaybackratechange)|All|
|
||||
|[onProgress](#onprogress)|All|
|
||||
|[onSeek](#onseek)|Android, iOS, Windows UWP|
|
||||
|[onRestoreUserInterfaceForPictureInPictureStop](#onrestoreuserinterfaceforpictureinpicturestop)|iOS|
|
||||
|[onTimedMetadata](#ontimedmetadata)|Android, iOS|
|
||||
|
||||
| Name | Platforms Support |
|
||||
|-------------------------------------------------------------------------------------------------|---------------------------|
|
||||
| [onAudioBecomingNoisy](#onaudiobecomingnoisy) | Android, iOS |
|
||||
| [onBandwidthUpdate](#onbandwidthupdate) | Android |
|
||||
| [onBuffer](#onbuffer) | Android, iOS |
|
||||
| [onEnd](#onend) | All |
|
||||
| [onError](#onerror) | Android, iOS |
|
||||
| [onExternalPlaybackChange](#onexternalplaybackchange) | iOS |
|
||||
| [onFullscreenPlayerWillPresent](#onfullscreenplayerwillpresent) | Android, iOS |
|
||||
| [onFullscreenPlayerDidPresent](#onfullscreenplayerdidpresent) | Android, iOS |
|
||||
| [onFullscreenPlayerWillDismiss](#onfullscreenplayerwilldismiss) | Android, iOS |
|
||||
| [onFullscreenPlayerDidDismiss](#onfullscreenplayerdiddismiss) | Android, iOS |
|
||||
| [onLoad](#onload) | All |
|
||||
| [onLoadStart](#onloadstart) | All |
|
||||
| [onPictureInPictureStatusChanged](#onpictureinpicturestatuschanged) | iOS |
|
||||
| [onPlaybackRateChange](#onplaybackratechange) | All |
|
||||
| [onProgress](#onprogress) | All |
|
||||
| [onReadyForDisplay](#onreadyfordisplay) | Android, iOS, Web |
|
||||
| [onReceiveAdEvent](#onReceiveAdEvent) | Android, iOS |
|
||||
| [onRestoreUserInterfaceForPictureInPictureStop](#onrestoreuserinterfaceforpictureinpicturestop) | iOS |
|
||||
| [onSeek](#onseek) | Android, iOS, Windows UWP |
|
||||
| [onTimedMetadata](#ontimedmetadata) | Android, iOS |
|
||||
|
||||
### Methods
|
||||
| Name |Plateforms Support |
|
||||
@ -357,6 +359,16 @@ var styles = StyleSheet.create({
|
||||
|
||||
### Configurable props
|
||||
|
||||
#### adTagUrl
|
||||
Sets the VAST uri to play AVOD ads.
|
||||
|
||||
Example:
|
||||
```
|
||||
adTagUrl="https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/vmap_ad_samples&sz=640x480&cust_params=sample_ar%3Dpremidpostoptimizedpodbumper&ciu_szs=300x250&gdfp_req=1&ad_rule=1&output=vmap&unviewed_position_start=1&env=vp&impl=s&cmsid=496&vid=short_onecue&correlator="
|
||||
```
|
||||
|
||||
Platforms: Android, iOS
|
||||
|
||||
#### allowsExternalPlayback
|
||||
Indicates whether the player allows switching to external playback mode such as AirPlay or HDMI.
|
||||
* **true (default)** - allow switching to external playback mode
|
||||
@ -1161,16 +1173,6 @@ Example:
|
||||
|
||||
Platforms: Android
|
||||
|
||||
#### onReadyForDisplay
|
||||
Callback function that is called when the first video frame is ready for display. This is when the poster is removed.
|
||||
|
||||
Payload: none
|
||||
|
||||
* iOS: [readyForDisplay](https://developer.apple.com/documentation/avkit/avplayerviewcontroller/1615830-readyfordisplay?language=objc)
|
||||
* Android [STATE_READY](https://exoplayer.dev/doc/reference/com/google/android/exoplayer2/Player.html#STATE_READY)
|
||||
|
||||
Platforms: Android, iOS, Web
|
||||
|
||||
#### onPictureInPictureStatusChanged
|
||||
Callback function that is called when picture in picture becomes active or inactive.
|
||||
|
||||
@ -1203,7 +1205,6 @@ Example:
|
||||
|
||||
Platforms: all
|
||||
|
||||
|
||||
#### onProgress
|
||||
Callback function that is called every progressUpdateInterval milliseconds with info about which position the media is currently playing.
|
||||
|
||||
@ -1224,6 +1225,83 @@ Example:
|
||||
|
||||
Platforms: all
|
||||
|
||||
#### onReadyForDisplay
|
||||
Callback function that is called when the first video frame is ready for display. This is when the poster is removed.
|
||||
|
||||
Payload: none
|
||||
|
||||
* iOS: [readyForDisplay](https://developer.apple.com/documentation/avkit/avplayerviewcontroller/1615830-readyfordisplay?language=objc)
|
||||
* Android [STATE_READY](https://exoplayer.dev/doc/reference/com/google/android/exoplayer2/Player.html#STATE_READY)
|
||||
|
||||
Platforms: Android, iOS, Web
|
||||
|
||||
#### onReceiveAdEvent
|
||||
Callback function that is called when an AdEvent is received from the IMA's SDK.
|
||||
|
||||
Enum `AdEvent` possible values for [Android](https://developers.google.com/interactive-media-ads/docs/sdks/html5/client-side/reference/js/google.ima.AdEvent) and [iOS](https://developers.google.com/interactive-media-ads/docs/sdks/ios/client-side/reference/Enums/IMAAdEventType):
|
||||
|
||||
| Event | Platform | Description |
|
||||
|----------------------------|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `AD_BREAK_ENDED` | iOS | Fired the first time each ad break ends. Applications must reenable seeking when this occurs (only used for dynamic ad insertion). |
|
||||
| `AD_BREAK_READY` | Android, iOS | Fires when an ad rule or a VMAP ad break would have played if autoPlayAdBreaks is false. |
|
||||
| `AD_BREAK_STARTED` | iOS | Fired first time each ad break begins playback. If an ad break is watched subsequent times this will not be fired. Applications must disable seeking when this occurs (only used for dynamic ad insertion). |
|
||||
| `AD_BUFFERING` | Android | Fires when the ad has stalled playback to buffer. |
|
||||
| `AD_CAN_PLAY` | Android | Fires when the ad is ready to play without buffering, either at the beginning of the ad or after buffering completes. |
|
||||
| `AD_METADATA` | Android | Fires when an ads list is loaded. |
|
||||
| `AD_PERIOD_ENDED` | iOS | Fired every time the stream switches from advertising or slate to content. This will be fired even when an ad is played a second time or when seeking into an ad (only used for dynamic ad insertion). |
|
||||
| `AD_PERIOD_STARTED` | iOS | Fired every time the stream switches from content to advertising or slate. This will be fired even when an ad is played a second time or when seeking into an ad (only used for dynamic ad insertion). |
|
||||
| `AD_PROGRESS` | Android | Fires when the ad's current time value changes. Calling getAdData() on this event will return an AdProgressData object. |
|
||||
| `ALL_ADS_COMPLETED` | Android, iOS | Fires when the ads manager is done playing all the valid ads in the ads response, or when the response doesn't return any valid ads. |
|
||||
| `CLICK` | Android, iOS | Fires when the ad is clicked. |
|
||||
| `COMPLETE` | Android, iOS | Fires when the ad completes playing. |
|
||||
| `CONTENT_PAUSE_REQUESTED` | Android | Fires when content should be paused. This usually happens right before an ad is about to cover the content. |
|
||||
| `CONTENT_RESUME_REQUESTED` | Android | Fires when content should be resumed. This usually happens when an ad finishes or collapses. |
|
||||
| `CUEPOINTS_CHANGED` | iOS | Cuepoints changed for VOD stream (only used for dynamic ad insertion). |
|
||||
| `DURATION_CHANGE` | Android | Fires when the ad's duration changes. |
|
||||
| `FIRST_QUARTILE` | Android, iOS | Fires when the ad playhead crosses first quartile. |
|
||||
| `IMPRESSION` | Android | Fires when the impression URL has been pinged. |
|
||||
| `INTERACTION` | Android | Fires when an ad triggers the interaction callback. Ad interactions contain an interaction ID string in the ad data. |
|
||||
| `LINEAR_CHANGED` | Android | Fires when the displayed ad changes from linear to nonlinear, or the reverse. |
|
||||
| `LOADED` | Android, iOS | Fires when ad data is available. |
|
||||
| `LOG` | Android, iOS | Fires when a non-fatal error is encountered. The user need not take any action since the SDK will continue with the same or next ad playback depending on the error situation. |
|
||||
| `MIDPOINT` | Android, iOS | Fires when the ad playhead crosses midpoint. |
|
||||
| `PAUSED` | Android, iOS | Fires when the ad is paused. |
|
||||
| `RESUMED` | Android, iOS | Fires when the ad is resumed. |
|
||||
| `SKIPPABLE_STATE_CHANGED` | Android | Fires when the displayed ads skippable state is changed. |
|
||||
| `SKIPPED` | Android, iOS | Fires when the ad is skipped by the user. |
|
||||
| `STARTED` | Android, iOS | Fires when the ad starts playing. |
|
||||
| `STREAM_LOADED` | iOS | Stream request has loaded (only used for dynamic ad insertion). |
|
||||
| `TAPPED` | iOS | Fires when the ad is tapped. |
|
||||
| `THIRD_QUARTILE` | Android, iOS | Fires when the ad playhead crosses third quartile. |
|
||||
| `UNKNOWN` | iOS | An unknown event has fired |
|
||||
| `USER_CLOSE` | Android | Fires when the ad is closed by the user. |
|
||||
| `VIDEO_CLICKED` | Android | Fires when the non-clickthrough portion of a video ad is clicked. |
|
||||
| `VIDEO_ICON_CLICKED` | Android | Fires when a user clicks a video icon. |
|
||||
| `VOLUME_CHANGED` | Android | Fires when the ad volume has changed. |
|
||||
| `VOLUME_MUTED` | Android | Fires when the ad volume has been muted. |
|
||||
|
||||
Payload:
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|---------|-----------------------|
|
||||
| event | AdEvent | The ad event received |
|
||||
|
||||
Example:
|
||||
```
|
||||
{
|
||||
"event": "LOADED"
|
||||
}
|
||||
```
|
||||
|
||||
Platforms: Android, iOS
|
||||
|
||||
#### 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
|
||||
|
||||
#### onSeek
|
||||
Callback function that is called when a seek completes.
|
||||
|
||||
@ -1247,13 +1325,6 @@ Both the currentTime & seekTime are reported because the video player may not se
|
||||
|
||||
Platforms: Android, 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
|
||||
Callback function that is called when timed metadata becomes available
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
- Fix regression when fullscreen prop is used combined with controls [#2911](https://github.com/react-native-video/react-native-video/pull/2911)
|
||||
- Fix: memory leak issue on iOS [#2907](https://github.com/react-native-video/react-native-video/pull/2907)
|
||||
- Fix setting text tracks before player is initialized on iOS [#2935](https://github.com/react-native-video/react-native-video/pull/2935)
|
||||
- Feature: Add VAST support for AVOD [#2923](https://github.com/react-native-video/react-native-video/pull/2923)
|
||||
|
||||
### Version 6.0.0-alpha.3
|
||||
|
||||
|
10
Video.js
10
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 => {
|
||||
if (!UIManager.getViewManagerConfig) {
|
||||
return UIManager[viewManagerName];
|
||||
@ -343,6 +350,7 @@ export default class Video extends Component {
|
||||
onGetLicense: nativeProps.drm && nativeProps.drm.getLicense && this._onGetLicense,
|
||||
onPictureInPictureStatusChanged: this._onPictureInPictureStatusChanged,
|
||||
onRestoreUserInterfaceForPictureInPictureStop: this._onRestoreUserInterfaceForPictureInPictureStop,
|
||||
onReceiveAdEvent: this._onReceiveAdEvent,
|
||||
});
|
||||
|
||||
const posterStyle = {
|
||||
@ -527,6 +535,8 @@ Video.propTypes = {
|
||||
onPictureInPictureStatusChanged: PropTypes.func,
|
||||
needsToRestoreUserInterfaceForPictureInPictureStop: PropTypes.func,
|
||||
onExternalPlaybackChange: PropTypes.func,
|
||||
adTagUrl: PropTypes.string,
|
||||
onReceiveAdEvent: PropTypes.func,
|
||||
|
||||
/* Required by react-native */
|
||||
scaleX: PropTypes.number,
|
||||
|
@ -45,5 +45,7 @@ dependencies {
|
||||
implementation('com.google.android.exoplayer:extension-okhttp:2.18.1') {
|
||||
exclude group: 'com.squareup.okhttp3', module: 'okhttp'
|
||||
}
|
||||
implementation 'com.google.android.exoplayer:extension-ima:2.18.1'
|
||||
|
||||
implementation "com.squareup.okhttp3:okhttp:" + '$OKHTTP_VERSION'
|
||||
}
|
||||
|
@ -22,13 +22,15 @@ import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.Tracks;
|
||||
import com.google.android.exoplayer2.text.Cue;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||
import com.google.android.exoplayer2.ui.AdViewProvider;
|
||||
import com.google.android.exoplayer2.ui.SubtitleView;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.video.VideoSize;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@TargetApi(16)
|
||||
public final class ExoPlayerView extends FrameLayout {
|
||||
public final class ExoPlayerView extends FrameLayout implements AdViewProvider {
|
||||
|
||||
private View surfaceView;
|
||||
private final View shutterView;
|
||||
@ -38,6 +40,7 @@ public final class ExoPlayerView extends FrameLayout {
|
||||
private ExoPlayer player;
|
||||
private Context context;
|
||||
private ViewGroup.LayoutParams layoutParams;
|
||||
private final FrameLayout adOverlayFrameLayout;
|
||||
|
||||
private boolean useTextureView = true;
|
||||
private boolean useSecureView = false;
|
||||
@ -80,8 +83,11 @@ public final class ExoPlayerView extends FrameLayout {
|
||||
|
||||
updateSurfaceView();
|
||||
|
||||
adOverlayFrameLayout = new FrameLayout(context);
|
||||
|
||||
layout.addView(shutterView, 1, layoutParams);
|
||||
layout.addView(subtitleLayout, 2, layoutParams);
|
||||
layout.addView(adOverlayFrameLayout, 3, layoutParams);
|
||||
|
||||
addViewInLayout(layout, 0, aspectRatioParams);
|
||||
}
|
||||
@ -139,6 +145,19 @@ public final class ExoPlayerView extends FrameLayout {
|
||||
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");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link ExoPlayer} to use. The {@link ExoPlayer#addListener} method of the
|
||||
* player will be called and previous
|
||||
|
@ -31,6 +31,7 @@ import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.uimanager.ThemedReactContext;
|
||||
import com.facebook.react.util.RNLog;
|
||||
import com.google.ads.interactivemedia.v3.api.AdEvent;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.DefaultLoadControl;
|
||||
import com.google.android.exoplayer2.DefaultRenderersFactory;
|
||||
@ -75,11 +76,11 @@ import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
||||
import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
|
||||
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionParameters;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionOverride;
|
||||
import com.google.android.exoplayer2.ui.PlayerControlView;
|
||||
import com.google.android.exoplayer2.upstream.BandwidthMeter;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
import com.google.android.exoplayer2.upstream.DataSpec;
|
||||
import com.google.android.exoplayer2.upstream.DefaultAllocator;
|
||||
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
|
||||
import com.google.android.exoplayer2.upstream.HttpDataSource;
|
||||
@ -93,6 +94,11 @@ import com.google.android.exoplayer2.source.dash.manifest.AdaptationSet;
|
||||
import com.google.android.exoplayer2.source.dash.manifest.Representation;
|
||||
import com.google.android.exoplayer2.source.dash.manifest.Descriptor;
|
||||
|
||||
import com.google.android.exoplayer2.ext.ima.ImaAdsLoader;
|
||||
import com.google.android.exoplayer2.source.ads.AdsMediaSource;
|
||||
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.net.CookieHandler;
|
||||
import java.net.CookieManager;
|
||||
import java.net.CookiePolicy;
|
||||
@ -119,7 +125,8 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
BandwidthMeter.EventListener,
|
||||
BecomingNoisyListener,
|
||||
AudioManager.OnAudioFocusChangeListener,
|
||||
DrmSessionEventListener {
|
||||
DrmSessionEventListener,
|
||||
AdEvent.AdEventListener {
|
||||
|
||||
public static final double DEFAULT_MAX_HEAP_ALLOCATION_PERCENT = 1;
|
||||
public static final double DEFAULT_MIN_BACK_BUFFER_MEMORY_RESERVE = 0;
|
||||
@ -144,6 +151,7 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
|
||||
private ExoPlayerView exoPlayerView;
|
||||
private FullScreenPlayerView fullScreenPlayerView;
|
||||
private ImaAdsLoader adsLoader;
|
||||
|
||||
private DataSource.Factory mediaDataSourceFactory;
|
||||
private ExoPlayer player;
|
||||
@ -203,6 +211,7 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
private String drmLicenseUrl = null;
|
||||
private String[] drmLicenseHeader = null;
|
||||
private boolean controls;
|
||||
private Uri adTagUrl;
|
||||
// \ End props
|
||||
|
||||
// React
|
||||
@ -221,6 +230,9 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
switch (msg.what) {
|
||||
case SHOW_PROGRESS:
|
||||
if (player != null) {
|
||||
if (playerControlView != null && isPlayingAd() && controls) {
|
||||
playerControlView.hide();
|
||||
}
|
||||
long pos = player.getCurrentPosition();
|
||||
long bufferedDuration = player.getBufferedPercentage() * player.getDuration() / 100;
|
||||
long duration = player.getDuration();
|
||||
@ -263,6 +275,9 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
audioBecomingNoisyReceiver = new AudioBecomingNoisyReceiver(themedReactContext);
|
||||
}
|
||||
|
||||
private boolean isPlayingAd() {
|
||||
return player != null && player.isPlayingAd();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setId(int id) {
|
||||
@ -389,7 +404,9 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
exoPlayerView.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
togglePlayerControlVisibility();
|
||||
if (!isPlayingAd()) {
|
||||
togglePlayerControlVisibility();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -607,13 +624,22 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
DefaultRenderersFactory renderersFactory =
|
||||
new DefaultRenderersFactory(getContext())
|
||||
.setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF);
|
||||
|
||||
// Create an AdsLoader.
|
||||
adsLoader = new ImaAdsLoader.Builder(themedReactContext).setAdEventListener(this).build();
|
||||
|
||||
MediaSource.Factory mediaSourceFactory = new DefaultMediaSourceFactory(mediaDataSourceFactory)
|
||||
.setLocalAdInsertionComponents(unusedAdTagUri -> adsLoader, exoPlayerView);
|
||||
|
||||
player = new ExoPlayer.Builder(getContext(), renderersFactory)
|
||||
.setTrackSelector(self.trackSelector)
|
||||
.setBandwidthMeter(bandwidthMeter)
|
||||
.setLoadControl(loadControl)
|
||||
.setMediaSourceFactory(mediaSourceFactory)
|
||||
.build();
|
||||
player.addListener(self);
|
||||
exoPlayerView.setPlayer(player);
|
||||
adsLoader.setPlayer(player);
|
||||
audioBecomingNoisyReceiver.setListener(self);
|
||||
bandwidthMeter.addEventListener(new Handler(), self);
|
||||
setPlayWhenReady(!isPaused);
|
||||
@ -643,11 +669,26 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
private void initializePlayerSource(ReactExoplayerView self, DrmSessionManager drmSessionManager) {
|
||||
ArrayList<MediaSource> mediaSourceList = buildTextSources();
|
||||
MediaSource videoSource = buildMediaSource(self.srcUri, self.extension, drmSessionManager);
|
||||
MediaSource mediaSourceWithAds = null;
|
||||
if (adTagUrl != null) {
|
||||
MediaSource.Factory mediaSourceFactory = new DefaultMediaSourceFactory(mediaDataSourceFactory)
|
||||
.setLocalAdInsertionComponents(unusedAdTagUri -> adsLoader, exoPlayerView);
|
||||
DataSpec adTagDataSpec = new DataSpec(adTagUrl);
|
||||
mediaSourceWithAds = new AdsMediaSource(videoSource, adTagDataSpec, ImmutableList.of(srcUri, adTagUrl), mediaSourceFactory, adsLoader, exoPlayerView);
|
||||
}
|
||||
MediaSource mediaSource;
|
||||
if (mediaSourceList.size() == 0) {
|
||||
mediaSource = videoSource;
|
||||
if (mediaSourceWithAds != null) {
|
||||
mediaSource = mediaSourceWithAds;
|
||||
} else {
|
||||
mediaSource = videoSource;
|
||||
}
|
||||
} else {
|
||||
mediaSourceList.add(0, videoSource);
|
||||
if (mediaSourceWithAds != null) {
|
||||
mediaSourceList.add(0, mediaSourceWithAds);
|
||||
} else {
|
||||
mediaSourceList.add(0, videoSource);
|
||||
}
|
||||
MediaSource[] textSourceArray = mediaSourceList.toArray(
|
||||
new MediaSource[mediaSourceList.size()]
|
||||
);
|
||||
@ -729,7 +770,17 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
int type = Util.inferContentType(!TextUtils.isEmpty(overrideExtension) ? "." + overrideExtension
|
||||
: uri.getLastPathSegment());
|
||||
config.setDisableDisconnectError(this.disableDisconnectError);
|
||||
MediaItem mediaItem = new MediaItem.Builder().setUri(uri).build();
|
||||
|
||||
MediaItem.Builder mediaItemBuilder = new MediaItem.Builder().setUri(uri);
|
||||
|
||||
if (adTagUrl != null) {
|
||||
mediaItemBuilder.setAdsConfiguration(
|
||||
new MediaItem.AdsConfiguration.Builder(adTagUrl).build()
|
||||
);
|
||||
}
|
||||
|
||||
MediaItem mediaItem = mediaItemBuilder.build();
|
||||
|
||||
DrmSessionManagerProvider drmProvider = null;
|
||||
if (drmSessionManager != null) {
|
||||
drmProvider = new DrmSessionManagerProvider() {
|
||||
@ -813,12 +864,15 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
|
||||
private void releasePlayer() {
|
||||
if (player != null) {
|
||||
adsLoader.setPlayer(null);
|
||||
updateResumePosition();
|
||||
player.release();
|
||||
player.removeListener(this);
|
||||
trackSelector = null;
|
||||
player = null;
|
||||
}
|
||||
adsLoader.release();
|
||||
adsLoader = null;
|
||||
progressHandler.removeMessages(SHOW_PROGRESS);
|
||||
themedReactContext.removeLifecycleEventListener(this);
|
||||
audioBecomingNoisyReceiver.removeListener();
|
||||
@ -1049,7 +1103,7 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
}
|
||||
|
||||
private void videoLoaded() {
|
||||
if (loadVideoStarted) {
|
||||
if (!player.isPlayingAd() && loadVideoStarted) {
|
||||
loadVideoStarted = false;
|
||||
if (audioTrackType != null) {
|
||||
setSelectedAudioTrack(audioTrackType, audioTrackValue);
|
||||
@ -1419,6 +1473,10 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
mReportBandwidth = reportBandwidth;
|
||||
}
|
||||
|
||||
public void setAdTagUrl(final Uri uri) {
|
||||
adTagUrl = uri;
|
||||
}
|
||||
|
||||
public void setRawSrc(final Uri uri, final String extension) {
|
||||
if (uri != null) {
|
||||
boolean isSourceEqual = uri.equals(srcUri);
|
||||
@ -1917,4 +1975,9 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
public void setSubtitleStyle(SubtitleStyle style) {
|
||||
exoPlayerView.setSubtitleStyle(style);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAdEvent(AdEvent adEvent) {
|
||||
eventEmitter.receiveAdEvent(adEvent.getType().name());
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ public class ReactExoplayerViewManager extends ViewGroupManager<ReactExoplayerVi
|
||||
|
||||
private static final String PROP_SRC = "src";
|
||||
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_DRM = "drm";
|
||||
private static final String PROP_DRM_TYPE = "type";
|
||||
@ -189,6 +190,18 @@ 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;
|
||||
}
|
||||
|
||||
Uri adTagUrl = Uri.parse(uriString);
|
||||
|
||||
videoView.setAdTagUrl(adTagUrl);
|
||||
}
|
||||
|
||||
|
||||
@ReactProp(name = PROP_RESIZE_MODE)
|
||||
public void setResizeMode(final ReactExoplayerView videoView, final String 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_FOCUS_CHANGE = "onAudioFocusChanged";
|
||||
private static final String EVENT_PLAYBACK_RATE_CHANGE = "onPlaybackRateChange";
|
||||
private static final String EVENT_ON_RECEIVE_AD_EVENT = "onReceiveAdEvent";
|
||||
|
||||
static final String[] Events = {
|
||||
EVENT_LOAD_START,
|
||||
@ -73,6 +74,7 @@ class VideoEventEmitter {
|
||||
EVENT_AUDIO_FOCUS_CHANGE,
|
||||
EVENT_PLAYBACK_RATE_CHANGE,
|
||||
EVENT_BANDWIDTH,
|
||||
EVENT_ON_RECEIVE_AD_EVENT
|
||||
};
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@ -98,6 +100,7 @@ class VideoEventEmitter {
|
||||
EVENT_AUDIO_FOCUS_CHANGE,
|
||||
EVENT_PLAYBACK_RATE_CHANGE,
|
||||
EVENT_BANDWIDTH,
|
||||
EVENT_ON_RECEIVE_AD_EVENT
|
||||
})
|
||||
@interface VideoEvents {
|
||||
}
|
||||
@ -330,6 +333,13 @@ class VideoEventEmitter {
|
||||
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) {
|
||||
eventEmitter.receiveEvent(viewId, type, event);
|
||||
}
|
||||
|
@ -237,7 +237,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 7.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
@ -271,7 +271,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 7.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
|
187
ios/Video/Features/RCTIMAAdsManager.swift
Normal file
187
ios/Video/Features/RCTIMAAdsManager.swift
Normal file
@ -0,0 +1,187 @@
|
||||
import Foundation
|
||||
import GoogleInteractiveMediaAds
|
||||
|
||||
class RCTIMAAdsManager: NSObject, IMAAdsLoaderDelegate, IMAAdsManagerDelegate {
|
||||
|
||||
private var _video:RCTVideo
|
||||
|
||||
/* Entry point for the SDK. Used to make ad requests. */
|
||||
private var adsLoader: IMAAdsLoader!
|
||||
/* Main point of interaction with the SDK. Created by the SDK as the result of an ad request. */
|
||||
private var adsManager: IMAAdsManager!
|
||||
|
||||
init(video:RCTVideo!) {
|
||||
_video = video
|
||||
|
||||
super.init()
|
||||
}
|
||||
|
||||
func setUpAdsLoader() {
|
||||
adsLoader = IMAAdsLoader(settings: nil)
|
||||
adsLoader.delegate = self
|
||||
}
|
||||
|
||||
func requestAds() {
|
||||
// Create ad display container for ad rendering.
|
||||
let adDisplayContainer = IMAAdDisplayContainer(adContainer: _video, viewController: _video.reactViewController())
|
||||
|
||||
let adTagUrl = _video.getAdTagUrl()
|
||||
let contentPlayhead = _video.getContentPlayhead()
|
||||
|
||||
if adTagUrl != nil && contentPlayhead != nil {
|
||||
// Create an ad request with our ad tag, display container, and optional user context.
|
||||
let request = IMAAdsRequest(
|
||||
adTagUrl: adTagUrl!,
|
||||
adDisplayContainer: adDisplayContainer,
|
||||
contentPlayhead: contentPlayhead,
|
||||
userContext: nil)
|
||||
|
||||
adsLoader.requestAds(with: request)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Getters
|
||||
|
||||
func getAdsLoader() -> IMAAdsLoader? {
|
||||
return adsLoader
|
||||
}
|
||||
|
||||
func getAdsManager() -> IMAAdsManager? {
|
||||
return adsManager
|
||||
}
|
||||
|
||||
// MARK: - IMAAdsLoaderDelegate
|
||||
|
||||
func adsLoader(_ loader: IMAAdsLoader, adsLoadedWith adsLoadedData: IMAAdsLoadedData) {
|
||||
// Grab the instance of the IMAAdsManager and set yourself as the delegate.
|
||||
adsManager = adsLoadedData.adsManager
|
||||
adsManager?.delegate = self
|
||||
|
||||
|
||||
// Create ads rendering settings and tell the SDK to use the in-app browser.
|
||||
let adsRenderingSettings: IMAAdsRenderingSettings = IMAAdsRenderingSettings();
|
||||
adsRenderingSettings.linkOpenerPresentingController = _video.reactViewController();
|
||||
|
||||
adsManager.initialize(with: adsRenderingSettings)
|
||||
}
|
||||
|
||||
func adsLoader(_ loader: IMAAdsLoader, failedWith adErrorData: IMAAdLoadingErrorData) {
|
||||
if adErrorData.adError.message != nil {
|
||||
print("Error loading ads: " + adErrorData.adError.message!)
|
||||
}
|
||||
|
||||
_video.setPaused(false)
|
||||
}
|
||||
|
||||
// MARK: - IMAAdsManagerDelegate
|
||||
|
||||
func adsManager(_ adsManager: IMAAdsManager, didReceive event: IMAAdEvent) {
|
||||
// Play each ad once it has been loaded
|
||||
if event.type == IMAAdEventType.LOADED {
|
||||
adsManager.start()
|
||||
}
|
||||
|
||||
if _video.onReceiveAdEvent != nil {
|
||||
let type = convertEventToString(event: event.type)
|
||||
|
||||
_video.onReceiveAdEvent?([
|
||||
"event": type,
|
||||
"target": _video.reactTag!
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
func adsManager(_ adsManager: IMAAdsManager, didReceive error: IMAAdError) {
|
||||
if error.message != nil {
|
||||
print("AdsManager error: " + error.message!)
|
||||
}
|
||||
|
||||
// Fall back to playing content
|
||||
_video.setPaused(false)
|
||||
}
|
||||
|
||||
func adsManagerDidRequestContentPause(_ adsManager: IMAAdsManager) {
|
||||
// Pause the content for the SDK to play ads.
|
||||
_video.setPaused(true)
|
||||
_video.setAdPlaying(true)
|
||||
}
|
||||
|
||||
func adsManagerDidRequestContentResume(_ adsManager: IMAAdsManager) {
|
||||
// Resume the content since the SDK is done playing ads (at least for now).
|
||||
_video.setAdPlaying(false)
|
||||
_video.setPaused(false)
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
func convertEventToString(event: IMAAdEventType!) -> String {
|
||||
var result = "UNKNOWN";
|
||||
|
||||
switch(event) {
|
||||
case .AD_BREAK_READY:
|
||||
result = "AD_BREAK_READY";
|
||||
break;
|
||||
case .AD_BREAK_ENDED:
|
||||
result = "AD_BREAK_ENDED";
|
||||
break;
|
||||
case .AD_BREAK_STARTED:
|
||||
result = "AD_BREAK_STARTED";
|
||||
break;
|
||||
case .AD_PERIOD_ENDED:
|
||||
result = "AD_PERIOD_ENDED";
|
||||
break;
|
||||
case .AD_PERIOD_STARTED:
|
||||
result = "AD_PERIOD_STARTED";
|
||||
break;
|
||||
case .ALL_ADS_COMPLETED:
|
||||
result = "ALL_ADS_COMPLETED";
|
||||
break;
|
||||
case .CLICKED:
|
||||
result = "CLICK";
|
||||
break;
|
||||
case .COMPLETE:
|
||||
result = "COMPLETE";
|
||||
break;
|
||||
case .CUEPOINTS_CHANGED:
|
||||
result = "CUEPOINTS_CHANGED";
|
||||
break;
|
||||
case .FIRST_QUARTILE:
|
||||
result = "FIRST_QUARTILE";
|
||||
break;
|
||||
case .LOADED:
|
||||
result = "LOADED";
|
||||
break;
|
||||
case .LOG:
|
||||
result = "LOG";
|
||||
break;
|
||||
case .MIDPOINT:
|
||||
result = "MIDPOINT";
|
||||
break;
|
||||
case .PAUSE:
|
||||
result = "PAUSED";
|
||||
break;
|
||||
case .RESUME:
|
||||
result = "RESUMED";
|
||||
break;
|
||||
case .SKIPPED:
|
||||
result = "SKIPPED";
|
||||
break;
|
||||
case .STARTED:
|
||||
result = "STARTED";
|
||||
break;
|
||||
case .STREAM_LOADED:
|
||||
result = "STREAM_LOADED";
|
||||
break;
|
||||
case .TAPPED:
|
||||
result = "TAPPED";
|
||||
break;
|
||||
case .THIRD_QUARTILE:
|
||||
result = "THIRD_QUARTILE";
|
||||
break;
|
||||
default:
|
||||
result = "UNKNOWN";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
import AVFoundation
|
||||
import AVKit
|
||||
import Foundation
|
||||
import GoogleInteractiveMediaAds
|
||||
import React
|
||||
import Promises
|
||||
|
||||
@ -60,6 +61,14 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
|
||||
private var _filterEnabled:Bool = false
|
||||
private var _presentingViewController:UIViewController?
|
||||
|
||||
/* IMA Ads */
|
||||
private var _adTagUrl:String?
|
||||
private var _imaAdsManager: RCTIMAAdsManager!
|
||||
private var _didRequestAds:Bool = false
|
||||
private var _adPlaying:Bool = false
|
||||
/* Playhead used by the SDK to track content video progress and insert mid-rolls. */
|
||||
private var _contentPlayhead: IMAAVPlayerContentPlayhead?
|
||||
|
||||
private var _resouceLoaderDelegate: RCTResourceLoaderDelegate?
|
||||
private var _playerObserver: RCTPlayerObserver = RCTPlayerObserver()
|
||||
|
||||
@ -94,10 +103,13 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
|
||||
@objc var onPictureInPictureStatusChanged: RCTDirectEventBlock?
|
||||
@objc var onRestoreUserInterfaceForPictureInPictureStop: RCTDirectEventBlock?
|
||||
@objc var onGetLicense: RCTDirectEventBlock?
|
||||
@objc var onReceiveAdEvent: RCTDirectEventBlock?
|
||||
|
||||
init(eventDispatcher:RCTEventDispatcher!) {
|
||||
super.init(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
|
||||
|
||||
_imaAdsManager = RCTIMAAdsManager(video: self)
|
||||
|
||||
_eventDispatcher = eventDispatcher
|
||||
|
||||
NotificationCenter.default.addObserver(
|
||||
@ -135,6 +147,8 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
|
||||
_imaAdsManager = RCTIMAAdsManager(video: self)
|
||||
}
|
||||
|
||||
deinit {
|
||||
@ -203,6 +217,10 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
|
||||
])
|
||||
|
||||
if currentTimeSecs >= 0 {
|
||||
if !_didRequestAds && currentTimeSecs >= 0.0001 && _adTagUrl != nil {
|
||||
_imaAdsManager.requestAds()
|
||||
_didRequestAds = true
|
||||
}
|
||||
onVideoProgress?([
|
||||
"currentTime": NSNumber(value: Float(currentTimeSecs)),
|
||||
"playableDuration": RCTVideoUtils.calculatePlayableDuration(_player),
|
||||
@ -292,6 +310,13 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
|
||||
self.setAutomaticallyWaitsToMinimizeStalling(self._automaticallyWaitsToMinimizeStalling)
|
||||
}
|
||||
|
||||
if self._adTagUrl != nil {
|
||||
// Set up your content playhead and contentComplete callback.
|
||||
self._contentPlayhead = IMAAVPlayerContentPlayhead(avPlayer: self._player!)
|
||||
|
||||
self._imaAdsManager.setUpAdsLoader()
|
||||
}
|
||||
|
||||
//Perform on next run loop, otherwise onVideoLoadStart is nil
|
||||
self.onVideoLoadStart?([
|
||||
"src": [
|
||||
@ -400,18 +425,26 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
|
||||
@objc
|
||||
func setPaused(_ paused:Bool) {
|
||||
if paused {
|
||||
_player?.pause()
|
||||
_player?.rate = 0.0
|
||||
if _adPlaying {
|
||||
_imaAdsManager.getAdsManager()?.pause()
|
||||
} else {
|
||||
_player?.pause()
|
||||
_player?.rate = 0.0
|
||||
}
|
||||
} else {
|
||||
RCTPlayerOperations.configureAudio(ignoreSilentSwitch:_ignoreSilentSwitch, mixWithOthers:_mixWithOthers)
|
||||
|
||||
if #available(iOS 10.0, *), !_automaticallyWaitsToMinimizeStalling {
|
||||
_player?.playImmediately(atRate: _rate)
|
||||
if _adPlaying {
|
||||
_imaAdsManager.getAdsManager()?.resume()
|
||||
} else {
|
||||
_player?.play()
|
||||
if #available(iOS 10.0, *), !_automaticallyWaitsToMinimizeStalling {
|
||||
_player?.playImmediately(atRate: _rate)
|
||||
} else {
|
||||
_player?.play()
|
||||
_player?.rate = _rate
|
||||
}
|
||||
_player?.rate = _rate
|
||||
}
|
||||
_player?.rate = _rate
|
||||
}
|
||||
|
||||
_paused = paused
|
||||
@ -782,6 +815,25 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
|
||||
_filterEnabled = filterEnabled
|
||||
}
|
||||
|
||||
// MARK: - RCTIMAAdsManager
|
||||
|
||||
func getAdTagUrl() -> String? {
|
||||
return _adTagUrl
|
||||
}
|
||||
|
||||
@objc
|
||||
func setAdTagUrl(_ adTagUrl:String!) {
|
||||
_adTagUrl = adTagUrl
|
||||
}
|
||||
|
||||
func getContentPlayhead() -> IMAAVPlayerContentPlayhead? {
|
||||
return _contentPlayhead
|
||||
}
|
||||
|
||||
func setAdPlaying(_ adPlaying:Bool) {
|
||||
_adPlaying = adPlaying
|
||||
}
|
||||
|
||||
// MARK: - React View Management
|
||||
|
||||
func insertReactSubview(view:UIView!, atIndex:Int) {
|
||||
@ -1059,9 +1111,13 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
|
||||
@objc func handlePlayerItemDidReachEnd(notification:NSNotification!) {
|
||||
onVideoEnd?(["target": reactTag as Any])
|
||||
|
||||
if notification.object as? AVPlayerItem == _player?.currentItem {
|
||||
_imaAdsManager.getAdsLoader()?.contentComplete()
|
||||
}
|
||||
|
||||
if _repeat {
|
||||
let item:AVPlayerItem! = notification.object as? AVPlayerItem
|
||||
item.seek(to: CMTime.zero)
|
||||
item.seek(to: CMTime.zero, completionHandler: nil)
|
||||
self.applyModifiers()
|
||||
} else {
|
||||
self.setPaused(true);
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(src, NSDictionary);
|
||||
RCT_EXPORT_VIEW_PROPERTY(drm, NSDictionary);
|
||||
RCT_EXPORT_VIEW_PROPERTY(adTagUrl, NSString);
|
||||
RCT_EXPORT_VIEW_PROPERTY(maxBitRate, float);
|
||||
RCT_EXPORT_VIEW_PROPERTY(resizeMode, NSString);
|
||||
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(onPictureInPictureStatusChanged, RCTDirectEventBlock);
|
||||
RCT_EXPORT_VIEW_PROPERTY(onRestoreUserInterfaceForPictureInPictureStop, RCTDirectEventBlock);
|
||||
RCT_EXPORT_VIEW_PROPERTY(onReceiveAdEvent, RCTDirectEventBlock);
|
||||
|
||||
RCT_EXTERN_METHOD(save:(NSDictionary *)options
|
||||
reactTag:(nonnull NSNumber *)reactTag
|
||||
|
@ -18,6 +18,9 @@ Pod::Spec.new do |s|
|
||||
s.subspec "Video" do |ss|
|
||||
ss.source_files = "ios/Video/**/*.{h,m,swift}"
|
||||
ss.dependency "PromisesSwift"
|
||||
|
||||
ss.ios.dependency 'GoogleAds-IMA-iOS-SDK', '~> 3.18.1'
|
||||
ss.tvos.dependency 'GoogleAds-IMA-tvOS-SDK', '~> 4.2'
|
||||
end
|
||||
|
||||
s.subspec "VideoCaching" do |ss|
|
||||
|
Loading…
Reference in New Issue
Block a user