diff --git a/android/src/main/java/com/brentvatne/common/api/AdsProps.kt b/android/src/main/java/com/brentvatne/common/api/AdsProps.kt new file mode 100644 index 00000000..9443d537 --- /dev/null +++ b/android/src/main/java/com/brentvatne/common/api/AdsProps.kt @@ -0,0 +1,46 @@ +package com.brentvatne.common.api + +import android.net.Uri +import android.text.TextUtils +import com.brentvatne.common.toolbox.DebugLog +import com.brentvatne.common.toolbox.ReactBridgeUtils +import com.facebook.react.bridge.ReadableMap + +class AdsProps { + var adTagUrl: Uri? = null + var adLanguage: String? = null + + /** return true if this and src are equals */ + override fun equals(other: Any?): Boolean { + if (other == null || other !is AdsProps) return false + return ( + adTagUrl == other.adTagUrl && + adLanguage == other.adLanguage + ) + } + + companion object { + private const val PROP_AD_TAG_URL = "adTagUrl" + private const val PROP_AD_LANGUAGE = "adLanguage" + + @JvmStatic + fun parse(src: ReadableMap?): AdsProps { + val adsProps = AdsProps() + DebugLog.w("olivier", "uri: parse AdsProps") + + if (src != null) { + val uriString = ReactBridgeUtils.safeGetString(src, PROP_AD_TAG_URL) + if (TextUtils.isEmpty(uriString)) { + adsProps.adTagUrl = null + } else { + adsProps.adTagUrl = Uri.parse(uriString) + } + val languageString = ReactBridgeUtils.safeGetString(src, PROP_AD_LANGUAGE) + if (!TextUtils.isEmpty(languageString)) { + adsProps.adLanguage = languageString + } + } + return adsProps + } + } +} diff --git a/android/src/main/java/com/brentvatne/common/api/Source.kt b/android/src/main/java/com/brentvatne/common/api/Source.kt index 1e024022..4437f668 100644 --- a/android/src/main/java/com/brentvatne/common/api/Source.kt +++ b/android/src/main/java/com/brentvatne/common/api/Source.kt @@ -14,6 +14,7 @@ import com.brentvatne.common.toolbox.ReactBridgeUtils.safeGetBool import com.brentvatne.common.toolbox.ReactBridgeUtils.safeGetInt import com.brentvatne.common.toolbox.ReactBridgeUtils.safeGetMap import com.brentvatne.common.toolbox.ReactBridgeUtils.safeGetString +import com.brentvatne.react.BuildConfig import com.facebook.react.bridge.ReadableMap import java.util.Locale import java.util.Objects @@ -65,6 +66,11 @@ class Source { */ var cmcdProps: CMCDProps? = null + /** + * Ads playback properties + */ + var adsProps: AdsProps? = null + /** * The list of sideLoaded text tracks */ @@ -84,7 +90,8 @@ class Source { drmProps == other.drmProps && contentStartTime == other.contentStartTime && cmcdProps == other.cmcdProps && - sideLoadedTextTracks == other.sideLoadedTextTracks + sideLoadedTextTracks == other.sideLoadedTextTracks && + adsProps == other.adsProps ) } @@ -149,6 +156,7 @@ class Source { private const val PROP_SRC_HEADERS = "requestHeaders" private const val PROP_SRC_DRM = "drm" private const val PROP_SRC_CMCD = "cmcd" + private const val PROP_SRC_ADS = "ad" private const val PROP_SRC_TEXT_TRACKS_ALLOW_CHUNKLESS_PREPARATION = "textTracksAllowChunklessPreparation" private const val PROP_SRC_TEXT_TRACKS = "textTracks" @@ -210,6 +218,9 @@ class Source { source.extension = safeGetString(src, PROP_SRC_TYPE, null) source.drmProps = parse(safeGetMap(src, PROP_SRC_DRM)) source.cmcdProps = CMCDProps.parse(safeGetMap(src, PROP_SRC_CMCD)) + if (BuildConfig.USE_EXOPLAYER_IMA) { + source.adsProps = AdsProps.parse(safeGetMap(src, PROP_SRC_ADS)) + } source.textTracksAllowChunklessPreparation = safeGetBool(src, PROP_SRC_TEXT_TRACKS_ALLOW_CHUNKLESS_PREPARATION, true) source.sideLoadedTextTracks = SideLoadedTextTrackList.parse(safeGetArray(src, PROP_SRC_TEXT_TRACKS)) diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index c340e149..cc1e76b7 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -102,6 +102,7 @@ import androidx.media3.extractor.metadata.id3.TextInformationFrame; import androidx.media3.session.MediaSessionService; import androidx.media3.ui.LegacyPlayerControlView; +import com.brentvatne.common.api.AdsProps; import com.brentvatne.common.api.BufferConfig; import com.brentvatne.common.api.BufferingStrategy; import com.brentvatne.common.api.ControlsConfig; @@ -240,8 +241,6 @@ public class ReactExoplayerView extends FrameLayout implements private boolean playInBackground = false; private boolean mReportBandwidth = false; private boolean controls; - private Uri adTagUrl; - private String adLanguage; private boolean showNotificationControls = false; // \ End props @@ -827,16 +826,23 @@ public class ReactExoplayerView extends FrameLayout implements mediaSourceFactory.setDataSourceFactory(RNVSimpleCache.INSTANCE.getCacheFactory(buildHttpDataSourceFactory(true))); } - ImaSdkSettings imaSdkSettings = ImaSdkFactory.getInstance().createImaSdkSettings(); - imaSdkSettings.setLanguage(adLanguage); + if (BuildConfig.USE_EXOPLAYER_IMA) { + AdsProps adProps = source.getAdsProps(); + + // Create an AdsLoader. + ImaAdsLoader.Builder imaLoaderBuilder = new ImaAdsLoader + .Builder(themedReactContext) + .setAdEventListener(this) + .setAdErrorListener(this); + + if (adProps != null && adProps.getAdLanguage() != null) { + ImaSdkSettings imaSdkSettings = ImaSdkFactory.getInstance().createImaSdkSettings(); + imaSdkSettings.setLanguage(adProps.getAdLanguage()); + imaLoaderBuilder.setImaSdkSettings(imaSdkSettings); + } + adsLoader = imaLoaderBuilder.build(); + } - // Create an AdsLoader. - adsLoader = new ImaAdsLoader - .Builder(themedReactContext) - .setImaSdkSettings(imaSdkSettings) - .setAdEventListener(this) - .setAdErrorListener(this) - .build(); mediaSourceFactory.setLocalAdInsertionComponents(unusedAdTagUri -> adsLoader, exoPlayerView); player = new ExoPlayer.Builder(getContext(), renderersFactory) @@ -904,7 +910,11 @@ public class ReactExoplayerView extends FrameLayout implements ArrayList mediaSourceList = buildTextSources(); MediaSource videoSource = buildMediaSource(runningSource.getUri(), runningSource.getExtension(), drmSessionManager, runningSource.getCropStartMs(), runningSource.getCropEndMs()); MediaSource mediaSourceWithAds = null; - if (adTagUrl != null && BuildConfig.USE_EXOPLAYER_IMA) { + Uri adTagUrl = null; + if (source.getAdsProps() != null) { + adTagUrl = source.getAdsProps().getAdTagUrl(); + } + if (adTagUrl != null && adsLoader != null) { DefaultMediaSourceFactory mediaSourceFactory = new DefaultMediaSourceFactory(mediaDataSourceFactory) .setLocalAdInsertionComponents(unusedAdTagUri -> adsLoader, exoPlayerView); DataSpec adTagDataSpec = new DataSpec(adTagUrl); @@ -1105,11 +1115,13 @@ public class ReactExoplayerView extends FrameLayout implements if (customMetadata != null) { mediaItemBuilder.setMediaMetadata(customMetadata); } - - if (adTagUrl != null) { - mediaItemBuilder.setAdsConfiguration( - new MediaItem.AdsConfiguration.Builder(adTagUrl).build() - ); + if (source.getAdsProps() != null) { + Uri adTagUrl = source.getAdsProps().getAdTagUrl(); + if (adTagUrl != null) { + mediaItemBuilder.setAdsConfiguration( + new MediaItem.AdsConfiguration.Builder(adTagUrl).build() + ); + } } MediaItem.LiveConfiguration.Builder liveConfiguration = ConfigurationUtils.getLiveConfiguration(bufferConfig); @@ -1261,8 +1273,8 @@ public class ReactExoplayerView extends FrameLayout implements if (adsLoader != null) { adsLoader.release(); + adsLoader = null; } - adsLoader = null; progressHandler.removeMessages(SHOW_PROGRESS); audioBecomingNoisyReceiver.removeListener(); bandwidthMeter.removeEventListener(this); @@ -1926,15 +1938,6 @@ public class ReactExoplayerView extends FrameLayout implements mReportBandwidth = reportBandwidth; } - public void setAdTagUrl(final Uri uri) { - DebugLog.w(TAG, "setAdTagUrl" + uri); - adTagUrl = uri; - } - - public void setAdLanguage(final String language) { - adLanguage = language; - } - private void reloadSource() { playerNeedsSource = true; initializePlayer(); diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.kt b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.kt index 9921ca03..967da9ff 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.kt +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.kt @@ -1,8 +1,6 @@ package com.brentvatne.exoplayer import android.graphics.Color -import android.net.Uri -import android.text.TextUtils import android.util.Log import com.brentvatne.common.api.BufferConfig import com.brentvatne.common.api.BufferingStrategy @@ -26,8 +24,6 @@ class ReactExoplayerViewManager(private val config: ReactExoplayerConfig) : View private const val TAG = "ExoViewManager" private const val REACT_CLASS = "RCTVideo" private const val PROP_SRC = "src" - private const val PROP_AD_TAG_URL = "adTagUrl" - private const val PROP_AD_LANGUAGE = "adLanguage" private const val PROP_RESIZE_MODE = "resizeMode" private const val PROP_REPEAT = "repeat" private const val PROP_SELECTED_AUDIO_TRACK = "selectedAudioTrack" @@ -92,26 +88,6 @@ class ReactExoplayerViewManager(private val config: ReactExoplayerConfig) : View videoView.setSrc(Source.parse(src, context)) } - @ReactProp(name = PROP_AD_TAG_URL) - fun setAdTagUrl(videoView: ReactExoplayerView, uriString: String?) { - if (TextUtils.isEmpty(uriString)) { - videoView.setAdTagUrl(null) - return - } - val adTagUrl = Uri.parse(uriString) - videoView.setAdTagUrl(adTagUrl) - } - - @ReactProp(name = PROP_AD_LANGUAGE) - fun setAdLanguage(videoView: ReactExoplayerView, languageString: String?) { - if (TextUtils.isEmpty(languageString)) { - videoView.setAdLanguage(null) // Maybe "en" default? - return - } - - videoView.setAdLanguage(languageString) - } - @ReactProp(name = PROP_RESIZE_MODE) fun setResizeMode(videoView: ReactExoplayerView, resizeMode: String) { when (resizeMode) { diff --git a/docs/pages/component/props.mdx b/docs/pages/component/props.mdx index d636dd63..3d2549a2 100644 --- a/docs/pages/component/props.mdx +++ b/docs/pages/component/props.mdx @@ -8,6 +8,9 @@ This page shows the list of available properties to configure player ### `adTagUrl` +> [!WARNING] +> Deprecated, use source.ad.adTagUrl instead + Sets the VAST uri to play AVOD ads. @@ -858,6 +861,26 @@ source={{ }} ``` +### `ad` + + + +Sets the ad configuration. + +Example: + +``` +ad: { + 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=" + adLanguage="fr" +} +``` + +See: [./ads.md] for more informations + +Note: You need enable IMA SDK in gradle or pod file - [enable client side ads insertion](/installation) + + #### `contentStartTime` diff --git a/examples/basic/src/VideoPlayer.tsx b/examples/basic/src/VideoPlayer.tsx index feb4db90..dfe2552f 100644 --- a/examples/basic/src/VideoPlayer.tsx +++ b/examples/basic/src/VideoPlayer.tsx @@ -265,8 +265,7 @@ const VideoPlayer: FC = ({}) => {