From 1f68ddf713c01594c5d4db431df8a514016289d8 Mon Sep 17 00:00:00 2001 From: Axel Vencatareddy Date: Fri, 24 Nov 2023 12:40:14 +0100 Subject: [PATCH] feat: add onAdError event listener --- .../common/react/VideoEventEmitter.java | 16 ++++++++++++++-- .../brentvatne/exoplayer/ReactExoplayerView.java | 15 +++++++++++++-- .../ads/interactivemedia/v3/api/AdError.java | 10 ++++++++++ .../interactivemedia/v3/api/AdErrorEvent.java | 9 +++++++++ ios/Video/Features/RCTIMAAdsManager.swift | 13 ++++++++++++- ios/Video/RCTVideo.swift | 7 ++++--- ios/Video/RCTVideoManager.m | 1 + src/Video.tsx | 14 ++++++++++++-- src/VideoNativeComponent.ts | 3 ++- src/types/events.ts | 7 +++++++ 10 files changed, 84 insertions(+), 11 deletions(-) create mode 100644 android/src/main/java/com/google/ads/interactivemedia/v3/api/AdError.java create mode 100644 android/src/main/java/com/google/ads/interactivemedia/v3/api/AdErrorEvent.java diff --git a/android/src/main/java/com/brentvatne/common/react/VideoEventEmitter.java b/android/src/main/java/com/brentvatne/common/react/VideoEventEmitter.java index b8718287b0..c0a4b62d88 100644 --- a/android/src/main/java/com/brentvatne/common/react/VideoEventEmitter.java +++ b/android/src/main/java/com/brentvatne/common/react/VideoEventEmitter.java @@ -56,6 +56,7 @@ public VideoEventEmitter(ReactContext reactContext) { private static final String EVENT_TEXT_TRACKS = "onTextTracks"; private static final String EVENT_VIDEO_TRACKS = "onVideoTracks"; private static final String EVENT_ON_RECEIVE_AD_EVENT = "onReceiveAdEvent"; + private static final String EVENT_ON_AD_ERROR = "onAdError"; static public final String[] Events = { EVENT_LOAD_START, @@ -83,7 +84,8 @@ public VideoEventEmitter(ReactContext reactContext) { EVENT_TEXT_TRACKS, EVENT_VIDEO_TRACKS, EVENT_BANDWIDTH, - EVENT_ON_RECEIVE_AD_EVENT + EVENT_ON_RECEIVE_AD_EVENT, + EVENT_ON_AD_ERROR }; @Retention(RetentionPolicy.SOURCE) @@ -113,7 +115,8 @@ public VideoEventEmitter(ReactContext reactContext) { EVENT_TEXT_TRACKS, EVENT_VIDEO_TRACKS, EVENT_BANDWIDTH, - EVENT_ON_RECEIVE_AD_EVENT + EVENT_ON_RECEIVE_AD_EVENT, + EVENT_ON_AD_ERROR }) @interface VideoEvents { } @@ -420,6 +423,15 @@ public void receiveAdEvent(String event) { receiveEvent(EVENT_ON_RECEIVE_AD_EVENT, map); } + public void receiveAdErrorEvent(AdError error) { + WritableMap map = Arguments.createMap(); + map.putString("message", error.getMessage()); + map.putString("code", String.valueOf(error.getErrorCode())); + map.putString("type", String.valueOf(error.getErrorType())); + + receiveEvent(EVENT_ON_AD_ERROR, map); + } + private void receiveEvent(@VideoEvents String type, WritableMap event) { eventEmitter.receiveEvent(viewId, type, event); } diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index ab11643c2d..26f6a155be 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -103,6 +103,7 @@ import com.facebook.react.bridge.ReadableMap; import com.facebook.react.uimanager.ThemedReactContext; import com.google.ads.interactivemedia.v3.api.AdEvent; +import com.google.ads.interactivemedia.v3.api.AdErrorEvent; import com.google.android.exoplayer2.ext.ima.ImaAdsLoader; import com.google.common.collect.ImmutableList; @@ -128,7 +129,8 @@ public class ReactExoplayerView extends FrameLayout implements BandwidthMeter.EventListener, BecomingNoisyListener, DrmSessionEventListener, - AdEvent.AdEventListener { + AdEvent.AdEventListener, + AdErrorEvent.AdErrorListener { public static final double DEFAULT_MAX_HEAP_ALLOCATION_PERCENT = 1; public static final double DEFAULT_MIN_BACK_BUFFER_MEMORY_RESERVE = 0; @@ -616,7 +618,11 @@ private void initializePlayerCore(ReactExoplayerView self) { .setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF); // Create an AdsLoader. - adsLoader = new ImaAdsLoader.Builder(themedReactContext).setAdEventListener(this).build(); + adsLoader = new ImaAdsLoader + .Builder(themedReactContext) + .setAdEventListener(this) + .setAdErrorListener(this) + .build(); MediaSource.Factory mediaSourceFactory = new DefaultMediaSourceFactory(mediaDataSourceFactory) .setLocalAdInsertionComponents(unusedAdTagUri -> adsLoader, exoPlayerView); @@ -2090,4 +2096,9 @@ public void setShutterColor(Integer color) { public void onAdEvent(AdEvent adEvent) { eventEmitter.receiveAdEvent(adEvent.getType().name()); } + + @Override + public void onAdError(AdErrorEvent adErrorEvent) { + eventEmitter.receiveAdErrorEvent(adErrorEvent.getError()); + } } diff --git a/android/src/main/java/com/google/ads/interactivemedia/v3/api/AdError.java b/android/src/main/java/com/google/ads/interactivemedia/v3/api/AdError.java new file mode 100644 index 0000000000..0347bea25b --- /dev/null +++ b/android/src/main/java/com/google/ads/interactivemedia/v3/api/AdError.java @@ -0,0 +1,10 @@ +package com.google.ads.interactivemedia.v3.api; + +import androidx.annotation.InspectableProperty; + +public abstract class AdError { + public abstract InspectableProperty getErrorCode(); + public abstract InspectableProperty getErrorCodeNumber(); + public abstract InspectableProperty getErrorType(); + public abstract InspectableProperty getMessage(); +} diff --git a/android/src/main/java/com/google/ads/interactivemedia/v3/api/AdErrorEvent.java b/android/src/main/java/com/google/ads/interactivemedia/v3/api/AdErrorEvent.java new file mode 100644 index 0000000000..df2c4bc856 --- /dev/null +++ b/android/src/main/java/com/google/ads/interactivemedia/v3/api/AdErrorEvent.java @@ -0,0 +1,9 @@ +package com.google.ads.interactivemedia.v3.api; + +public abstract class AdErrorEvent { + public abstract AdError getError(); + + public interface AdErrorEventListener { + public void onAdErrorEvent(AdErrorEvent adErrorEvent); + } +} diff --git a/ios/Video/Features/RCTIMAAdsManager.swift b/ios/Video/Features/RCTIMAAdsManager.swift index 8a5e0643b6..cf6a1383bc 100644 --- a/ios/Video/Features/RCTIMAAdsManager.swift +++ b/ios/Video/Features/RCTIMAAdsManager.swift @@ -110,8 +110,19 @@ class RCTIMAAdsManager: NSObject, IMAAdsLoaderDelegate, IMAAdsManagerDelegate, I print("AdsManager error: " + error.message!) } + guard let _video = _video else {return} + + if _video.onAdError != nil { + _video.onAdError?([ + "message": error.message ?? "", + "code": error.code, + "type": error.type, + "target": _video.reactTag! + ]) + } + // Fall back to playing content - _video?.setPaused(false) + _video.setPaused(false) } func adsManagerDidRequestContentPause(_ adsManager: IMAAdsManager) { diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index 3cf6a019a4..b0d18f6f18 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -114,6 +114,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH @objc var onRestoreUserInterfaceForPictureInPictureStop: RCTDirectEventBlock? @objc var onGetLicense: RCTDirectEventBlock? @objc var onReceiveAdEvent: RCTDirectEventBlock? + @objc var onAdError: RCTDirectEventBlock? @objc func _onPictureInPictureStatusChanged() { onPictureInPictureStatusChanged?([ "isActive": NSNumber(value: true)]) @@ -145,7 +146,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH name: UIApplication.willResignActiveNotification, object: nil ) - + NotificationCenter.default.addObserver( self, selector: #selector(applicationDidBecomeActive(notification:)), @@ -1332,11 +1333,11 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH _playerObserver.removePlayerTimeObserver() } } - + @objc func handleAVPlayerAccess(notification:NSNotification!) { let accessLog:AVPlayerItemAccessLog! = (notification.object as! AVPlayerItem).accessLog() let lastEvent:AVPlayerItemAccessLogEvent! = accessLog.events.last - + onVideoBandwidthUpdate?(["bitrate": lastEvent.observedBitrate, "target": reactTag]) } } diff --git a/ios/Video/RCTVideoManager.m b/ios/Video/RCTVideoManager.m index 291868eb71..31492fd954 100644 --- a/ios/Video/RCTVideoManager.m +++ b/ios/Video/RCTVideoManager.m @@ -64,6 +64,7 @@ @interface RCT_EXTERN_MODULE(RCTVideoManager, RCTViewManager) RCT_EXPORT_VIEW_PROPERTY(onPictureInPictureStatusChanged, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onRestoreUserInterfaceForPictureInPictureStop, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onReceiveAdEvent, RCTDirectEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onAdError, RCTDirectEventBlock); RCT_EXTERN_METHOD(save:(NSDictionary *)options reactTag:(nonnull NSNumber *)reactTag diff --git a/src/Video.tsx b/src/Video.tsx index a0193a8737..d62fda81d4 100644 --- a/src/Video.tsx +++ b/src/Video.tsx @@ -13,10 +13,10 @@ import NativeVideoComponent, { } from './VideoNativeComponent'; import type {StyleProp, ImageStyle, NativeSyntheticEvent} from 'react-native'; -import type {ReactVideoProps} from './types/video'; import {getReactTag, resolveAssetSourceForVideo} from './utils'; import {VideoManager} from './VideoNativeComponent'; import type { + OnAdErrorData, OnAudioFocusChangedData, OnAudioTracksData, OnBandwidthUpdateData, @@ -35,7 +35,8 @@ import type { OnVideoAspectRatioData, OnVideoErrorData, OnVideoTracksData, -} from './types/events'; + ReactVideoProps, +} from './types'; export type VideoSaveData = { uri: string; @@ -83,6 +84,7 @@ const Video = forwardRef( onReadyForDisplay, onPlaybackRateChange, onVolumeChange, + onAdError, onAudioBecomingNoisy, onPictureInPictureStatusChanged, onRestoreUserInterfaceForPictureInPictureStop, @@ -399,6 +401,13 @@ const Video = forwardRef( [onReceiveAdEvent], ); + const _onAdError = useCallback( + (e: NativeSyntheticEvent) => { + onAdError?.(e.nativeEvent); + }, + [onAdError], + ); + const _onVideoAspectRatio = useCallback( (e: NativeSyntheticEvent) => { onAspectRatio?.(e.nativeEvent); @@ -494,6 +503,7 @@ const Video = forwardRef( selectedTextTrack={_selectedTextTrack} selectedAudioTrack={_selectedAudioTrack} selectedVideoTrack={_selectedVideoTrack} + onAdError={_onAdError} onGetLicense={onGetLicense} onVideoLoad={onVideoLoad} onVideoLoadStart={onVideoLoadStart} diff --git a/src/VideoNativeComponent.ts b/src/VideoNativeComponent.ts index 0647af3d28..018a7fb544 100644 --- a/src/VideoNativeComponent.ts +++ b/src/VideoNativeComponent.ts @@ -7,7 +7,7 @@ import {NativeModules, requireNativeComponent} from 'react-native'; import type ResizeMode from './types/ResizeMode'; import type FilterType from './types/FilterType'; import type Orientation from './types/Orientation'; -import type {AdEvent, OnTextTracksTypeData} from './types'; +import type {AdEvent, OnAdErrorData, OnTextTracksTypeData} from './types'; // -------- There are types for native component (future codegen) -------- // if you are looking for types for react component, see src/types/video.ts @@ -354,6 +354,7 @@ export interface VideoNativeProps extends ViewProps { onReceiveAdEvent?: ( event: NativeSyntheticEvent, ) => void; + onAdError?: (event: NativeSyntheticEvent) => void; onVideoPlaybackStateChanged?: ( event: NativeSyntheticEvent, ) => void; // android only diff --git a/src/types/events.ts b/src/types/events.ts index b1bc1e4bac..9a9d0dae71 100644 --- a/src/types/events.ts +++ b/src/types/events.ts @@ -118,6 +118,12 @@ export type OnReceiveAdEventData = Readonly<{ event: AdEvent; }>; +export type OnAdErrorData = Readonly<{ + code: string; + message: string; + type: string; +}>; + export type OnVideoErrorData = Readonly<{ error: OnVideoErrorDataDetails; target?: number; // ios @@ -152,6 +158,7 @@ export type OnBandwidthUpdateData = Readonly< >; export interface ReactVideoEvents { + onAdError?: (error: OnAdErrorData) => void; // Android, iOS onAudioBecomingNoisy?: () => void; //Android, iOS onAudioFocusChanged?: (e: OnAudioFocusChangedData) => void; // Android onIdle?: () => void; // Android