react-native-video/ios/Video/Features/RCTIMAAdsManager.swift

225 lines
7.6 KiB
Swift
Raw Normal View History

#if USE_GOOGLE_IMA
import Foundation
import GoogleInteractiveMediaAds
2022-11-17 04:01:29 -07:00
class RCTIMAAdsManager: NSObject, IMAAdsLoaderDelegate, IMAAdsManagerDelegate, IMALinkOpenerDelegate {
private weak var _video: RCTVideo?
feat: implement enterPictureInPictureOnLeave prop for both platform (Android, iOS) (#3385) * docs: enable Android PIP * chore: change comments * feat(android): implement Android PictureInPicture * refactor: minor refactor code and apply lint * fix: rewrite pip action intent code for Android14 * fix: remove redundant codes * feat: add isInPictureInPicture flag for lifecycle handling - activity provide helper method for same purpose, but this flag makes code simple * feat: add pipFullscreenPlayerView for makes PIP include video only * fix: add manifest value checker for prevent crash * docs: add pictureInPicture prop's Android guide * fix: sync controller visibility * refactor: refining variable name * fix: check multi window mode when host pause - some OS version call onPause on multi-window mode * fix: handling when onStop is called while in multi-window mode * refactor: enhance PIP util codes * fix: fix FullscreenPlayerView constructor * refactor: add enterPictureInPictureOnLeave prop and pip methods - remove pictureInPicture boolean prop - add enterPictureInPictureOnLeave boolean prop - add enterPictureInPicture method - add exitPictureInPicture method * fix: fix lint error * fix: prevent audio play in background without playInBackground prop * fix: fix onDetachedFromWindow * docs: update docs for pip * fix(android): sync pip controller with external controller state - for media session * fix(ios): fix pip active fn variable reference * refactor(ios): refactor code * refactor(android): refactor codes * fix(android): fix lint error * refactor(android): refactor android pip logics * fix(android): fix flickering issue when stop picture in picture * fix(android): fix import * fix(android): fix picture in picture with fullscreen mode * fix(ios): fix syntax error * fix(android): fix Fragment managed code * refactor(android): remove redundant override lifecycle * fix(js): add PIP type definition for codegen * fix(android): fix syntax * chore(android): fix lint error * fix(ios): fix enter background handler * refactor(ios): remove redundant code * fix(ios): fix applicationDidEnterBackground for PIP * fix(android): fix onPictureInPictureStatusChanged * fix(ios): fix RCTPictureInPicture * refactor(android): Ignore exception for some device ignore pip checker - some device ignore PIP availability check, so we need to handle exception to prevent crash * fix(android): add hideWithoutPlayer fn into Kotlin ver * refactor(android): remove redundant code * fix(android): fix pip ratio to be calculated with correct ratio value * fix(android): fix crash issue when unmounting in PIP mode * fix(android): fix lint error * Update android/src/main/java/com/brentvatne/react/VideoManagerModule.kt * fix(android): fix lint error * fix(ios): fix lint error * fix(ios): fix lint error * feat(expo): add android picture in picture config within expo plugin * fix: Replace Fragment with androidx.activity - remove code that uses Fragment, which is a tricky implementation * fix: fix lint error * fix(android): disable auto enter when player released * fix(android): fix event handler to check based on Activity it's bound to --------- Co-authored-by: jonghun <jonghun@toss.im> Co-authored-by: Olivier Bouillet <62574056+freeboub@users.noreply.github.com>
2025-01-04 04:37:33 -07:00
private var _isPictureInPictureActive: () -> Bool
2022-11-17 04:01:29 -07:00
/* Entry point for the SDK. Used to make ad requests. */
private var adsLoader: IMAAdsLoader!
/* Main point of interaction with the SDK. Created by the SDK as the result of an ad request. */
private var adsManager: IMAAdsManager!
2022-11-17 04:01:29 -07:00
feat: implement enterPictureInPictureOnLeave prop for both platform (Android, iOS) (#3385) * docs: enable Android PIP * chore: change comments * feat(android): implement Android PictureInPicture * refactor: minor refactor code and apply lint * fix: rewrite pip action intent code for Android14 * fix: remove redundant codes * feat: add isInPictureInPicture flag for lifecycle handling - activity provide helper method for same purpose, but this flag makes code simple * feat: add pipFullscreenPlayerView for makes PIP include video only * fix: add manifest value checker for prevent crash * docs: add pictureInPicture prop's Android guide * fix: sync controller visibility * refactor: refining variable name * fix: check multi window mode when host pause - some OS version call onPause on multi-window mode * fix: handling when onStop is called while in multi-window mode * refactor: enhance PIP util codes * fix: fix FullscreenPlayerView constructor * refactor: add enterPictureInPictureOnLeave prop and pip methods - remove pictureInPicture boolean prop - add enterPictureInPictureOnLeave boolean prop - add enterPictureInPicture method - add exitPictureInPicture method * fix: fix lint error * fix: prevent audio play in background without playInBackground prop * fix: fix onDetachedFromWindow * docs: update docs for pip * fix(android): sync pip controller with external controller state - for media session * fix(ios): fix pip active fn variable reference * refactor(ios): refactor code * refactor(android): refactor codes * fix(android): fix lint error * refactor(android): refactor android pip logics * fix(android): fix flickering issue when stop picture in picture * fix(android): fix import * fix(android): fix picture in picture with fullscreen mode * fix(ios): fix syntax error * fix(android): fix Fragment managed code * refactor(android): remove redundant override lifecycle * fix(js): add PIP type definition for codegen * fix(android): fix syntax * chore(android): fix lint error * fix(ios): fix enter background handler * refactor(ios): remove redundant code * fix(ios): fix applicationDidEnterBackground for PIP * fix(android): fix onPictureInPictureStatusChanged * fix(ios): fix RCTPictureInPicture * refactor(android): Ignore exception for some device ignore pip checker - some device ignore PIP availability check, so we need to handle exception to prevent crash * fix(android): add hideWithoutPlayer fn into Kotlin ver * refactor(android): remove redundant code * fix(android): fix pip ratio to be calculated with correct ratio value * fix(android): fix crash issue when unmounting in PIP mode * fix(android): fix lint error * Update android/src/main/java/com/brentvatne/react/VideoManagerModule.kt * fix(android): fix lint error * fix(ios): fix lint error * fix(ios): fix lint error * feat(expo): add android picture in picture config within expo plugin * fix: Replace Fragment with androidx.activity - remove code that uses Fragment, which is a tricky implementation * fix: fix lint error * fix(android): disable auto enter when player released * fix(android): fix event handler to check based on Activity it's bound to --------- Co-authored-by: jonghun <jonghun@toss.im> Co-authored-by: Olivier Bouillet <62574056+freeboub@users.noreply.github.com>
2025-01-04 04:37:33 -07:00
init(video: RCTVideo!, isPictureInPictureActive: @escaping () -> Bool) {
_video = video
feat: implement enterPictureInPictureOnLeave prop for both platform (Android, iOS) (#3385) * docs: enable Android PIP * chore: change comments * feat(android): implement Android PictureInPicture * refactor: minor refactor code and apply lint * fix: rewrite pip action intent code for Android14 * fix: remove redundant codes * feat: add isInPictureInPicture flag for lifecycle handling - activity provide helper method for same purpose, but this flag makes code simple * feat: add pipFullscreenPlayerView for makes PIP include video only * fix: add manifest value checker for prevent crash * docs: add pictureInPicture prop's Android guide * fix: sync controller visibility * refactor: refining variable name * fix: check multi window mode when host pause - some OS version call onPause on multi-window mode * fix: handling when onStop is called while in multi-window mode * refactor: enhance PIP util codes * fix: fix FullscreenPlayerView constructor * refactor: add enterPictureInPictureOnLeave prop and pip methods - remove pictureInPicture boolean prop - add enterPictureInPictureOnLeave boolean prop - add enterPictureInPicture method - add exitPictureInPicture method * fix: fix lint error * fix: prevent audio play in background without playInBackground prop * fix: fix onDetachedFromWindow * docs: update docs for pip * fix(android): sync pip controller with external controller state - for media session * fix(ios): fix pip active fn variable reference * refactor(ios): refactor code * refactor(android): refactor codes * fix(android): fix lint error * refactor(android): refactor android pip logics * fix(android): fix flickering issue when stop picture in picture * fix(android): fix import * fix(android): fix picture in picture with fullscreen mode * fix(ios): fix syntax error * fix(android): fix Fragment managed code * refactor(android): remove redundant override lifecycle * fix(js): add PIP type definition for codegen * fix(android): fix syntax * chore(android): fix lint error * fix(ios): fix enter background handler * refactor(ios): remove redundant code * fix(ios): fix applicationDidEnterBackground for PIP * fix(android): fix onPictureInPictureStatusChanged * fix(ios): fix RCTPictureInPicture * refactor(android): Ignore exception for some device ignore pip checker - some device ignore PIP availability check, so we need to handle exception to prevent crash * fix(android): add hideWithoutPlayer fn into Kotlin ver * refactor(android): remove redundant code * fix(android): fix pip ratio to be calculated with correct ratio value * fix(android): fix crash issue when unmounting in PIP mode * fix(android): fix lint error * Update android/src/main/java/com/brentvatne/react/VideoManagerModule.kt * fix(android): fix lint error * fix(ios): fix lint error * fix(ios): fix lint error * feat(expo): add android picture in picture config within expo plugin * fix: Replace Fragment with androidx.activity - remove code that uses Fragment, which is a tricky implementation * fix: fix lint error * fix(android): disable auto enter when player released * fix(android): fix event handler to check based on Activity it's bound to --------- Co-authored-by: jonghun <jonghun@toss.im> Co-authored-by: Olivier Bouillet <62574056+freeboub@users.noreply.github.com>
2025-01-04 04:37:33 -07:00
_isPictureInPictureActive = isPictureInPictureActive
super.init()
}
2022-11-17 04:01:29 -07:00
func setUpAdsLoader() {
guard let _video else { return }
let settings = IMASettings()
if let adLanguage = _video.getAdLanguage() {
settings.language = adLanguage
}
adsLoader = IMAAdsLoader(settings: settings)
adsLoader.delegate = self
}
2022-11-17 04:01:29 -07:00
func requestAds() {
guard let _video else { return }
// Create ad display container for ad rendering.
let adDisplayContainer = IMAAdDisplayContainer(adContainer: _video, viewController: _video.reactViewController())
let adTagUrl = _video.getAdTagUrl()
let contentPlayhead = _video.getContentPlayhead()
2022-11-17 04:01:29 -07:00
if adTagUrl != nil && contentPlayhead != nil {
// Create an ad request with our ad tag, display container, and optional user context.
let request = IMAAdsRequest(
adTagUrl: adTagUrl!,
adDisplayContainer: adDisplayContainer,
contentPlayhead: contentPlayhead,
userContext: nil
)
adsLoader.requestAds(with: request)
}
2022-11-17 04:01:29 -07:00
}
func releaseAds() {
guard let adsManager else { return }
// Destroy AdsManager may be delayed for a few milliseconds
// But what we want is it stopped producing sound immediately
// Issue found on tvOS 17, or iOS if view detach & STARTED event happen at the same moment
adsManager.volume = 0
adsManager.pause()
adsManager.destroy()
}
// MARK: - Getters
2022-11-17 04:01:29 -07:00
func getAdsLoader() -> IMAAdsLoader? {
return adsLoader
}
2022-11-17 04:01:29 -07:00
func getAdsManager() -> IMAAdsManager? {
return adsManager
}
2022-11-17 04:01:29 -07:00
// MARK: - IMAAdsLoaderDelegate
2022-11-17 04:01:29 -07:00
func adsLoader(_: IMAAdsLoader, adsLoadedWith adsLoadedData: IMAAdsLoadedData) {
guard let _video else { return }
// Grab the instance of the IMAAdsManager and set yourself as the delegate.
adsManager = adsLoadedData.adsManager
adsManager?.delegate = self
// Create ads rendering settings and tell the SDK to use the in-app browser.
let adsRenderingSettings = IMAAdsRenderingSettings()
adsRenderingSettings.linkOpenerDelegate = self
adsRenderingSettings.linkOpenerPresentingController = _video.reactViewController()
2022-11-17 04:01:29 -07:00
adsManager.initialize(with: adsRenderingSettings)
}
2022-11-17 04:01:29 -07:00
func adsLoader(_: IMAAdsLoader, failedWith adErrorData: IMAAdLoadingErrorData) {
if adErrorData.adError.message != nil {
print("Error loading ads: " + adErrorData.adError.message!)
}
2022-11-17 04:01:29 -07:00
_video?.setPaused(false)
2022-11-17 04:01:29 -07:00
}
// MARK: - IMAAdsManagerDelegate
2022-11-17 04:01:29 -07:00
func adsManager(_ adsManager: IMAAdsManager, didReceive event: IMAAdEvent) {
guard let _video else { return }
// Mute ad if the main player is muted
if _video.isMuted() {
adsManager.volume = 0
}
// Play each ad once it has been loaded
if event.type == IMAAdEventType.LOADED {
feat: implement enterPictureInPictureOnLeave prop for both platform (Android, iOS) (#3385) * docs: enable Android PIP * chore: change comments * feat(android): implement Android PictureInPicture * refactor: minor refactor code and apply lint * fix: rewrite pip action intent code for Android14 * fix: remove redundant codes * feat: add isInPictureInPicture flag for lifecycle handling - activity provide helper method for same purpose, but this flag makes code simple * feat: add pipFullscreenPlayerView for makes PIP include video only * fix: add manifest value checker for prevent crash * docs: add pictureInPicture prop's Android guide * fix: sync controller visibility * refactor: refining variable name * fix: check multi window mode when host pause - some OS version call onPause on multi-window mode * fix: handling when onStop is called while in multi-window mode * refactor: enhance PIP util codes * fix: fix FullscreenPlayerView constructor * refactor: add enterPictureInPictureOnLeave prop and pip methods - remove pictureInPicture boolean prop - add enterPictureInPictureOnLeave boolean prop - add enterPictureInPicture method - add exitPictureInPicture method * fix: fix lint error * fix: prevent audio play in background without playInBackground prop * fix: fix onDetachedFromWindow * docs: update docs for pip * fix(android): sync pip controller with external controller state - for media session * fix(ios): fix pip active fn variable reference * refactor(ios): refactor code * refactor(android): refactor codes * fix(android): fix lint error * refactor(android): refactor android pip logics * fix(android): fix flickering issue when stop picture in picture * fix(android): fix import * fix(android): fix picture in picture with fullscreen mode * fix(ios): fix syntax error * fix(android): fix Fragment managed code * refactor(android): remove redundant override lifecycle * fix(js): add PIP type definition for codegen * fix(android): fix syntax * chore(android): fix lint error * fix(ios): fix enter background handler * refactor(ios): remove redundant code * fix(ios): fix applicationDidEnterBackground for PIP * fix(android): fix onPictureInPictureStatusChanged * fix(ios): fix RCTPictureInPicture * refactor(android): Ignore exception for some device ignore pip checker - some device ignore PIP availability check, so we need to handle exception to prevent crash * fix(android): add hideWithoutPlayer fn into Kotlin ver * refactor(android): remove redundant code * fix(android): fix pip ratio to be calculated with correct ratio value * fix(android): fix crash issue when unmounting in PIP mode * fix(android): fix lint error * Update android/src/main/java/com/brentvatne/react/VideoManagerModule.kt * fix(android): fix lint error * fix(ios): fix lint error * fix(ios): fix lint error * feat(expo): add android picture in picture config within expo plugin * fix: Replace Fragment with androidx.activity - remove code that uses Fragment, which is a tricky implementation * fix: fix lint error * fix(android): disable auto enter when player released * fix(android): fix event handler to check based on Activity it's bound to --------- Co-authored-by: jonghun <jonghun@toss.im> Co-authored-by: Olivier Bouillet <62574056+freeboub@users.noreply.github.com>
2025-01-04 04:37:33 -07:00
if _isPictureInPictureActive() {
return
}
adsManager.start()
}
2022-11-17 04:01:29 -07:00
if _video.onReceiveAdEvent != nil {
let type = convertEventToString(event: event.type)
if event.adData != nil {
_video.onReceiveAdEvent?([
"event": type,
"data": event.adData ?? [String](),
"target": _video.reactTag!,
])
} else {
_video.onReceiveAdEvent?([
"event": type,
"target": _video.reactTag!,
])
}
2023-09-17 13:12:46 -06:00
}
2022-11-17 04:01:29 -07:00
}
func adsManager(_: IMAAdsManager, didReceive error: IMAAdError) {
if error.message != nil {
print("AdsManager error: " + error.message!)
}
2022-11-17 04:01:29 -07:00
guard let _video else { return }
if _video.onReceiveAdEvent != nil {
_video.onReceiveAdEvent?([
"event": "ERROR",
"data": [
"message": error.message ?? "",
"code": error.code,
"type": error.type,
],
"target": _video.reactTag!,
])
}
2022-11-17 04:01:29 -07:00
// Fall back to playing content
_video.setPaused(false)
2022-11-17 04:01:29 -07:00
}
func adsManagerDidRequestContentPause(_: IMAAdsManager) {
// Pause the content for the SDK to play ads.
_video?.setPaused(true)
_video?.setAdPlaying(true)
}
func adsManagerDidRequestContentResume(_: IMAAdsManager) {
// Resume the content since the SDK is done playing ads (at least for now).
_video?.setAdPlaying(false)
_video?.setPaused(false)
}
2022-11-17 04:01:29 -07:00
// MARK: - IMALinkOpenerDelegate
func linkOpenerDidClose(inAppLink _: NSObject) {
adsManager?.resume()
}
// MARK: - Helpers
2022-11-17 04:01:29 -07:00
func convertEventToString(event: IMAAdEventType!) -> String {
var result = "UNKNOWN"
2022-11-17 04:01:29 -07:00
switch event {
2022-11-17 04:01:29 -07:00
case .AD_BREAK_READY:
result = "AD_BREAK_READY"
2022-11-17 04:01:29 -07:00
case .AD_BREAK_ENDED:
result = "AD_BREAK_ENDED"
2022-11-17 04:01:29 -07:00
case .AD_BREAK_STARTED:
result = "AD_BREAK_STARTED"
2022-11-17 04:01:29 -07:00
case .AD_PERIOD_ENDED:
result = "AD_PERIOD_ENDED"
2022-11-17 04:01:29 -07:00
case .AD_PERIOD_STARTED:
result = "AD_PERIOD_STARTED"
2022-11-17 04:01:29 -07:00
case .ALL_ADS_COMPLETED:
result = "ALL_ADS_COMPLETED"
2022-11-17 04:01:29 -07:00
case .CLICKED:
result = "CLICK"
2022-11-17 04:01:29 -07:00
case .COMPLETE:
result = "COMPLETED"
2022-11-17 04:01:29 -07:00
case .CUEPOINTS_CHANGED:
result = "CUEPOINTS_CHANGED"
2022-11-17 04:01:29 -07:00
case .FIRST_QUARTILE:
result = "FIRST_QUARTILE"
2022-11-17 04:01:29 -07:00
case .LOADED:
result = "LOADED"
2022-11-17 04:01:29 -07:00
case .LOG:
result = "LOG"
2022-11-17 04:01:29 -07:00
case .MIDPOINT:
result = "MIDPOINT"
2022-11-17 04:01:29 -07:00
case .PAUSE:
result = "PAUSED"
2022-11-17 04:01:29 -07:00
case .RESUME:
result = "RESUMED"
2022-11-17 04:01:29 -07:00
case .SKIPPED:
result = "SKIPPED"
2022-11-17 04:01:29 -07:00
case .STARTED:
result = "STARTED"
2022-11-17 04:01:29 -07:00
case .STREAM_LOADED:
result = "STREAM_LOADED"
2022-11-17 04:01:29 -07:00
case .TAPPED:
result = "TAPPED"
2022-11-17 04:01:29 -07:00
case .THIRD_QUARTILE:
result = "THIRD_QUARTILE"
2022-11-17 04:01:29 -07:00
default:
result = "UNKNOWN"
}
2022-11-17 04:01:29 -07:00
return result
}
2022-11-17 04:01:29 -07:00
}
#endif