From d05231d76b87e2f65bc7648bfb81d01e4054b2de Mon Sep 17 00:00:00 2001 From: Axel Vencatareddy Date: Mon, 27 Nov 2023 21:43:30 +0100 Subject: [PATCH 01/15] feat(ad): add data to onReceiveAdEvent (#3378) * feat(ad): add adData to onReceiveAdEvent * fix: remove adData from response if empty * fix: add getAdData to stub file * chore: fix build without IMA * fix: rename `adData` to `data` --------- Co-authored-by: olivier --- .../common/react/VideoEventEmitter.java | 14 +++++++++++++ .../exoplayer/ReactExoplayerView.java | 6 +++++- .../ads/interactivemedia/v3/api/AdEvent.java | 3 +++ docs/pages/component/events.md | 20 +++++++++++-------- ios/Video/Features/RCTIMAAdsManager.swift | 16 +++++++++++---- src/VideoNativeComponent.ts | 1 + src/types/Ads.ts | 2 +- src/types/events.ts | 1 + src/types/video.ts | 2 +- 9 files changed, 50 insertions(+), 15 deletions(-) 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..8e818f6abb 100644 --- a/android/src/main/java/com/brentvatne/common/react/VideoEventEmitter.java +++ b/android/src/main/java/com/brentvatne/common/react/VideoEventEmitter.java @@ -18,6 +18,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.Map; public class VideoEventEmitter { @@ -413,6 +414,19 @@ public void audioBecomingNoisy() { receiveEvent(EVENT_AUDIO_BECOMING_NOISY, null); } + public void receiveAdEvent(String event, Map data) { + WritableMap map = Arguments.createMap(); + map.putString("event", event); + + WritableMap dataMap = Arguments.createMap(); + for (Map.Entry entry : data.entrySet()) { + dataMap.putString(entry.getKey(), entry.getValue()); + } + map.putMap("data", dataMap); + + receiveEvent(EVENT_ON_RECEIVE_AD_EVENT, map); + } + public void receiveAdEvent(String event) { WritableMap map = Arguments.createMap(); map.putString("event", event); diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index 21ef75a0a2..698856dcb8 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -2100,6 +2100,10 @@ public void setShutterColor(Integer color) { @Override public void onAdEvent(AdEvent adEvent) { - eventEmitter.receiveAdEvent(adEvent.getType().name()); + if (adEvent.getAdData() != null) { + eventEmitter.receiveAdEvent(adEvent.getType().name(), adEvent.getAdData()); + } else { + eventEmitter.receiveAdEvent(adEvent.getType().name()); + } } } 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 index 1913df2937..00f640d636 100644 --- 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 @@ -2,8 +2,11 @@ import androidx.annotation.InspectableProperty; +import java.util.Map; + public abstract class AdEvent { public abstract InspectableProperty getType(); + public abstract Map getAdData(); public interface AdEventListener { public void onAdEvent(AdEvent adEvent); diff --git a/docs/pages/component/events.md b/docs/pages/component/events.md index aa598c4d53..b093377e5f 100644 --- a/docs/pages/component/events.md +++ b/docs/pages/component/events.md @@ -362,10 +362,10 @@ Enum `AdEvent` possible values for [Android](https://developers.google.com/inter | `AD_METADATA` | Android | Fires when an ads list is loaded. | | `AD_PERIOD_ENDED` | iOS | Fired every time the stream switches from advertising or slate to content. This will be fired even when an ad is played a second time or when seeking into an ad (only used for dynamic ad insertion). | | `AD_PERIOD_STARTED` | iOS | Fired every time the stream switches from content to advertising or slate. This will be fired even when an ad is played a second time or when seeking into an ad (only used for dynamic ad insertion). | -| `AD_PROGRESS` | Android | Fires when the ad's current time value changes. Calling getAdData() on this event will return an AdProgressData object. | +| `AD_PROGRESS` | Android | Fires when the ad's current time value changes. The event `data` will be populated with an AdProgressData object. | | `ALL_ADS_COMPLETED` | Android, iOS | Fires when the ads manager is done playing all the valid ads in the ads response, or when the response doesn't return any valid ads. | | `CLICK` | Android, iOS | Fires when the ad is clicked. | -| `COMPLETE` | Android, iOS | Fires when the ad completes playing. | +| `COMPLETED` | Android, iOS | Fires when the ad completes playing. | | `CONTENT_PAUSE_REQUESTED` | Android | Fires when content should be paused. This usually happens right before an ad is about to cover the content. | | `CONTENT_RESUME_REQUESTED` | Android | Fires when content should be resumed. This usually happens when an ad finishes or collapses. | | `CUEPOINTS_CHANGED` | iOS | Cuepoints changed for VOD stream (only used for dynamic ad insertion). | @@ -395,14 +395,18 @@ Enum `AdEvent` possible values for [Android](https://developers.google.com/inter Payload: -| Property | Type | Description | -|----------|---------|-----------------------| -| event | AdEvent | The ad event received | +| Property | Type | Description | +|----------|-------------------------------------|-----------------------| +| event | AdEvent | The ad event received | +| data | Record \| undefined | The ad event data | Example: -```javascript +```json { - "event": "LOADED" + "data": { + "key": "value" + }, + "event": "LOG" } ``` @@ -540,4 +544,4 @@ Example: } ``` -Platforms: Android, iOS \ No newline at end of file +Platforms: Android, iOS diff --git a/ios/Video/Features/RCTIMAAdsManager.swift b/ios/Video/Features/RCTIMAAdsManager.swift index 8a5e0643b6..4cb55fc4c9 100644 --- a/ios/Video/Features/RCTIMAAdsManager.swift +++ b/ios/Video/Features/RCTIMAAdsManager.swift @@ -98,10 +98,18 @@ class RCTIMAAdsManager: NSObject, IMAAdsLoaderDelegate, IMAAdsManagerDelegate, I if _video.onReceiveAdEvent != nil { let type = convertEventToString(event: event.type) - _video.onReceiveAdEvent?([ - "event": type, - "target": _video.reactTag! - ]); + if (event.adData != nil) { + _video.onReceiveAdEvent?([ + "event": type, + "data": event.adData ?? [String](), + "target": _video.reactTag! + ]); + } else { + _video.onReceiveAdEvent?([ + "event": type, + "target": _video.reactTag! + ]); + } } } diff --git a/src/VideoNativeComponent.ts b/src/VideoNativeComponent.ts index 54634e35df..5b9549314b 100644 --- a/src/VideoNativeComponent.ts +++ b/src/VideoNativeComponent.ts @@ -238,6 +238,7 @@ export type OnPictureInPictureStatusChangedData = Readonly<{ }>; export type OnReceiveAdEventData = Readonly<{ + data?: Record; event: AdEvent; }>; diff --git a/src/types/Ads.ts b/src/types/Ads.ts index 6534a80ec0..2d100e222d 100644 --- a/src/types/Ads.ts +++ b/src/types/Ads.ts @@ -32,7 +32,7 @@ export enum AdEvent { */ AD_PERIOD_STARTED = 'AD_PERIOD_STARTED', /** - * Android only: Fires when the ad's current time value changes. Calling getAdData() on this event will return an AdProgressData object. + * Android only: Fires when the ad's current time value changes. The event `data` will be populated with an AdProgressData object. */ AD_PROGRESS = 'AD_PROGRESS', /** diff --git a/src/types/events.ts b/src/types/events.ts index b1bc1e4bac..bfde7a4b70 100644 --- a/src/types/events.ts +++ b/src/types/events.ts @@ -115,6 +115,7 @@ export type OnPictureInPictureStatusChangedData = Readonly<{ }>; export type OnReceiveAdEventData = Readonly<{ + data?: Record; event: AdEvent; }>; diff --git a/src/types/video.ts b/src/types/video.ts index 34b0b3d5f1..ef91e31244 100644 --- a/src/types/video.ts +++ b/src/types/video.ts @@ -174,7 +174,7 @@ export interface ReactVideoProps extends ReactVideoEvents { source?: ReactVideoSource; drm?: Drm; style?: StyleProp; - adTagUrl?: string; // iOS + adTagUrl?: string; audioOnly?: boolean; automaticallyWaitsToMinimizeStalling?: boolean; // iOS backBufferDurationMs?: number; // Android From cac802fb77a96a188bb2f0f48b6e0549ca9a56a3 Mon Sep 17 00:00:00 2001 From: YangJH Date: Fri, 1 Dec 2023 05:16:24 +0900 Subject: [PATCH 02/15] chore(android): replacing deprecated SYSTEM_UI flag (#3386) --- .../exoplayer/ReactExoplayerView.java | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index 698856dcb8..e0b7d45290 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -25,6 +25,9 @@ import androidx.activity.OnBackPressedCallback; import androidx.annotation.NonNull; import androidx.annotation.WorkerThread; +import androidx.core.view.WindowCompat; +import androidx.core.view.WindowInsetsCompat; +import androidx.core.view.WindowInsetsControllerCompat; import androidx.media3.common.AudioAttributes; import androidx.media3.common.C; import androidx.media3.common.Format; @@ -102,6 +105,7 @@ import com.facebook.react.bridge.LifecycleEventListener; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.UiThreadUtil; import com.facebook.react.uimanager.ThemedReactContext; import com.google.ads.interactivemedia.v3.api.AdEvent; import com.google.common.collect.ImmutableList; @@ -1969,34 +1973,27 @@ public void setFullscreen(boolean fullscreen) { } Window window = activity.getWindow(); - View decorView = window.getDecorView(); - int uiOptions; + WindowInsetsControllerCompat controller = new WindowInsetsControllerCompat(window, window.getDecorView()); if (isFullscreen) { - if (Util.SDK_INT >= 19) { // 4.4+ - uiOptions = SYSTEM_UI_FLAG_HIDE_NAVIGATION - | SYSTEM_UI_FLAG_IMMERSIVE_STICKY - | SYSTEM_UI_FLAG_FULLSCREEN; - } else { - uiOptions = SYSTEM_UI_FLAG_HIDE_NAVIGATION - | SYSTEM_UI_FLAG_FULLSCREEN; - } eventEmitter.fullscreenWillPresent(); if (controls && fullScreenPlayerView != null) { fullScreenPlayerView.show(); } - post(() -> { - decorView.setSystemUiVisibility(uiOptions); + UiThreadUtil.runOnUiThread(() -> { + WindowCompat.setDecorFitsSystemWindows(window, false); + controller.hide(WindowInsetsCompat.Type.systemBars()); + controller.setSystemBarsBehavior(WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE); eventEmitter.fullscreenDidPresent(); }); } else { - uiOptions = View.SYSTEM_UI_FLAG_VISIBLE; eventEmitter.fullscreenWillDismiss(); if (controls && fullScreenPlayerView != null) { fullScreenPlayerView.dismiss(); reLayout(exoPlayerView); } - post(() -> { - decorView.setSystemUiVisibility(uiOptions); + UiThreadUtil.runOnUiThread(() -> { + WindowCompat.setDecorFitsSystemWindows(window, true); + controller.show(WindowInsetsCompat.Type.systemBars()); eventEmitter.fullscreenDidDismiss(); }); } From 472820123a61384a4356254fd1c6c42cadc00f95 Mon Sep 17 00:00:00 2001 From: Olivier Bouillet <62574056+freeboub@users.noreply.github.com> Date: Thu, 30 Nov 2023 21:17:41 +0100 Subject: [PATCH 03/15] chore(sample): fix lint and ts sample & add ci jobs to build samples (#3365) * chore: start fixing linter and tsc warning and errors * chore(ci): add basic example build * chore: allow to build with or without ads on android * chore: fix job definition --------- Co-authored-by: olivier Co-authored-by: Krzysztof Moch --- .github/actions/setup-node/action.yml | 34 ++++++ .github/workflows/build-android.yml | 80 +++++++++++++ .github/workflows/build-ios.yml | 165 ++++++++++++++++++++++++++ examples/basic/android/build.gradle | 2 +- examples/basic/ios/Podfile | 9 +- 5 files changed, 287 insertions(+), 3 deletions(-) create mode 100644 .github/actions/setup-node/action.yml create mode 100644 .github/workflows/build-android.yml create mode 100644 .github/workflows/build-ios.yml diff --git a/.github/actions/setup-node/action.yml b/.github/actions/setup-node/action.yml new file mode 100644 index 0000000000..4627f212fc --- /dev/null +++ b/.github/actions/setup-node/action.yml @@ -0,0 +1,34 @@ +name: Setup node_modules +description: Setup Node.js and install dependencies + +inputs: + working-directory: + description: 'working directory for yarn install' + default: ./ + required: false + +runs: + using: composite + steps: + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: 18.x + + - name: Cache dependencies + id: yarn-cache + uses: actions/cache@v3 + with: + path: | + **/node_modules + .yarn/install-state.gz + key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }}-${{ hashFiles('**/package.json', '!node_modules/**') }} + restore-keys: | + ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }} + ${{ runner.os }}-yarn- + - name: Install dependencies + working-directory: ${{ inputs.working-directory }} + if: steps.yarn-cache.outputs.cache-hit != 'true' + run: yarn install --immutable --ignore-scripts + shell: bash + diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml new file mode 100644 index 0000000000..40fcbb3215 --- /dev/null +++ b/.github/workflows/build-android.yml @@ -0,0 +1,80 @@ +name: Build Android + +on: + push: + branches: + - master + paths: + - '.github/workflows/build-android.yml' + - 'android/**' + - 'examples/basic/android/**' + - 'yarn.lock' + - 'examples/basic/yarn.lock' + pull_request: + paths: + - '.github/workflows/build-android.yml' + - 'android/**' + - 'examples/basic/android/**' + - 'yarn.lock' + - 'examples/basic/yarn.lock' + +jobs: + build: + name: Build Android Example App + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup JDK 11 + uses: actions/setup-java@v2 + with: + distribution: 'zulu' + java-version: 11 + java-package: jdk + + - name: Install node_modules + uses: ./.github/actions/setup-node + with: + working-directory: examples/basic + + - name: Restore Gradle cache + uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + - name: Run Gradle Build for basic example + run: cd examples/basic/android && ./gradlew assembleDebug --build-cache && cd ../../.. + + build-without-ads: + name: Build Android Example App Without Ads + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup JDK 11 + uses: actions/setup-java@v2 + with: + distribution: 'zulu' + java-version: 11 + java-package: jdk + + - name: Install node_modules + uses: ./.github/actions/setup-node + with: + working-directory: examples/basic + + - name: Restore Gradle cache + uses: actions/cache@v3 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-gradle- + - name: Run Gradle Build for basic example + run: cd examples/basic/android && export RNV_SAMPLE_ENABLE_ADS=false && ./gradlew assembleDebug --build-cache && cd ../../.. \ No newline at end of file diff --git a/.github/workflows/build-ios.yml b/.github/workflows/build-ios.yml new file mode 100644 index 0000000000..cf969a3476 --- /dev/null +++ b/.github/workflows/build-ios.yml @@ -0,0 +1,165 @@ +name: Build iOS + +on: + push: + branches: + - main + paths: + - '.github/workflows/build-ios.yml' + - 'ios/**' + - '*.podspec' + - 'examples/basic/ios/**' + pull_request: + paths: + - '.github/workflows/build-ios.yml' + - 'ios/**' + - '*.podspec' + - 'examples/basic/ios/**' + +jobs: + build: + name: Build iOS Example App + runs-on: macOS-latest + defaults: + run: + working-directory: examples/basic/ios + steps: + - uses: actions/checkout@v4 + + - name: Install node_modules + uses: ./.github/actions/setup-node + with: + working-directory: examples/basic + + - name: Restore buildcache + uses: mikehardy/buildcache-action@v2 + continue-on-error: true + + - name: Setup Ruby (bundle) + uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.6.10 + bundler-cache: true + + - name: Restore Pods cache + uses: actions/cache@v3 + with: + path: | + examples/basic/ios/Pods + ~/Library/Caches/CocoaPods + ~/.cocoapods + key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }} + restore-keys: | + ${{ runner.os }}-pods- + - name: Install Pods + run: pod install + - name: Install xcpretty + run: gem install xcpretty + - name: Build App + run: "set -o pipefail && xcodebuild \ + -derivedDataPath build -UseModernBuildSystem=YES \ + -workspace videoplayer.xcworkspace \ + -scheme videoplayer \ + -sdk iphonesimulator \ + -configuration Debug \ + -destination 'platform=iOS Simulator,name=iPhone 11 Pro' \ + build \ + CODE_SIGNING_ALLOWED=NO | xcpretty" + + build-with-ads: + name: Build iOS Example App With Ads + runs-on: macOS-latest + defaults: + run: + working-directory: examples/basic/ios + steps: + - uses: actions/checkout@v4 + + - name: Install node_modules + uses: ./.github/actions/setup-node + with: + working-directory: examples/basic + + - name: Restore buildcache + uses: mikehardy/buildcache-action@v2 + continue-on-error: true + + - name: Setup Ruby (bundle) + uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.6.10 + bundler-cache: true + + - name: Restore Pods cache + uses: actions/cache@v3 + with: + path: | + examples/basic/ios/Pods + ~/Library/Caches/CocoaPods + ~/.cocoapods + key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }} + restore-keys: | + ${{ runner.os }}-pods- + - name: Install Pods + run: export RNV_SAMPLE_ENABLE_ADS=true && pod install + - name: Install xcpretty + run: gem install xcpretty + - name: Build App + run: "set -o pipefail && export RNV_SAMPLE_ENABLE_ADS=true && xcodebuild \ + -derivedDataPath build -UseModernBuildSystem=YES \ + -workspace videoplayer.xcworkspace \ + -scheme videoplayer \ + -sdk iphonesimulator \ + -configuration Debug \ + -destination 'platform=iOS Simulator,name=iPhone 11 Pro' \ + build \ + CODE_SIGNING_ALLOWED=NO | xcpretty" + + build-with-caching: + name: Build iOS Example App With Caching + runs-on: macOS-latest + defaults: + run: + working-directory: examples/basic/ios + steps: + - uses: actions/checkout@v4 + + - name: Install node_modules + uses: ./.github/actions/setup-node + with: + working-directory: examples/basic + + - name: Restore buildcache + uses: mikehardy/buildcache-action@v2 + continue-on-error: true + + - name: Setup Ruby (bundle) + uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.6.10 + bundler-cache: true + + - name: Restore Pods cache + uses: actions/cache@v3 + with: + path: | + examples/basic/ios/Pods + ~/Library/Caches/CocoaPods + ~/.cocoapods + key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }} + restore-keys: | + ${{ runner.os }}-pods- + - name: Install Pods + run: export RNV_SAMPLE_VIDEO_CACHING=true && pod install + - name: Install xcpretty + run: gem install xcpretty + - name: Build App + run: "set -o pipefail && export RNV_SAMPLE_VIDEO_CACHING=true && xcodebuild \ + -derivedDataPath build -UseModernBuildSystem=YES \ + -workspace videoplayer.xcworkspace \ + -scheme videoplayer \ + -sdk iphonesimulator \ + -configuration Debug \ + -destination 'platform=iOS Simulator,name=iPhone 11 Pro' \ + build \ + CODE_SIGNING_ALLOWED=NO | xcpretty" \ No newline at end of file diff --git a/examples/basic/android/build.gradle b/examples/basic/android/build.gradle index 8034dfe888..67960abbe3 100644 --- a/examples/basic/android/build.gradle +++ b/examples/basic/android/build.gradle @@ -11,7 +11,7 @@ buildscript { // We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP. ndkVersion = "23.1.7779620" - RNVUseExoplayerIMA = true + RNVUseExoplayerIMA = System.getenv("RNV_SAMPLE_ENABLE_ADS") ?: true } repositories { google() diff --git a/examples/basic/ios/Podfile b/examples/basic/ios/Podfile index fa8c02c8ba..445e22406f 100644 --- a/examples/basic/ios/Podfile +++ b/examples/basic/ios/Podfile @@ -29,8 +29,13 @@ target 'videoplayer' do config = use_native_modules! use_frameworks! :linkage => :static - # $RNVideoUseGoogleIMA = true - $RNVideoUseVideoCaching = true + + if ENV['RNV_SAMPLE_ENABLE_ADS'] + $RNVideoUseGoogleIMA = true + end + if ENV['RNV_SAMPLE_VIDEO_CACHING'] + $RNVideoUseVideoCaching = true + end # Flags change depending on the env values. flags = get_default_flags() From 4d9334b4771f28c51f5410e17702e44e08524d28 Mon Sep 17 00:00:00 2001 From: Krzysztof Moch Date: Fri, 1 Dec 2023 08:51:11 +0100 Subject: [PATCH 04/15] chore(ci): add JS & TS linter check (#3394) --- .github/workflows/check-js.yml | 62 ++++++++++++++++++++++++++++++++++ .github/workflows/ci.yml | 13 ------- package.json | 1 + 3 files changed, 63 insertions(+), 13 deletions(-) create mode 100644 .github/workflows/check-js.yml delete mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/check-js.yml b/.github/workflows/check-js.yml new file mode 100644 index 0000000000..7783978954 --- /dev/null +++ b/.github/workflows/check-js.yml @@ -0,0 +1,62 @@ +name: Check JS + +on: + push: + branches: + - master + paths: + - '.github/workflows/check-js.yml' + - 'src/**' + - '*.json' + - '*.js' + - '*.jsx' + - '*.ts' + - '*.tsx' + - '*.lock' + pull_request: + paths: + - '.github/workflows/check-js.yml' + - 'src/**' + - '*.json' + - '*.js' + - '*.jsx' + - '*.ts' + - '*.tsx' + - '*.lock' + +jobs: + TypeScript-Check: + name: Check TS (tsc) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install node_modules + uses: ./.github/actions/setup-node + + - name: Install reviewdog + uses: reviewdog/action-setup@v1 + + - name: Check TypeScript + run: | + yarn tsc | reviewdog -name="tsc" -efm="%f(%l,%c): error TS%n: %m" -reporter="github-pr-review" -filter-mode="nofilter" -fail-on-error -tee + env: + REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + JS-Lint: + name: Lint JS (eslint, prettier) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install node_modules + uses: ./.github/actions/setup-node + + - name: Run ESLint + run: yarn lint -f @jamesacarr/github-actions + + - name: Run ESLint with auto-fix + run: yarn lint --fix + + - name: Verify no files have changed after auto-fix + run: git diff --exit-code HEAD \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 4f0b43f388..0000000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,13 +0,0 @@ -name: ci -on: [push, pull_request] -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: 18.x - - run: yarn --no-lockfile - - run: yarn lint diff --git a/package.json b/package.json index f1438196f6..8ce94e9387 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "@types/react": "~18.0.0" }, "devDependencies": { + "@jamesacarr/eslint-formatter-github-actions": "^0.2.0", "@react-native/eslint-config": "^0.72.2", "@release-it/conventional-changelog": "^7.0.2", "@types/jest": "^28.1.2", From 596c02d2b3b5175e1653844c39a47ecfd5e23163 Mon Sep 17 00:00:00 2001 From: Axel Vencatareddy Date: Sat, 2 Dec 2023 13:52:01 +0100 Subject: [PATCH 05/15] feat: add onAdError event listener (#3381) * feat: add onAdError event listener * feat: remove onAdError event listener and use already existing * feat: add ERROR event to docs --- .../media3/exoplayer/ima/ImaAdsLoader.java | 4 ++++ .../common/react/VideoEventEmitter.java | 14 ++++++++++++++ .../brentvatne/exoplayer/ReactExoplayerView.java | 13 +++++++++++-- .../ads/interactivemedia/v3/api/AdError.java | 10 ++++++++++ .../interactivemedia/v3/api/AdErrorEvent.java | 9 +++++++++ docs/pages/component/events.md | 1 + ios/Video/Features/RCTIMAAdsManager.swift | 16 +++++++++++++++- ios/Video/RCTVideo.swift | 6 +++--- src/Video.tsx | 4 ++-- src/types/Ads.ts | 4 ++++ 10 files changed, 73 insertions(+), 8 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/androidx/media3/exoplayer/ima/ImaAdsLoader.java b/android/src/main/java/androidx/media3/exoplayer/ima/ImaAdsLoader.java index 148d58033e..3119d09a1d 100644 --- a/android/src/main/java/androidx/media3/exoplayer/ima/ImaAdsLoader.java +++ b/android/src/main/java/androidx/media3/exoplayer/ima/ImaAdsLoader.java @@ -54,6 +54,10 @@ public Builder setAdEventListener(Object ignoredReactExoplayerView) { return this; } + public Builder setAdErrorListener(Object ignoredReactExoplayerView) { + return this; + } + public ImaAdsLoader build() { return null; } 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 8e818f6abb..f119cb2f78 100644 --- a/android/src/main/java/com/brentvatne/common/react/VideoEventEmitter.java +++ b/android/src/main/java/com/brentvatne/common/react/VideoEventEmitter.java @@ -12,6 +12,7 @@ import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableMap; import com.facebook.react.uimanager.events.RCTEventEmitter; +import com.google.ads.interactivemedia.v3.api.AdError; import java.io.PrintWriter; import java.io.StringWriter; @@ -434,6 +435,19 @@ public void receiveAdEvent(String event) { receiveEvent(EVENT_ON_RECEIVE_AD_EVENT, map); } + public void receiveAdErrorEvent(AdError error) { + WritableMap map = Arguments.createMap(); + map.putString("event", "ERROR"); + + WritableMap dataMap = Arguments.createMap(); + dataMap.putString("message", error.getMessage()); + dataMap.putString("code", String.valueOf(error.getErrorCode())); + dataMap.putString("type", String.valueOf(error.getErrorType())); + map.putMap("data", dataMap); + + receiveEvent(EVENT_ON_RECEIVE_AD_EVENT, 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 e0b7d45290..f8c2cc957f 100644 --- a/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -108,6 +108,7 @@ import com.facebook.react.bridge.UiThreadUtil; 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.common.collect.ImmutableList; import java.net.CookieHandler; @@ -132,7 +133,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; @@ -621,8 +623,10 @@ private void initializePlayerCore(ReactExoplayerView self) { .setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF); // Create an AdsLoader. - adsLoader = new ImaAdsLoader.Builder(themedReactContext) + adsLoader = new ImaAdsLoader + .Builder(themedReactContext) .setAdEventListener(this) + .setAdErrorListener(this) .build(); DefaultMediaSourceFactory mediaSourceFactory = new DefaultMediaSourceFactory(mediaDataSourceFactory); @@ -2103,4 +2107,9 @@ 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..86c296105c --- /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 String 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..f96418ff4e --- /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 AdErrorListener { + public void onAdError(AdErrorEvent adErrorEvent); + } +} diff --git a/docs/pages/component/events.md b/docs/pages/component/events.md index b093377e5f..638efcbf2b 100644 --- a/docs/pages/component/events.md +++ b/docs/pages/component/events.md @@ -370,6 +370,7 @@ Enum `AdEvent` possible values for [Android](https://developers.google.com/inter | `CONTENT_RESUME_REQUESTED` | Android | Fires when content should be resumed. This usually happens when an ad finishes or collapses. | | `CUEPOINTS_CHANGED` | iOS | Cuepoints changed for VOD stream (only used for dynamic ad insertion). | | `DURATION_CHANGE` | Android | Fires when the ad's duration changes. | +| `ERROR` | Android, iOS | Fires when an error occurred while loading the ad and prevent it from playing. | | `FIRST_QUARTILE` | Android, iOS | Fires when the ad playhead crosses first quartile. | | `IMPRESSION` | Android | Fires when the impression URL has been pinged. | | `INTERACTION` | Android | Fires when an ad triggers the interaction callback. Ad interactions contain an interaction ID string in the ad data. | diff --git a/ios/Video/Features/RCTIMAAdsManager.swift b/ios/Video/Features/RCTIMAAdsManager.swift index 4cb55fc4c9..e8f9b2568e 100644 --- a/ios/Video/Features/RCTIMAAdsManager.swift +++ b/ios/Video/Features/RCTIMAAdsManager.swift @@ -118,8 +118,22 @@ class RCTIMAAdsManager: NSObject, IMAAdsLoaderDelegate, IMAAdsManagerDelegate, I print("AdsManager error: " + error.message!) } + guard let _video = _video else {return} + + if _video.onReceiveAdEvent != nil { + _video.onReceiveAdEvent?([ + "event": "ERROR", + "data": [ + "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 dfdde5c566..530fe1fd84 100644 --- a/ios/Video/RCTVideo.swift +++ b/ios/Video/RCTVideo.swift @@ -146,7 +146,7 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH name: UIApplication.willResignActiveNotification, object: nil ) - + NotificationCenter.default.addObserver( self, selector: #selector(applicationDidBecomeActive(notification:)), @@ -1346,11 +1346,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/src/Video.tsx b/src/Video.tsx index 7f99651e7d..4a718e87b7 100644 --- a/src/Video.tsx +++ b/src/Video.tsx @@ -13,7 +13,6 @@ 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 { @@ -35,7 +34,8 @@ import type { OnVideoAspectRatioData, OnVideoErrorData, OnVideoTracksData, -} from './types/events'; + ReactVideoProps, +} from './types'; export type VideoSaveData = { uri: string; diff --git a/src/types/Ads.ts b/src/types/Ads.ts index 2d100e222d..3da25f768c 100644 --- a/src/types/Ads.ts +++ b/src/types/Ads.ts @@ -63,6 +63,10 @@ export enum AdEvent { * Android only: Fires when the ad's duration changes. */ DURATION_CHANGE = 'DURATION_CHANGE', + /** + * Fires when an error is encountered and the ad can't be played. + */ + ERROR = 'ERROR', /** * Fires when the ad playhead crosses first quartile. */ From 26043335e1c6b3e90b3678cfde5297a16a25471f Mon Sep 17 00:00:00 2001 From: olivier Date: Sat, 2 Dec 2023 13:59:55 +0100 Subject: [PATCH 06/15] chore: release v6.0.0-beta.1 --- CHANGELOG.md | 23 ++++++++++++++++++++++- package.json | 2 +- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 73a623cfa8..0753728610 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,25 @@ + +# [6.0.0-beta.1](https://github.com/react-native-video/react-native-video/compare/v6.0.0-beta.0...v6.0.0-beta.1) (2023-12-02) + + +### Bug Fixes + +* **android:** ads build and enable ads in android sample ([#3376](https://github.com/react-native-video/react-native-video/issues/3376)) ([fe89122](https://github.com/react-native-video/react-native-video/commit/fe89122524826093689118a4515802d83ca88679)) +* **android:** fix leak caused by removing lifecycle listener too early ([#3380](https://github.com/react-native-video/react-native-video/issues/3380)) ([0c0f317](https://github.com/react-native-video/react-native-video/commit/0c0f3174cb37d3c664a345ea00fcbaafffcd4b10)) +* **android:** revert media3 update, back to 1.1.1 ([#3369](https://github.com/react-native-video/react-native-video/issues/3369)) ([5beef38](https://github.com/react-native-video/react-native-video/commit/5beef383cba13d3ac471bfde27e4acfaa19adfec)) +* **ios:** check for ios url query encoding ([#3384](https://github.com/react-native-video/react-native-video/issues/3384)) ([de4159f](https://github.com/react-native-video/react-native-video/commit/de4159f0c2825a58d88f3882215da4bf51fdbeb2)) +* **ios:** fix pip(when player doesn't fill screen) ([#3363](https://github.com/react-native-video/react-native-video/issues/3363)) ([11f6201](https://github.com/react-native-video/react-native-video/commit/11f62013e33939ce3f78ec7cf40e4da464afa824)) + + +### Features + +* **ad:** add data to onReceiveAdEvent ([#3378](https://github.com/react-native-video/react-native-video/issues/3378)) ([d05231d](https://github.com/react-native-video/react-native-video/commit/d05231d76b87e2f65bc7648bfb81d01e4054b2de)) +* add AdEvent enum to have an exhaustive list of all possible AdEvent values ([#3374](https://github.com/react-native-video/react-native-video/issues/3374)) ([b3744f9](https://github.com/react-native-video/react-native-video/commit/b3744f9b9f25b469fb8b0828e3762842bd5026de)) +* add onAdError event listener ([#3381](https://github.com/react-native-video/react-native-video/issues/3381)) ([596c02d](https://github.com/react-native-video/react-native-video/commit/596c02d2b3b5175e1653844c39a47ecfd5e23163)) +* **android:** bump media3 version from v1.1.1 to v1.2.0 ([#3362](https://github.com/react-native-video/react-native-video/issues/3362)) ([17dbf6e](https://github.com/react-native-video/react-native-video/commit/17dbf6e8264c5c6bed10ff23d96c2b7296a49651)) +* implement startPosition ([#3355](https://github.com/react-native-video/react-native-video/issues/3355)) ([2648502](https://github.com/react-native-video/react-native-video/commit/2648502b364c2802f5a2a7302c31200905c0a807)) + # [6.0.0-beta.1](https://github.com/react-native-video/react-native-video/compare/v6.0.0-beta.0...v6.0.0-beta.1) (WIP) * **android:** fix leak caused by removing lifecycle listener too early ([#3380](https://github.com/react-native-video/react-native-video/pull/3380)) @@ -389,4 +410,4 @@ - Add cookie support for ExoPlayer [#922](https://github.com/react-native-community/react-native-video/pull/922) - Remove ExoPlayer onMetadata that wasn't being used [#1040](https://github.com/react-native-community/react-native-video/pull/1040) - Fix bug where setting the progress interval on iOS didn't work [#800](https://github.com/react-native-community/react-native-video/pull/800) -- Support setting the poster resize mode [#595](https://github.com/react-native-community/react-native-video/pull/595) +- Support setting the poster resize mode [#595](https://github.com/react-native-community/react-native-video/pull/595) \ No newline at end of file diff --git a/package.json b/package.json index 8ce94e9387..c30d8c6e10 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-video", - "version": "6.0.0-beta.0", + "version": "6.0.0-beta.1", "description": "A