diff --git a/__tests__/googleMobileAds.test.ts b/__tests__/googleMobileAds.test.ts index 235d97a4..e35fc661 100644 --- a/__tests__/googleMobileAds.test.ts +++ b/__tests__/googleMobileAds.test.ts @@ -87,6 +87,22 @@ describe('Admob', function () { admob().openDebugMenu(''); }).toThrowError('openDebugMenu expected a non-empty string value'); }); + + it('does call native setAppVolume method', () => { + admob().setAppVolume(0.5); + expect(RNGoogleMobileAdsModule.setAppVolume).toBeCalledTimes(1); + }); + + it('throws if setAppVolume is greater then 1', function () { + expect(() => { + admob().setAppVolume(2); + }).toThrowError('The app volume must be a value between 0 and 1 inclusice.'); + }); + + it('does call native setAppMuted method', () => { + admob().setAppMuted(true); + expect(RNGoogleMobileAdsModule.setAppMuted).toBeCalledTimes(1); + }); }); }); }); diff --git a/android/src/main/java/io/invertase/googlemobileads/ReactNativeGoogleMobileAdsModule.java b/android/src/main/java/io/invertase/googlemobileads/ReactNativeGoogleMobileAdsModule.java index 980fcbf6..02eb9f62 100644 --- a/android/src/main/java/io/invertase/googlemobileads/ReactNativeGoogleMobileAdsModule.java +++ b/android/src/main/java/io/invertase/googlemobileads/ReactNativeGoogleMobileAdsModule.java @@ -194,4 +194,14 @@ public void openDebugMenu(final String adUnit) { .runOnUiThread(() -> MobileAds.openDebugMenu(getCurrentActivity(), adUnit)); } } + + @ReactMethod + public void setAppVolume(final Float volume) { + MobileAds.setAppVolume(volume); + } + + @ReactMethod + public void setAppMuted(final Boolean muted) { + MobileAds.setAppMuted(muted); + } } diff --git a/docs/displaying-ads.mdx b/docs/displaying-ads.mdx index b8b70b19..d5f0a27c 100644 --- a/docs/displaying-ads.mdx +++ b/docs/displaying-ads.mdx @@ -484,3 +484,38 @@ function App() { ``` The `sizes` prop takes an array of [`BannerAdSize`](/reference/admob/banneradsize) types. + +### Video ad volume control + +If your app has its own volume controls, such as custom music or sound effect volumes, disclosing app volume to the Google Mobile Ads SDK enables video ads to respect app volume settings. This ensures users receive video ads with the expected audio volume. + +The device volume, controlled through volume buttons or OS-level volume slider, determines the volume for device audio output. However, apps can independently adjust volume levels relative to the device volume to tailor the audio experience. + +For App Open, Banner, Interstitial, Rewarded, and Rewarded Interstitial ad formats you can report the relative app volume to the Google Mobile Ads SDK by calling the setAppVolume function. Valid ad volume values range from 0.0 (silent) to 1.0 (current device volume). Here's an example of how to report the relative app volume to the SDK: + +```js +import React from 'react'; +import MobileAds, { GAMBannerAd, BannerAdSize, TestIds } from 'react-native-google-mobile-ads'; + +const adUnitId = __DEV__ ? TestIds.GAM_BANNER : '/xxx/yyyy'; + +function App() { + MobileAds().setAppVolume(0.5); + + return ; +} +``` + +For App Open, Banner, Interstitial, Rewarded, and Rewarded Interstitial ad formats, you can inform the Google Mobile Ads SDK that the app volume has been muted by calling the setAppMuted function: + +```js +import React from 'react'; +import MobileAds, { GAMBannerAd, BannerAdSize, TestIds } from 'react-native-google-mobile-ads'; + +const adUnitId = __DEV__ ? TestIds.GAM_BANNER : '/xxx/yyyy'; + +function App() { + MobileAds().setMuted(true); + + return ; +} \ No newline at end of file diff --git a/ios/RNGoogleMobileAds/RNGoogleMobileAdsModule.mm b/ios/RNGoogleMobileAds/RNGoogleMobileAdsModule.mm index 9e48dec6..7fc1d425 100644 --- a/ios/RNGoogleMobileAds/RNGoogleMobileAdsModule.mm +++ b/ios/RNGoogleMobileAds/RNGoogleMobileAdsModule.mm @@ -67,6 +67,18 @@ - (dispatch_queue_t)methodQueue { #endif } +RCT_EXPORT_METHOD(setAppVolume : (float)volume) { +#if !TARGET_OS_MACCATALYST + GADMobileAds.sharedInstance.applicationVolume = volume; +#endif +} + +RCT_EXPORT_METHOD(setAppMuted : (BOOL *)muted) { +#if !TARGET_OS_MACCATALYST + GADMobileAds.sharedInstance.applicationMuted = muted; +#endif +} + #ifdef RCT_NEW_ARCH_ENABLED - (std::shared_ptr)getTurboModule: (const facebook::react::ObjCTurboModule::InitParams &)params { diff --git a/jest.setup.ts b/jest.setup.ts index 3f3a2896..1e50c899 100644 --- a/jest.setup.ts +++ b/jest.setup.ts @@ -34,6 +34,8 @@ jest.doMock('react-native', () => { setRequestConfiguration: jest.fn(), openAdInspector: jest.fn(), openDebugMenu: jest.fn(), + setAppVolume: jest.fn(), + setAppMuted: jest.fn(), }; }, }, diff --git a/src/MobileAds.ts b/src/MobileAds.ts index 318d2509..96a37225 100644 --- a/src/MobileAds.ts +++ b/src/MobileAds.ts @@ -58,6 +58,16 @@ class MobileAdsModule implements MobileAdsModuleInterface { if (!adUnit) throw new Error('googleMobileAds.openDebugMenu expected a non-empty string value'); RNGoogleMobileAdsModule.openDebugMenu(adUnit); } + + setAppVolume(volume: number) { + if (volume < 0 || volume > 1) + throw new Error('The app volume must be a value between 0 and 1 inclusice.'); + RNGoogleMobileAdsModule.setAppVolume(volume); + } + + setAppMuted(muted: boolean) { + RNGoogleMobileAdsModule.setAppMuted(muted); + } } const MobileAdsInstance = new MobileAdsModule(); diff --git a/src/NativeGoogleMobileAdsModule.ts b/src/NativeGoogleMobileAdsModule.ts index a5308222..68ca677b 100644 --- a/src/NativeGoogleMobileAdsModule.ts +++ b/src/NativeGoogleMobileAdsModule.ts @@ -9,6 +9,8 @@ export interface Spec extends TurboModule { setRequestConfiguration(requestConfiguration?: UnsafeObject): Promise; openAdInspector(): Promise; openDebugMenu(adUnit: string): void; + setAppVolume(volume: number): void; + setAppMuted(muted: boolean): void; } export default TurboModuleRegistry.getEnforcing('RNGoogleMobileAdsModule'); diff --git a/src/types/MobileAdsModule.interface.ts b/src/types/MobileAdsModule.interface.ts index 83d75210..1bdae716 100644 --- a/src/types/MobileAdsModule.interface.ts +++ b/src/types/MobileAdsModule.interface.ts @@ -49,4 +49,32 @@ export interface MobileAdsModuleInterface { * @param adUnit Any valid ad unit from your Ad Manager account is sufficient to open the debug options menu. */ openDebugMenu(adUnit: string): void; + + /** + * Sets the application's audio volume. Affects audio volumes of all ads relative to other audio output. + * + * Warning: Lowering your app's audio volume reduces video ad eligibility and may reduce your app's ad revenue. + * You should only utilize this API if your app provides custom volume controls to the user, and you should reflect + * the user's volume choice in this API. + * + * @see https://developers.google.com/ad-manager/mobile-ads-sdk/android/global-settings + * @see https://developers.google.com/ad-manager/mobile-ads-sdk/ios/global-settings + * + * @param volume the volume as a float from 0 (muted) to 1.0 (full media volume). Defaults to 1.0 + */ + setAppVolume(volume: number): void; + + /** + * Indicates whether the application's audio is muted. Affects initial mute state for all ads. + * + * Warning: Muting your application reduces video ad eligibility and may reduce your app's ad revenue. + * You should only utilize this API if your app provides a custom mute control to the user, and you should + * reflect the user's mute decision in this API. + * + * @see https://developers.google.com/ad-manager/mobile-ads-sdk/android/global-settings + * @see https://developers.google.com/ad-manager/mobile-ads-sdk/ios/global-settings + * + * @param muted true if the app is muted, false otherwise. Defaults to false. + */ + setAppMuted(muted: boolean): void; }