diff --git a/API.md b/API.md index 8b6878d1..a769aeba 100644 --- a/API.md +++ b/API.md @@ -164,6 +164,21 @@ protected List getPackages() { ); } ``` + +#### Enable custom feature in gradle file + +##### Enable client side ads insertion +To enable client side ads insertion CSAI with google IMA SDK, you need to enable it in your gradle file. + +```gradle +buildscript { + ext { + ... + RNVUseExoplayerIMA = true + ... + } +} +``` ### Windows installation @@ -370,6 +385,9 @@ Example: adTagUrl="https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/vmap_ad_samples&sz=640x480&cust_params=sample_ar%3Dpremidpostoptimizedpodbumper&ciu_szs=300x250&gdfp_req=1&ad_rule=1&output=vmap&unviewed_position_start=1&env=vp&impl=s&cmsid=496&vid=short_onecue&correlator=" ``` +Note: On android, you need enable IMA SDK in gradle file, see: [enableclient side ads insertion](#enable-client-side-ads-insertion) + + Platforms: Android, iOS #### allowsExternalPlayback diff --git a/android/build.gradle b/android/build.gradle index fdd257bf..0c3969c9 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -4,6 +4,18 @@ def safeExtGet(prop, fallback) { rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback } +def useExoplayerIMA = safeExtGet("RNVUseExoplayerIMA", false) + +println "useExoplayerIMA:" + useExoplayerIMA + +// This string is used to define build path. +// As react native build output directory is react-native path of the module. +// We need to force a new path on each configuration change. +// If you add a new build parameter, please add the new value in this string +def configStringPath = ( + 'useExoplayerIMA' + useExoplayerIMA \ +).md5() + android { compileSdkVersion safeExtGet('compileSdkVersion', 31) buildToolsVersion safeExtGet('buildToolsVersion', '30.0.2') @@ -24,6 +36,19 @@ android { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } + + buildDir 'buildOutput_' + configStringPath + + sourceSets { + main { + java { + if (useExoplayerIMA) { + exclude 'com/google/ads/interactivemedia/v3/api' + exclude 'com/google/android/exoplayer2/ext/ima' + } + } + } + } } repositories { @@ -45,7 +70,9 @@ dependencies { implementation('com.google.android.exoplayer:extension-okhttp:2.18.1') { exclude group: 'com.squareup.okhttp3', module: 'okhttp' } - implementation 'com.google.android.exoplayer:extension-ima:2.18.1' + if (useExoplayerIMA) { + implementation 'com.google.android.exoplayer:extension-ima:2.18.1' + } implementation "com.squareup.okhttp3:okhttp:" + '$OKHTTP_VERSION' } diff --git a/android/src/main/java/com/google/ads/interactivemedia/v3/api/AdEvent.java b/android/src/main/java/com/google/ads/interactivemedia/v3/api/AdEvent.java new file mode 100644 index 00000000..1913df29 --- /dev/null +++ b/android/src/main/java/com/google/ads/interactivemedia/v3/api/AdEvent.java @@ -0,0 +1,11 @@ +package com.google.ads.interactivemedia.v3.api; + +import androidx.annotation.InspectableProperty; + +public abstract class AdEvent { + public abstract InspectableProperty getType(); + + public interface AdEventListener { + public void onAdEvent(AdEvent adEvent); + } +} diff --git a/android/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java b/android/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java new file mode 100644 index 00000000..d23e7dd6 --- /dev/null +++ b/android/src/main/java/com/google/android/exoplayer2/ext/ima/ImaAdsLoader.java @@ -0,0 +1,65 @@ +package com.google.android.exoplayer2.ext.ima; + +import androidx.annotation.Nullable; + +import com.facebook.react.uimanager.ThemedReactContext; +import com.google.android.exoplayer2.ExoPlayer; +import com.google.android.exoplayer2.Player; +import com.google.android.exoplayer2.source.ads.AdsLoader; +import com.google.android.exoplayer2.source.ads.AdsMediaSource; +import com.google.android.exoplayer2.ui.AdViewProvider; +import com.google.android.exoplayer2.upstream.DataSpec; + +import java.io.IOException; + +public class ImaAdsLoader implements AdsLoader { + public void setPlayer(ExoPlayer player) { + } + + @Override + public void setPlayer(@Nullable Player player) { + + } + + public void release() { + } + + @Override + public void setSupportedContentTypes(int... ints) { + + } + + @Override + public void start(AdsMediaSource adsMediaSource, DataSpec dataSpec, Object o, AdViewProvider adViewProvider, EventListener eventListener) { + + } + + @Override + public void stop(AdsMediaSource adsMediaSource, EventListener eventListener) { + + } + + @Override + public void handlePrepareComplete(AdsMediaSource adsMediaSource, int i, int i1) { + + } + + @Override + public void handlePrepareError(AdsMediaSource adsMediaSource, int i, int i1, IOException e) { + + } + + public static class Builder { + public Builder(ThemedReactContext themedReactContext) { + + } + + public Builder setAdEventListener(Object reactExoplayerView) { + return this; + } + + public ImaAdsLoader build() { + return null; + } + } +} diff --git a/examples/basic/android/build.gradle b/examples/basic/android/build.gradle index 8569fee3..533ca601 100644 --- a/examples/basic/android/build.gradle +++ b/examples/basic/android/build.gradle @@ -14,6 +14,8 @@ buildscript { // Otherwise we default to the side-by-side NDK version from AGP. ndkVersion = "21.4.7075529" } + + RNVUseExoplayerIMA = true } repositories { google() diff --git a/examples/basic/metro.config.js b/examples/basic/metro.config.js index bede29ff..fa377047 100644 --- a/examples/basic/metro.config.js +++ b/examples/basic/metro.config.js @@ -5,7 +5,7 @@ * @format */ const path = require('path'); -const blacklist = require('metro-config/src/defaults/blacklist'); +const blacklist = require('metro-config/src/defaults/exclusionList'); module.exports = { resolver: { diff --git a/ios/Video/Features/RCTResourceLoaderDelegate.swift b/ios/Video/Features/RCTResourceLoaderDelegate.swift index e1f11528..52228517 100644 --- a/ios/Video/Features/RCTResourceLoaderDelegate.swift +++ b/ios/Video/Features/RCTResourceLoaderDelegate.swift @@ -44,7 +44,7 @@ class RCTResourceLoaderDelegate: NSObject, AVAssetResourceLoaderDelegate, URLSes } func resourceLoader(_ resourceLoader:AVAssetResourceLoader, didCancel loadingRequest:AVAssetResourceLoadingRequest) { - NSLog("didCancelLoadingRequest") + RCTLog("didCancelLoadingRequest") } func setLicenseResult(_ license:String!) { diff --git a/ios/Video/Features/RCTVideoUtils.swift b/ios/Video/Features/RCTVideoUtils.swift index 5ff5928c..9916db37 100644 --- a/ios/Video/Features/RCTVideoUtils.swift +++ b/ios/Video/Features/RCTVideoUtils.swift @@ -111,10 +111,14 @@ enum RCTVideoUtils { title = value as! String } let language:String! = currentOption?.extendedLanguageTag ?? "" + + let selectedOption: AVMediaSelectionOption? = player.currentItem?.currentMediaSelection.selectedMediaOption(in: group!) + let audioTrack = [ "index": NSNumber(value: i), "title": title, - "language": language + "language": language ?? "", + "selected": currentOption?.displayName == selectedOption?.displayName ] as [String : Any] audioTracks.add(audioTrack) } @@ -137,10 +141,13 @@ enum RCTVideoUtils { title = value as! String } let language:String! = currentOption?.extendedLanguageTag ?? "" + let selectedOpt = player.currentItem?.currentMediaSelection + let selectedOption: AVMediaSelectionOption? = player.currentItem?.currentMediaSelection.selectedMediaOption(in: group!) let textTrack = TextTrack([ "index": NSNumber(value: i), "title": title, - "language": language + "language": language, + "selected": currentOption?.displayName == selectedOption?.displayName ]) textTracks.append(textTrack) } diff --git a/ios/Video/RCTVideo.swift b/ios/Video/RCTVideo.swift index 73c5b0b8..85e8e101 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -1017,6 +1017,8 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH } if _videoLoadStarted { + let audioTracks = RCTVideoUtils.getAudioTrackInfo(_player) + let textTracks = RCTVideoUtils.getTextTrackInfo(_player).map(\.json) onVideoLoad?(["duration": NSNumber(value: duration), "currentTime": NSNumber(value: Float(CMTimeGetSeconds(_playerItem.currentTime()))), "canPlayReverse": NSNumber(value: _playerItem.canPlayReverse), @@ -1030,8 +1032,8 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH "height": width != nil ? NSNumber(value: height!) : "undefinded", "orientation": orientation ], - "audioTracks": RCTVideoUtils.getAudioTrackInfo(_player), - "textTracks": _textTracks ?? RCTVideoUtils.getTextTrackInfo(_player), + "audioTracks": audioTracks, + "textTracks": textTracks, "target": reactTag as Any]) } _videoLoadStarted = false @@ -1090,7 +1092,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH let newRect = change.newValue if !oldRect!.equalTo(newRect!) { if newRect!.equalTo(UIScreen.main.bounds) { - NSLog("in fullscreen") + RCTLog("in fullscreen") self.reactViewController().view.frame = UIScreen.main.bounds self.reactViewController().view.setNeedsLayout() diff --git a/ios/Video/RCTVideoSwiftLog/RCTVideoSwiftLog.swift b/ios/Video/RCTVideoSwiftLog/RCTVideoSwiftLog.swift index 99ca8e8a..0e0e1697 100644 --- a/ios/Video/RCTVideoSwiftLog/RCTVideoSwiftLog.swift +++ b/ios/Video/RCTVideoSwiftLog/RCTVideoSwiftLog.swift @@ -25,29 +25,31 @@ * way into one or the other eventually. Feel free to reuse it as desired. */ +let logHeader: String = "RNV:" + func RCTLogError(_ message: String, _ file: String=#file, _ line: UInt=#line) { - RCTVideoSwiftLog.error(message, file: file, line: line) + RCTVideoSwiftLog.error(logHeader + message, file: file, line: line) } func RCTLogWarn(_ message: String, _ file: String=#file, _ line: UInt=#line) { - RCTVideoSwiftLog.warn(message, file: file, line: line) + RCTVideoSwiftLog.warn(logHeader + message, file: file, line: line) } func RCTLogInfo(_ message: String, _ file: String=#file, _ line: UInt=#line) { - RCTVideoSwiftLog.info(message, file: file, line: line) + RCTVideoSwiftLog.info(logHeader + message, file: file, line: line) } func RCTLog(_ message: String, _ file: String=#file, _ line: UInt=#line) { - RCTVideoSwiftLog.log(message, file: file, line: line) + RCTVideoSwiftLog.log(logHeader + message, file: file, line: line) } func RCTLogTrace(_ message: String, _ file: String=#file, _ line: UInt=#line) { - RCTVideoSwiftLog.trace(message, file: file, line: line) + RCTVideoSwiftLog.trace(logHeader + message, file: file, line: line) } func DebugLog(_ message: String) { #if DEBUG - print(message) + print(logHeader + message) #endif } diff --git a/ios/VideoCaching/RCTVideoCache.m b/ios/VideoCaching/RCTVideoCache.m index 1a2b83a5..05ad65d4 100644 --- a/ios/VideoCaching/RCTVideoCache.m +++ b/ios/VideoCaching/RCTVideoCache.m @@ -30,7 +30,7 @@ options.useDirectorySeparation = NO; #ifdef DEBUG options.debugOutput = ^(NSString *string) { - NSLog(@"Video Cache: %@", string); + RCTLog(@"Video Cache: %@", string); }; #endif [self createTemporaryPath]; @@ -48,7 +48,7 @@ error:&error]; #ifdef DEBUG if (!success || error) { - NSLog(@"Error while! %@", error); + RCTLog(@"Error while! %@", error); } #endif } @@ -64,7 +64,7 @@ [self.videoCache storeData:data forKey:key locked:NO withCallback:^(SPTPersistentCacheResponse * _Nonnull response) { if (response.error) { #ifdef DEBUG - NSLog(@"An error occured while saving the video into the cache: %@", [response.error localizedDescription]); + RCTLog(@"An error occured while saving the video into the cache: %@", [response.error localizedDescription]); #endif handler(NO); return; diff --git a/package.json b/package.json index bde0ebf5..aa671cd9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-video", - "version": "6.0.0-alpha.4", + "version": "6.0.0-alpha.5", "description": "A