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 <guyha@reshet.tv>
Co-authored-by: Olivier Bouillet <62574056+freeboub@users.noreply.github.com>
This commit is contained in:
Guy Haguy 2024-08-29 13:30:05 +03:00 committed by GitHub
parent 9c38d9f4ef
commit 703ed43996
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 109 additions and 1 deletions

View File

@ -11,9 +11,17 @@ import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.exoplayer.source.ads.AdsLoader; import androidx.media3.exoplayer.source.ads.AdsLoader;
import androidx.media3.exoplayer.source.ads.AdsMediaSource; import androidx.media3.exoplayer.source.ads.AdsMediaSource;
import com.google.ads.interactivemedia.v3.api.ImaSdkSettings;
import java.io.IOException; import java.io.IOException;
public class ImaAdsLoader implements AdsLoader { public class ImaAdsLoader implements AdsLoader {
private final ImaSdkSettings imaSdkSettings;
public ImaAdsLoader(ImaSdkSettings imaSdkSettings) {
this.imaSdkSettings = imaSdkSettings;
}
public void setPlayer(ExoPlayer ignoredPlayer) { public void setPlayer(ExoPlayer ignoredPlayer) {
} }
@ -45,6 +53,7 @@ public class ImaAdsLoader implements AdsLoader {
} }
public static class Builder { public static class Builder {
private ImaSdkSettings imaSdkSettings;
public Builder(Context ignoredThemedReactContext) { public Builder(Context ignoredThemedReactContext) {
} }
@ -56,6 +65,11 @@ public class ImaAdsLoader implements AdsLoader {
return this; return this;
} }
public Builder setImaSdkSettings(ImaSdkSettings imaSdkSettings) {
this.imaSdkSettings = imaSdkSettings;
return this;
}
public ImaAdsLoader build() { public ImaAdsLoader build() {
return null; return null;
} }

View File

@ -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.AdError;
import com.google.ads.interactivemedia.v3.api.AdEvent; import com.google.ads.interactivemedia.v3.api.AdEvent;
import com.google.ads.interactivemedia.v3.api.AdErrorEvent; 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 com.google.common.collect.ImmutableList;
import java.net.CookieHandler; import java.net.CookieHandler;
@ -251,6 +253,7 @@ public class ReactExoplayerView extends FrameLayout implements
private boolean mReportBandwidth = false; private boolean mReportBandwidth = false;
private boolean controls; private boolean controls;
private Uri adTagUrl; private Uri adTagUrl;
private String adLanguage;
private boolean showNotificationControls = false; private boolean showNotificationControls = false;
// \ End props // \ End props
@ -754,9 +757,13 @@ public class ReactExoplayerView extends FrameLayout implements
.setEnableDecoderFallback(true) .setEnableDecoderFallback(true)
.forceEnableMediaCodecAsynchronousQueueing(); .forceEnableMediaCodecAsynchronousQueueing();
ImaSdkSettings imaSdkSettings = ImaSdkFactory.getInstance().createImaSdkSettings();
imaSdkSettings.setLanguage(adLanguage);
// Create an AdsLoader. // Create an AdsLoader.
adsLoader = new ImaAdsLoader adsLoader = new ImaAdsLoader
.Builder(themedReactContext) .Builder(themedReactContext)
.setImaSdkSettings(imaSdkSettings)
.setAdEventListener(this) .setAdEventListener(this)
.setAdErrorListener(this) .setAdErrorListener(this)
.build(); .build();
@ -1851,6 +1858,10 @@ public class ReactExoplayerView extends FrameLayout implements
adTagUrl = uri; adTagUrl = uri;
} }
public void setAdLanguage(final String language) {
adLanguage = language;
}
public void setTextTracks(SideLoadedTextTrackList textTracks) { public void setTextTracks(SideLoadedTextTrackList textTracks) {
this.textTracks = textTracks; this.textTracks = textTracks;
reloadSource(); // FIXME Shall be moved inside source reloadSource(); // FIXME Shall be moved inside source

View File

@ -29,6 +29,7 @@ class ReactExoplayerViewManager(private val config: ReactExoplayerConfig) : View
private const val REACT_CLASS = "RCTVideo" private const val REACT_CLASS = "RCTVideo"
private const val PROP_SRC = "src" private const val PROP_SRC = "src"
private const val PROP_AD_TAG_URL = "adTagUrl" 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_RESIZE_MODE = "resizeMode"
private const val PROP_REPEAT = "repeat" private const val PROP_REPEAT = "repeat"
private const val PROP_SELECTED_AUDIO_TRACK = "selectedAudioTrack" private const val PROP_SELECTED_AUDIO_TRACK = "selectedAudioTrack"
@ -110,6 +111,16 @@ class ReactExoplayerViewManager(private val config: ReactExoplayerConfig) : View
videoView.setAdTagUrl(adTagUrl) 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) @ReactProp(name = PROP_RESIZE_MODE)
fun setResizeMode(videoView: ReactExoplayerView, resizeMode: String) { fun setResizeMode(videoView: ReactExoplayerView, resizeMode: String) {
when (resizeMode) { when (resizeMode) {

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -23,3 +23,16 @@ Example:
onReceiveAdEvent={event => console.log(event)} 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"
...
```

View File

@ -19,7 +19,12 @@
} }
func setUpAdsLoader() { 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 adsLoader.delegate = self
} }

View File

@ -87,6 +87,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
/* IMA Ads */ /* IMA Ads */
private var _adTagUrl: String? private var _adTagUrl: String?
private var _adLanguage: String?
#if USE_GOOGLE_IMA #if USE_GOOGLE_IMA
private var _imaAdsManager: RCTIMAAdsManager! private var _imaAdsManager: RCTIMAAdsManager!
/* Playhead used by the SDK to track content video progress and insert mid-rolls. */ /* 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 // MARK: - RCTIMAAdsManager
func getAdLanguage() -> String? {
return _adLanguage
}
func getAdTagUrl() -> String? { func getAdTagUrl() -> String? {
return _adTagUrl return _adTagUrl
} }

View File

@ -6,6 +6,7 @@
RCT_EXPORT_VIEW_PROPERTY(src, NSDictionary); RCT_EXPORT_VIEW_PROPERTY(src, NSDictionary);
RCT_EXPORT_VIEW_PROPERTY(drm, NSDictionary); RCT_EXPORT_VIEW_PROPERTY(drm, NSDictionary);
RCT_EXPORT_VIEW_PROPERTY(adTagUrl, NSString); RCT_EXPORT_VIEW_PROPERTY(adTagUrl, NSString);
RCT_EXPORT_VIEW_PROPERTY(adLanguage, 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);

View File

@ -305,6 +305,7 @@ export type OnControlsVisibilityChange = Readonly<{
export interface VideoNativeProps extends ViewProps { export interface VideoNativeProps extends ViewProps {
src?: VideoSrc; src?: VideoSrc;
adTagUrl?: string; adTagUrl?: string;
adLanguage?: string;
allowsExternalPlayback?: boolean; // ios, true allowsExternalPlayback?: boolean; // ios, true
disableFocus?: boolean; // android disableFocus?: boolean; // android
maxBitRate?: Float; maxBitRate?: Float;

View File

@ -255,6 +255,7 @@ export interface ReactVideoProps extends ReactVideoEvents, ViewProps {
drm?: Drm; drm?: Drm;
style?: StyleProp<ViewStyle>; style?: StyleProp<ViewStyle>;
adTagUrl?: string; adTagUrl?: string;
adLanguage?: ISO639_1;
audioOutput?: AudioOutput; // Mobile audioOutput?: AudioOutput; // Mobile
automaticallyWaitsToMinimizeStalling?: boolean; // iOS automaticallyWaitsToMinimizeStalling?: boolean; // iOS
bufferConfig?: BufferConfig; // Android bufferConfig?: BufferConfig; // Android