From 703ed4399667e0142704d19686563dd62fb4883d Mon Sep 17 00:00:00 2001 From: Guy Haguy <68995744+guyhguy25@users.noreply.github.com> Date: Thu, 29 Aug 2024 13:30:05 +0300 Subject: [PATCH] feat: add ads localize (#4113) * add prop adLanguage; add docs * add native code ios&android for adLanguage props * add missing function to adsLoader and imafactory * Update docs/pages/component/ads.md Language correction Co-authored-by: Olivier Bouillet <62574056+freeboub@users.noreply.github.com> --------- Co-authored-by: Guy Co-authored-by: Olivier Bouillet <62574056+freeboub@users.noreply.github.com> --- .../media3/exoplayer/ima/ImaAdsLoader.java | 14 +++++++++++ .../exoplayer/ReactExoplayerView.java | 11 +++++++++ .../exoplayer/ReactExoplayerViewManager.kt | 11 +++++++++ .../v3/api/ImaSdkFactory.java | 22 +++++++++++++++++ .../v3/api/ImaSdkSettings.java | 24 +++++++++++++++++++ docs/pages/component/ads.md | 13 ++++++++++ ios/Video/Features/RCTIMAAdsManager.swift | 7 +++++- ios/Video/RCTVideo.swift | 5 ++++ ios/Video/RCTVideoManager.m | 1 + src/specs/VideoNativeComponent.ts | 1 + src/types/video.ts | 1 + 11 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 android/src/main/java/com/google/ads/interactivemedia/v3/api/ImaSdkFactory.java create mode 100644 android/src/main/java/com/google/ads/interactivemedia/v3/api/ImaSdkSettings.java diff --git a/android/src/main/java/androidx/media3/exoplayer/ima/ImaAdsLoader.java b/android/src/main/java/androidx/media3/exoplayer/ima/ImaAdsLoader.java index 8d30b2b193..8fadc33471 100644 --- a/android/src/main/java/androidx/media3/exoplayer/ima/ImaAdsLoader.java +++ b/android/src/main/java/androidx/media3/exoplayer/ima/ImaAdsLoader.java @@ -11,9 +11,17 @@ import androidx.media3.exoplayer.source.ads.AdsLoader; import androidx.media3.exoplayer.source.ads.AdsMediaSource; +import com.google.ads.interactivemedia.v3.api.ImaSdkSettings; + import java.io.IOException; public class ImaAdsLoader implements AdsLoader { + private final ImaSdkSettings imaSdkSettings; + + public ImaAdsLoader(ImaSdkSettings imaSdkSettings) { + this.imaSdkSettings = imaSdkSettings; + } + public void setPlayer(ExoPlayer ignoredPlayer) { } @@ -45,6 +53,7 @@ public void handlePrepareError(@NonNull AdsMediaSource adsMediaSource, int i, in } public static class Builder { + private ImaSdkSettings imaSdkSettings; public Builder(Context ignoredThemedReactContext) { } @@ -56,6 +65,11 @@ public Builder setAdErrorListener(Object ignoredReactExoplayerView) { return this; } + public Builder setImaSdkSettings(ImaSdkSettings imaSdkSettings) { + this.imaSdkSettings = imaSdkSettings; + return this; + } + public ImaAdsLoader build() { return null; } diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index bb5279032d..dacae93748 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -133,6 +133,8 @@ import com.google.ads.interactivemedia.v3.api.AdError; import com.google.ads.interactivemedia.v3.api.AdEvent; import com.google.ads.interactivemedia.v3.api.AdErrorEvent; +import com.google.ads.interactivemedia.v3.api.ImaSdkSettings; +import com.google.ads.interactivemedia.v3.api.ImaSdkFactory; import com.google.common.collect.ImmutableList; import java.net.CookieHandler; @@ -251,6 +253,7 @@ public class ReactExoplayerView extends FrameLayout implements private boolean mReportBandwidth = false; private boolean controls; private Uri adTagUrl; + private String adLanguage; private boolean showNotificationControls = false; // \ End props @@ -754,9 +757,13 @@ private void initializePlayerCore(ReactExoplayerView self) { .setEnableDecoderFallback(true) .forceEnableMediaCodecAsynchronousQueueing(); + ImaSdkSettings imaSdkSettings = ImaSdkFactory.getInstance().createImaSdkSettings(); + imaSdkSettings.setLanguage(adLanguage); + // Create an AdsLoader. adsLoader = new ImaAdsLoader .Builder(themedReactContext) + .setImaSdkSettings(imaSdkSettings) .setAdEventListener(this) .setAdErrorListener(this) .build(); @@ -1851,6 +1858,10 @@ public void setAdTagUrl(final Uri uri) { adTagUrl = uri; } + public void setAdLanguage(final String language) { + adLanguage = language; + } + public void setTextTracks(SideLoadedTextTrackList textTracks) { this.textTracks = textTracks; reloadSource(); // FIXME Shall be moved inside source diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.kt b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.kt index 195875be5b..bb607671b1 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.kt +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.kt @@ -29,6 +29,7 @@ class ReactExoplayerViewManager(private val config: ReactExoplayerConfig) : View 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" @@ -110,6 +111,16 @@ class ReactExoplayerViewManager(private val config: ReactExoplayerConfig) : View 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/android/src/main/java/com/google/ads/interactivemedia/v3/api/ImaSdkFactory.java b/android/src/main/java/com/google/ads/interactivemedia/v3/api/ImaSdkFactory.java new file mode 100644 index 0000000000..6580b4ffe2 --- /dev/null +++ b/android/src/main/java/com/google/ads/interactivemedia/v3/api/ImaSdkFactory.java @@ -0,0 +1,22 @@ +package com.google.ads.interactivemedia.v3.api; + +public abstract class ImaSdkFactory { + private static ImaSdkFactory instance; + + public abstract ImaSdkSettings createImaSdkSettings(); + + public static ImaSdkFactory getInstance() { + if (instance == null) { + instance = new ConcreteImaSdkFactory(); + } + return instance; + } +} + +class ConcreteImaSdkFactory extends ImaSdkFactory { + + @Override + public ImaSdkSettings createImaSdkSettings() { + return new ConcreteImaSdkSettings(); + } +} \ No newline at end of file diff --git a/android/src/main/java/com/google/ads/interactivemedia/v3/api/ImaSdkSettings.java b/android/src/main/java/com/google/ads/interactivemedia/v3/api/ImaSdkSettings.java new file mode 100644 index 0000000000..82887395c6 --- /dev/null +++ b/android/src/main/java/com/google/ads/interactivemedia/v3/api/ImaSdkSettings.java @@ -0,0 +1,24 @@ +package com.google.ads.interactivemedia.v3.api; + +import androidx.annotation.InspectableProperty; + +public abstract class ImaSdkSettings { + public abstract String getLanguage(); + public abstract void setLanguage(String language); +} + +// Concrete Implementation +class ConcreteImaSdkSettings extends ImaSdkSettings { + + private String language; + + @Override + public String getLanguage() { + return language; + } + + @Override + public void setLanguage(String language) { + this.language = language; + } +} diff --git a/docs/pages/component/ads.md b/docs/pages/component/ads.md index d9804baf7f..5b5f185740 100644 --- a/docs/pages/component/ads.md +++ b/docs/pages/component/ads.md @@ -23,3 +23,16 @@ Example: onReceiveAdEvent={event => console.log(event)} ... ``` + +### Localization +To change the language of the IMA SDK, you need to pass `adLanguage` prop to `Video` component. List of supported languages, you can find [here](https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/localization#locale-codes) + +By default, ios will use system language and android will use `en` + +Example: + +```jsx +... +adLanguage="fr" +... +``` diff --git a/ios/Video/Features/RCTIMAAdsManager.swift b/ios/Video/Features/RCTIMAAdsManager.swift index 26c6213e2b..a6548f3f02 100644 --- a/ios/Video/Features/RCTIMAAdsManager.swift +++ b/ios/Video/Features/RCTIMAAdsManager.swift @@ -19,7 +19,12 @@ } func setUpAdsLoader() { - adsLoader = IMAAdsLoader(settings: nil) + guard let _video else { return } + let settings = IMASettings() + if let adLanguage = _video.getAdLanguage() { + settings.language = adLanguage + } + adsLoader = IMAAdsLoader(settings: settings) adsLoader.delegate = self } diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index c394c8e90b..f1ca220f17 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -87,6 +87,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH /* IMA Ads */ private var _adTagUrl: String? + private var _adLanguage: String? #if USE_GOOGLE_IMA private var _imaAdsManager: RCTIMAAdsManager! /* Playhead used by the SDK to track content video progress and insert mid-rolls. */ @@ -1222,6 +1223,10 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH // MARK: - RCTIMAAdsManager + func getAdLanguage() -> String? { + return _adLanguage + } + func getAdTagUrl() -> String? { return _adTagUrl } diff --git a/ios/Video/RCTVideoManager.m b/ios/Video/RCTVideoManager.m index 3807f955ba..dab191e7cf 100644 --- a/ios/Video/RCTVideoManager.m +++ b/ios/Video/RCTVideoManager.m @@ -6,6 +6,7 @@ @interface RCT_EXTERN_MODULE (RCTVideoManager, RCTViewManager) RCT_EXPORT_VIEW_PROPERTY(src, NSDictionary); RCT_EXPORT_VIEW_PROPERTY(drm, NSDictionary); RCT_EXPORT_VIEW_PROPERTY(adTagUrl, NSString); +RCT_EXPORT_VIEW_PROPERTY(adLanguage, NSString); RCT_EXPORT_VIEW_PROPERTY(maxBitRate, float); RCT_EXPORT_VIEW_PROPERTY(resizeMode, NSString); RCT_EXPORT_VIEW_PROPERTY(repeat, BOOL); diff --git a/src/specs/VideoNativeComponent.ts b/src/specs/VideoNativeComponent.ts index 146dee11e4..a5eba10c38 100644 --- a/src/specs/VideoNativeComponent.ts +++ b/src/specs/VideoNativeComponent.ts @@ -305,6 +305,7 @@ export type OnControlsVisibilityChange = Readonly<{ export interface VideoNativeProps extends ViewProps { src?: VideoSrc; adTagUrl?: string; + adLanguage?: string; allowsExternalPlayback?: boolean; // ios, true disableFocus?: boolean; // android maxBitRate?: Float; diff --git a/src/types/video.ts b/src/types/video.ts index aa575128dc..d29d0108a9 100644 --- a/src/types/video.ts +++ b/src/types/video.ts @@ -255,6 +255,7 @@ export interface ReactVideoProps extends ReactVideoEvents, ViewProps { drm?: Drm; style?: StyleProp; adTagUrl?: string; + adLanguage?: ISO639_1; audioOutput?: AudioOutput; // Mobile automaticallyWaitsToMinimizeStalling?: boolean; // iOS bufferConfig?: BufferConfig; // Android