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 8d30b2b1..8fadc334 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.ExoPlayer; 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 class ImaAdsLoader implements AdsLoader { } public static class Builder { + private ImaSdkSettings imaSdkSettings; public Builder(Context ignoredThemedReactContext) { } @@ -56,6 +65,11 @@ public class ImaAdsLoader implements AdsLoader { 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 bb527903..dacae937 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.facebook.react.uimanager.ThemedReactContext; 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 @@ public class ReactExoplayerView extends FrameLayout implements .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 class ReactExoplayerView extends FrameLayout implements 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 195875be..bb607671 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 00000000..6580b4ff --- /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 00000000..82887395 --- /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 d9804baf..5b5f1857 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 26c6213e..a6548f3f 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 c394c8e9..f1ca220f 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 3807f955..dab191e7 100644 --- a/ios/Video/RCTVideoManager.m +++ b/ios/Video/RCTVideoManager.m @@ -6,6 +6,7 @@ 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 146dee11..a5eba10c 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 aa575128..d29d0108 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