bitmovin-player-react-native 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.prettierignore +1 -0
- package/AGENTS.md +91 -0
- package/CHANGELOG.md +25 -2
- package/CONTRIBUTING.md +5 -8
- package/android/build.gradle +3 -3
- package/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt +4 -2
- package/android/src/main/java/com/bitmovin/player/reactnative/util/NonFiniteSanitizer.kt +36 -0
- package/build/hooks/useProxy.d.ts.map +1 -1
- package/build/hooks/useProxy.js +3 -1
- package/build/hooks/useProxy.js.map +1 -1
- package/build/utils/normalizeNonFinite.d.ts +2 -0
- package/build/utils/normalizeNonFinite.d.ts.map +1 -0
- package/build/utils/normalizeNonFinite.js +22 -0
- package/build/utils/normalizeNonFinite.js.map +1 -0
- package/ios/NonFiniteSanitizer.swift +61 -0
- package/ios/PlayerModule.swift +3 -3
- package/ios/RCTConvert+BitmovinPlayer.swift +1 -1
- package/ios/RNBitmovinPlayer.podspec +3 -3
- package/ios/RNPlayerView.swift +1 -1
- package/package.json +2 -2
- package/plugin/build/index.d.ts +1 -4
- package/plugin/build/withAppGradleDependencies.js +26 -0
- package/plugin/build/withBitmovinAndroidConfig.d.ts +2 -5
- package/plugin/build/withBitmovinAndroidConfig.js +5 -2
- package/plugin/build/withBitmovinConfig.d.ts +6 -5
- package/plugin/build/withBitmovinConfig.js +7 -6
- package/plugin/build/withBitmovinIosConfig.d.ts +2 -5
- package/plugin/build/withBitmovinIosConfig.js +5 -2
- package/plugin/src/withAppGradleDependencies.ts +35 -0
- package/plugin/src/withBitmovinAndroidConfig.ts +10 -10
- package/plugin/src/withBitmovinConfig.ts +13 -10
- package/plugin/src/withBitmovinIosConfig.ts +6 -6
- package/scripts/check-dependencies.js +7 -8
- package/scripts/setup-hooks.sh +7 -9
- package/src/hooks/useProxy.ts +3 -1
- package/src/utils/normalizeNonFinite.ts +16 -0
- package/android/src/main/AndroidManifestNew.xml +0 -2
package/.prettierignore
CHANGED
package/AGENTS.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Repository Guidelines
|
|
2
|
+
|
|
3
|
+
## Project Structure & Module Organization
|
|
4
|
+
|
|
5
|
+
- `src/`: TypeScript API surface and React components (library code).
|
|
6
|
+
- `ios/`, `android/`: Native bridges (Swift/Kotlin) used by the Expo Module.
|
|
7
|
+
- `plugin/`: Expo Config Plugin code used during prebuild.
|
|
8
|
+
- `example/`: Runnable sample app (Expo) to develop and validate changes.
|
|
9
|
+
- `integration_test/`: Expo app + Cavy-based integration tests.
|
|
10
|
+
- `scripts/`: Repo tooling (lint/format hooks, helpers). `build/`: transpiled output.
|
|
11
|
+
|
|
12
|
+
## Build, Test, and Development Commands
|
|
13
|
+
|
|
14
|
+
- `yarn bootstrap` — install deps, prebuild example, set up pods.
|
|
15
|
+
- `yarn build` (`build:module`, `build:plugin`) — build the library and plugin to `build/`.
|
|
16
|
+
- `yarn example start | ios | android` — run Metro or launch the example on simulators/emulators.
|
|
17
|
+
- `yarn integration-test test:ios | test:android | test` — run Cavy tests.
|
|
18
|
+
- `yarn lint` (`lint:all`), `yarn typecheck` (`typecheck:all`), `yarn format:all` — quality gates.
|
|
19
|
+
- `yarn docs` — generate TypeDoc documentation.
|
|
20
|
+
|
|
21
|
+
## Coding Style & Naming Conventions
|
|
22
|
+
|
|
23
|
+
- TypeScript: ESLint + Prettier (2 spaces, single quotes, trailing commas `es5`).
|
|
24
|
+
- Names: types/interfaces PascalCase; variables/functions camelCase; React components PascalCase.
|
|
25
|
+
- Files in `src/` use camelCase (e.g., `playerConfig.ts`).
|
|
26
|
+
- iOS: SwiftLint via `yarn lint:ios`; format with `yarn format:ios`.
|
|
27
|
+
- Android: ktlint via `yarn lint:android`; format with `yarn format:android`.
|
|
28
|
+
|
|
29
|
+
## Testing Guidelines
|
|
30
|
+
|
|
31
|
+
- Framework: Cavy; tests live under `integration_test/tests`.
|
|
32
|
+
- Setup: `cp integration_test/.env.example integration_test/.env` and set `EXPO_PUBLIC_BITMOVIN_PLAYER_LICENSE_KEY`.
|
|
33
|
+
- Run: `yarn integration-test test:ios` or `test:android` (simulator/emulator only).
|
|
34
|
+
- Add per‑feature tests (e.g., `Playback.test.ts`) covering happy‑path and error events.
|
|
35
|
+
|
|
36
|
+
## Commit & Pull Request Guidelines
|
|
37
|
+
|
|
38
|
+
- Commits: concise, imperative; do not use prefixes such as `fix:`, `chore:`, etc.
|
|
39
|
+
- PRs: follow `.github/PULL_REQUEST_TEMPLATE.md`; link issues; add screenshots for UI-facing changes.
|
|
40
|
+
- Required before review: `yarn lint:all`, `yarn typecheck:all`, build the library, and run the example on at least one platform.
|
|
41
|
+
- Changelog: add a `CHANGELOG.md` entry for user‑visible behavior changes.
|
|
42
|
+
|
|
43
|
+
## Security & Configuration Tips
|
|
44
|
+
|
|
45
|
+
- Never commit secrets. Keep license keys only in `integration_test/.env` (gitignored).
|
|
46
|
+
|
|
47
|
+
## Docs Index
|
|
48
|
+
|
|
49
|
+
- CONTRIBUTING.md — Development workflow, Development setup, TypeScript Code Style, Linting (pre-commit hooks), Kotlin, Swift, Testing, Scripts.
|
|
50
|
+
- example/README.md — Getting started, Development Setup (.env and license keys), Running the application (iOS/Android/tvOS/Android TV), Bundler only, Architecture, Troubleshooting.
|
|
51
|
+
- integration_test/README.md — Setup, Environment Configuration (.env), Running the tests, Architecture, Test Coverage, Platform Support.
|
|
52
|
+
- README.md — “Sample Application” section links to `example/` README and official docs; use as entry point.
|
|
53
|
+
|
|
54
|
+
## Developer Essentials
|
|
55
|
+
|
|
56
|
+
- Environment
|
|
57
|
+
- Node + Yarn (use Yarn across workspaces); macOS: Xcode + CocoaPods; Android: Android Studio + JDK 17+ (Gradle 8.2 wrapper).
|
|
58
|
+
- Platforms per README: Expo 53+, React Native 0.79+, React 17+.
|
|
59
|
+
- Tooling & Hooks
|
|
60
|
+
- Install hooks with `yarn setup-hooks`; verify with `yarn lint:all` and `yarn typecheck:all` before PRs.
|
|
61
|
+
- SwiftLint/ktlint required for native; use `yarn format:ios` / `yarn format:android` for auto-fixes.
|
|
62
|
+
- Workflow
|
|
63
|
+
- Bootstrap from repo root: `yarn bootstrap`. Re-run prebuilds after env/native/config changes (`yarn example prebuild`, `yarn integration-test prebuild`).
|
|
64
|
+
- Public API: export via `src/index.ts`; keep native module/view names identical to TS wrappers; maintain event parity with TS types.
|
|
65
|
+
- Instance routing uses stable `nativeId` values; ensure native registries clean up on module destruction.
|
|
66
|
+
- Secrets
|
|
67
|
+
- Never commit keys. Store license keys only in `.env` under `example/` and `integration_test/` as documented.
|
|
68
|
+
- Validation
|
|
69
|
+
- Use the example app for manual checks and the integration tests on simulators/emulators for feature coverage.
|
|
70
|
+
|
|
71
|
+
## Routing Public API to React Native
|
|
72
|
+
|
|
73
|
+
- Source of truth: export public surface from `src/index.ts:1-29` (ensure new modules are re‑exported).
|
|
74
|
+
- Use typed Expo wrappers in `src/modules/`:
|
|
75
|
+
- Define `declare class <Feature>Module extends NativeModule<Events> { ... }` and load with `requireNativeModule('<Feature>Module')` (example: `src/modules/PlayerModule.ts:1,251`).
|
|
76
|
+
- Match native names exactly on both platforms:
|
|
77
|
+
- iOS: `Name("PlayerModule")` in `ios/PlayerModule.swift:7`; Android: `Name("PlayerModule")` in `android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt:23`.
|
|
78
|
+
- Bridge views via view managers:
|
|
79
|
+
- TS host component: `requireNativeViewManager('RNPlayerViewManager')` in `src/components/PlayerView/native.ts:33-35`.
|
|
80
|
+
- iOS manager: `ios/RNPlayerViewManager.swift:6`; Android manager: `android/src/main/java/com/bitmovin/player/reactnative/RNPlayerViewManager.kt:12`.
|
|
81
|
+
- Events flow native → JS via Expo `Events(...)` lists; keep parity with TS props:
|
|
82
|
+
- Android events list: `android/.../RNPlayerViewManager.kt:50-112`; iOS: `ios/RNPlayerViewManager.swift:33-60`.
|
|
83
|
+
- TS event typings live in `src/components/PlayerView/nativeEvents.ts:72-160` (extend as needed).
|
|
84
|
+
- Instance routing uses `nativeId` across modules; keep IDs stable and registry-backed on native:
|
|
85
|
+
- Example registry and async functions in `android/src/main/java/com/bitmovin/player/reactnative/OfflineModule.kt:18-24,41-49`.
|
|
86
|
+
- Adding a new API (happy path):
|
|
87
|
+
- TS: add method on a `src/modules/<Feature>Module.ts` wrapper, export from `src/index.ts`.
|
|
88
|
+
- iOS/Android: add matching `AsyncFunction("methodName")` to Expo `ModuleDefinition`; emit events if needed.
|
|
89
|
+
- Views: add `Prop(...)`/`Events(...)` in native managers and extend `NativePlayerViewProps`.
|
|
90
|
+
- Tests: extend integration tests; validate on at least one platform.
|
|
91
|
+
- Naming: keep method/event names identical across TS, iOS, Android.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.2.0] - 2025-10-17
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- Update Bitmovin's native iOS SDK version to `3.97.2`
|
|
8
|
+
- Update Bitmovin's native Android SDK version to `3.128.0+jason`
|
|
9
|
+
- Update IMA SDK dependency on iOS to `3.26.1`
|
|
10
|
+
- Update IMA SDK dependency on tvOS to `4.15.1`
|
|
11
|
+
- Update IMA SDK dependency on Android to `3.37.0`
|
|
12
|
+
|
|
13
|
+
## [1.1.0] - 2025-09-03
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- Update Bitmovin's native Android SDK version to `3.123.0`
|
|
18
|
+
- Update Bitmovin's native iOS SDK version to `3.94.1`
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- Crash when using `react-native-reanimated` along with `bitmovin-player-react-native` and playing a live stream
|
|
23
|
+
- Expo config plugin feature configurations taking no effect
|
|
24
|
+
- Example application crash on tvOS simulator
|
|
25
|
+
|
|
3
26
|
## [1.0.0] - 2025-08-04
|
|
4
27
|
|
|
5
28
|
### Breaking Change
|
|
@@ -14,8 +37,8 @@
|
|
|
14
37
|
|
|
15
38
|
### Changed
|
|
16
39
|
|
|
17
|
-
- Minimum iOS/tvOS version is now 15.1+ (was 14.0+).
|
|
18
|
-
- Minimum Android SDK version is now 24 (was 21).
|
|
40
|
+
- Minimum iOS/tvOS version is now 15.1+ (was 14.0+). Due to a transient React Native minimum [iOS/tvOS version change](https://github.com/react-native-community/discussions-and-proposals/discussions/812).
|
|
41
|
+
- Minimum Android SDK version is now 24 (was 21). Due to a transient React Native minimum [Android version change](https://github.com/react-native-community/discussions-and-proposals/discussions/802).
|
|
19
42
|
- Native setup is now automated through Expo SDK - manual configuration is no longer required for v1.0.0+.
|
|
20
43
|
|
|
21
44
|
## [0.44.0] - 2025-07-25
|
package/CONTRIBUTING.md
CHANGED
|
@@ -41,17 +41,14 @@ To build and run the example app on iOS:
|
|
|
41
41
|
yarn example ios
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
To edit the Swift/Objective-C files, open `example
|
|
44
|
+
To edit the Swift/Objective-C files, open Xcode via `yarn example open:ios` and find the source files at `Pods > Development Pods > RNBitmovinPlayer`.
|
|
45
45
|
|
|
46
|
-
To edit the Kotlin files, open `example
|
|
46
|
+
To edit the Kotlin files, open Android Studio via `yarn example open:android` and find the source files at `bitmovin-player-react-native` under `Android`.
|
|
47
47
|
|
|
48
|
-
##
|
|
48
|
+
## Development setup
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
```yml
|
|
53
|
-
DEVELOPMENT_TEAM = YOUR_TEAM_ID
|
|
54
|
-
```
|
|
50
|
+
- For the Example app, see the relevant [`example/README`](example/README.md#development-setup) section.
|
|
51
|
+
- For the integration tests, see the relevant [`integration_test/README`](README.md#2-environment-configuration) section.
|
|
55
52
|
|
|
56
53
|
## TypeScript Code Style
|
|
57
54
|
|
package/android/build.gradle
CHANGED
|
@@ -104,10 +104,10 @@ dependencies {
|
|
|
104
104
|
implementation "androidx.concurrent:concurrent-futures-ktx:1.1.0"
|
|
105
105
|
|
|
106
106
|
// Google IMA
|
|
107
|
-
implementation 'com.google.ads.interactivemedia.v3:interactivemedia:3.
|
|
107
|
+
implementation 'com.google.ads.interactivemedia.v3:interactivemedia:3.37.0'
|
|
108
108
|
|
|
109
109
|
// Bitmovin
|
|
110
110
|
implementation 'com.google.android.gms:play-services-ads-identifier:18.0.1'
|
|
111
|
-
implementation 'com.bitmovin.player:player:3.
|
|
112
|
-
implementation 'com.bitmovin.player:player-media-session:3.
|
|
111
|
+
implementation 'com.bitmovin.player:player:3.128.0+jason'
|
|
112
|
+
implementation 'com.bitmovin.player:player-media-session:3.128.0+jason'
|
|
113
113
|
}
|
|
@@ -22,6 +22,7 @@ import com.bitmovin.player.api.ui.UiConfig
|
|
|
22
22
|
import com.bitmovin.player.reactnative.converter.toJson
|
|
23
23
|
import com.bitmovin.player.reactnative.converter.toUserInterfaceType
|
|
24
24
|
import com.bitmovin.player.reactnative.ui.RNPictureInPictureHandler
|
|
25
|
+
import com.bitmovin.player.reactnative.util.NonFiniteSanitizer
|
|
25
26
|
import expo.modules.kotlin.AppContext
|
|
26
27
|
import expo.modules.kotlin.viewevent.EventDispatcher
|
|
27
28
|
import expo.modules.kotlin.viewevent.ViewEventCallback
|
|
@@ -615,8 +616,9 @@ class RNPlayerView(context: Context, appContext: AppContext) : ExpoView(context,
|
|
|
615
616
|
}
|
|
616
617
|
|
|
617
618
|
private fun onEvent(dispatcher: ViewEventCallback<Map<String, Any>>, eventData: Map<String, Any>) {
|
|
618
|
-
|
|
619
|
-
|
|
619
|
+
val sanitized = NonFiniteSanitizer.sanitizeEventData(eventData)
|
|
620
|
+
dispatcher(sanitized)
|
|
621
|
+
onBmpEvent(sanitized)
|
|
620
622
|
}
|
|
621
623
|
|
|
622
624
|
fun setFullscreen(isFullscreen: Boolean) {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
package com.bitmovin.player.reactnative.util
|
|
2
|
+
|
|
3
|
+
object NonFiniteSanitizer {
|
|
4
|
+
/**
|
|
5
|
+
* Type-safe method specifically for event data maps.
|
|
6
|
+
* Sanitizes event data while preserving type safety and handling null filtering.
|
|
7
|
+
*/
|
|
8
|
+
fun sanitizeEventData(
|
|
9
|
+
eventData: Map<String, Any>,
|
|
10
|
+
): Map<String, Any> = eventData.mapValues { sanitize(it.value) ?: it.value }
|
|
11
|
+
|
|
12
|
+
private fun sanitize(value: Any?): Any? = when (value) {
|
|
13
|
+
null -> null
|
|
14
|
+
is Double -> if (value.isFinite()) value else value.toSentinel()
|
|
15
|
+
is Float -> if (value.isFinite()) value else value.toSentinel()
|
|
16
|
+
is Map<*, *> -> value.mapValues { sanitize(it.value) }
|
|
17
|
+
is List<*> -> value.mapNotNull { sanitize(it) }
|
|
18
|
+
is Array<*> -> value.mapNotNull { sanitize(it) }
|
|
19
|
+
is Number -> value // Int/Long/Short/Byte
|
|
20
|
+
else -> value
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
private const val SENTINEL_PREFIX = "BMP_"
|
|
25
|
+
|
|
26
|
+
private fun Double.toSentinel(): String = when (this) {
|
|
27
|
+
Double.POSITIVE_INFINITY -> "${SENTINEL_PREFIX}Infinity"
|
|
28
|
+
Double.NEGATIVE_INFINITY -> "${SENTINEL_PREFIX}-Infinity"
|
|
29
|
+
else -> "${SENTINEL_PREFIX}NaN"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private fun Float.toSentinel(): String = when (this) {
|
|
33
|
+
Float.POSITIVE_INFINITY -> "${SENTINEL_PREFIX}Infinity"
|
|
34
|
+
Float.NEGATIVE_INFINITY -> "${SENTINEL_PREFIX}-Infinity"
|
|
35
|
+
else -> "${SENTINEL_PREFIX}NaN"
|
|
36
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useProxy.d.ts","sourceRoot":"","sources":["../../src/hooks/useProxy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAe,MAAM,OAAO,CAAC;AAC/C,OAAO,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"useProxy.d.ts","sourceRoot":"","sources":["../../src/hooks/useProxy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAe,MAAM,OAAO,CAAC;AAC/C,OAAO,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAIlC;;GAEG;AACH,KAAK,QAAQ,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;AAEtC;;GAEG;AACH,KAAK,cAAc,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE;IAAE,WAAW,EAAE,CAAC,CAAA;CAAE,KAAK,IAAI,CAAC;AAE7D;;GAEG;AACH,wBAAgB,QAAQ,CACtB,OAAO,EAAE,SAAS,CAAC,GAAG,CAAC,GACtB,CAAC,CAAC,SAAS,KAAK,EAAE,QAAQ,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,cAAc,CAAC,CAAC,CAAC,CAchE"}
|
package/build/hooks/useProxy.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useCallback } from 'react';
|
|
2
2
|
import { findNodeHandle } from 'react-native';
|
|
3
|
+
import { normalizeNonFinite } from '../utils/normalizeNonFinite';
|
|
3
4
|
/**
|
|
4
5
|
* Create a proxy function that unwraps native events.
|
|
5
6
|
*/
|
|
@@ -10,7 +11,8 @@ export function useProxy(viewRef) {
|
|
|
10
11
|
return;
|
|
11
12
|
}
|
|
12
13
|
const { target, ...eventWithoutTarget } = event.nativeEvent;
|
|
13
|
-
|
|
14
|
+
const sanitized = normalizeNonFinite(eventWithoutTarget);
|
|
15
|
+
callback?.(sanitized);
|
|
14
16
|
}, [viewRef]);
|
|
15
17
|
}
|
|
16
18
|
//# sourceMappingURL=useProxy.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useProxy.js","sourceRoot":"","sources":["../../src/hooks/useProxy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,WAAW,EAAE,MAAM,OAAO,CAAC;AAE/C,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"useProxy.js","sourceRoot":"","sources":["../../src/hooks/useProxy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,WAAW,EAAE,MAAM,OAAO,CAAC;AAE/C,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAYjE;;GAEG;AACH,MAAM,UAAU,QAAQ,CACtB,OAAuB;IAEvB,OAAO,WAAW,CAChB,CAAkB,QAAsB,EAAE,EAAE,CAC1C,CAAC,KAAyB,EAAE,EAAE;QAC5B,MAAM,qBAAqB,GAAY,KAAK,CAAC,WAAmB,CAAC,MAAM,CAAC;QACxE,IAAI,qBAAqB,KAAK,cAAc,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;YAC9D,OAAO;QACT,CAAC;QACD,MAAM,EAAE,MAAM,EAAE,GAAG,kBAAkB,EAAE,GAAG,KAAK,CAAC,WAAkB,CAAC;QACnE,MAAM,SAAS,GAAG,kBAAkB,CAAC,kBAAkB,CAAC,CAAC;QACzD,QAAQ,EAAE,CAAC,SAAc,CAAC,CAAC;IAC7B,CAAC,EACH,CAAC,OAAO,CAAC,CACV,CAAC;AACJ,CAAC","sourcesContent":["import { RefObject, useCallback } from 'react';\nimport { Event } from '../events';\nimport { findNodeHandle } from 'react-native';\nimport { normalizeNonFinite } from '../utils/normalizeNonFinite';\n\n/**\n * A function that takes a generic event as argument.\n */\ntype Callback<E> = (event: E) => void;\n\n/**\n * A function that takes the synthetic version of a generic event as argument.\n */\ntype NativeCallback<E> = (event: { nativeEvent: E }) => void;\n\n/**\n * Create a proxy function that unwraps native events.\n */\nexport function useProxy(\n viewRef: RefObject<any>\n): <E extends Event>(callback?: Callback<E>) => NativeCallback<E> {\n return useCallback(\n <E extends Event>(callback?: Callback<E>) =>\n (event: { nativeEvent: E }) => {\n const eventTargetNodeHandle: number = (event.nativeEvent as any).target;\n if (eventTargetNodeHandle !== findNodeHandle(viewRef.current)) {\n return;\n }\n const { target, ...eventWithoutTarget } = event.nativeEvent as any;\n const sanitized = normalizeNonFinite(eventWithoutTarget);\n callback?.(sanitized as E);\n },\n [viewRef]\n );\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalizeNonFinite.d.ts","sourceRoot":"","sources":["../../src/utils/normalizeNonFinite.ts"],"names":[],"mappings":"AAAA,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAejD"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export function normalizeNonFinite(input) {
|
|
2
|
+
const sentinelPrefix = 'BMP_';
|
|
3
|
+
function walk(v) {
|
|
4
|
+
if (v === `${sentinelPrefix}Infinity`)
|
|
5
|
+
return Infinity;
|
|
6
|
+
if (v === `${sentinelPrefix}-Infinity`)
|
|
7
|
+
return -Infinity;
|
|
8
|
+
if (v === `${sentinelPrefix}NaN`)
|
|
9
|
+
return NaN;
|
|
10
|
+
if (Array.isArray(v))
|
|
11
|
+
return v.map(walk);
|
|
12
|
+
if (v && typeof v === 'object') {
|
|
13
|
+
const out = {};
|
|
14
|
+
for (const k of Object.keys(v))
|
|
15
|
+
out[k] = walk(v[k]);
|
|
16
|
+
return out;
|
|
17
|
+
}
|
|
18
|
+
return v;
|
|
19
|
+
}
|
|
20
|
+
return walk(input);
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=normalizeNonFinite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalizeNonFinite.js","sourceRoot":"","sources":["../../src/utils/normalizeNonFinite.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,kBAAkB,CAAI,KAAQ;IAC5C,MAAM,cAAc,GAAG,MAAM,CAAC;IAC9B,SAAS,IAAI,CAAC,CAAM;QAClB,IAAI,CAAC,KAAK,GAAG,cAAc,UAAU;YAAE,OAAO,QAAQ,CAAC;QACvD,IAAI,CAAC,KAAK,GAAG,cAAc,WAAW;YAAE,OAAO,CAAC,QAAQ,CAAC;QACzD,IAAI,CAAC,KAAK,GAAG,cAAc,KAAK;YAAE,OAAO,GAAG,CAAC;QAC7C,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;YAAE,OAAO,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;YAC/B,MAAM,GAAG,GAAQ,EAAE,CAAC;YACpB,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;gBAAE,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAE,CAAS,CAAC,CAAC,CAAC,CAAC,CAAC;YAC7D,OAAO,GAAG,CAAC;QACb,CAAC;QACD,OAAO,CAAC,CAAC;IACX,CAAC;IACD,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC;AACrB,CAAC","sourcesContent":["export function normalizeNonFinite<T>(input: T): T {\n const sentinelPrefix = 'BMP_';\n function walk(v: any): any {\n if (v === `${sentinelPrefix}Infinity`) return Infinity;\n if (v === `${sentinelPrefix}-Infinity`) return -Infinity;\n if (v === `${sentinelPrefix}NaN`) return NaN;\n if (Array.isArray(v)) return v.map(walk);\n if (v && typeof v === 'object') {\n const out: any = {};\n for (const k of Object.keys(v)) out[k] = walk((v as any)[k]);\n return out;\n }\n return v;\n }\n return walk(input);\n}\n"]}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
internal enum NonFiniteSanitizer {
|
|
4
|
+
/// Type-safe method specifically for event data dictionaries.
|
|
5
|
+
/// Sanitizes event data while preserving type safety.
|
|
6
|
+
static func sanitizeEventData(_ eventData: [String: Any]) -> [String: Any] {
|
|
7
|
+
eventData.reduce(into: [:]) {
|
|
8
|
+
$0[$1.key] = sanitize($1.value)
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Keep existing generic method for backward compatibility
|
|
13
|
+
private static func sanitize(_ value: Any) -> Any {
|
|
14
|
+
switch value {
|
|
15
|
+
case let doubleValue as Double:
|
|
16
|
+
guard doubleValue.isInfinite else { return doubleValue }
|
|
17
|
+
return doubleValue.toSentinel()
|
|
18
|
+
case let floatValue as Float:
|
|
19
|
+
guard floatValue.isInfinite else { return floatValue }
|
|
20
|
+
return floatValue.toSentinel()
|
|
21
|
+
case let dict as [AnyHashable: Any]:
|
|
22
|
+
return dict.reduce(into: [:]) {
|
|
23
|
+
$0[$1.key] = sanitize($1.value)
|
|
24
|
+
}
|
|
25
|
+
case let array as [Any]:
|
|
26
|
+
return array.map { sanitize($0) }
|
|
27
|
+
default:
|
|
28
|
+
return value
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private let sentinelPrefix = "BMP_"
|
|
34
|
+
|
|
35
|
+
private extension Float {
|
|
36
|
+
// Helper to convert non-finite doubles to sentinel strings
|
|
37
|
+
func toSentinel() -> String {
|
|
38
|
+
switch self {
|
|
39
|
+
case .infinity:
|
|
40
|
+
return "\(sentinelPrefix)Infinity"
|
|
41
|
+
case -.infinity:
|
|
42
|
+
return "\(sentinelPrefix)-Infinity"
|
|
43
|
+
default:
|
|
44
|
+
return "\(sentinelPrefix)NaN"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private extension Double {
|
|
50
|
+
// Helper to convert non-finite doubles to sentinel strings
|
|
51
|
+
func toSentinel() -> String {
|
|
52
|
+
switch self {
|
|
53
|
+
case .infinity:
|
|
54
|
+
return "\(sentinelPrefix)Infinity"
|
|
55
|
+
case -.infinity:
|
|
56
|
+
return "\(sentinelPrefix)-Infinity"
|
|
57
|
+
default:
|
|
58
|
+
return "\(sentinelPrefix)NaN"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
package/ios/PlayerModule.swift
CHANGED
|
@@ -188,7 +188,7 @@ public class PlayerModule: Module {
|
|
|
188
188
|
) { [weak self] (nativeId: NativeId, analyticsConfig: [String: Any]?, config: [String: Any]?, networkNativeId: NativeId?, _: String?) in // swiftlint:disable:this line_length
|
|
189
189
|
guard !PlayerRegistry.hasPlayer(nativeId: nativeId),
|
|
190
190
|
let playerConfig = RCTConvert.playerConfig(config),
|
|
191
|
-
let analyticsConfig = RCTConvert.analyticsConfig(analyticsConfig) else { return }
|
|
191
|
+
let analyticsConfig = RCTConvert.analyticsConfig(analyticsConfig) else { return }
|
|
192
192
|
#if os(iOS)
|
|
193
193
|
self?.setupRemoteControlConfig(playerConfig.remoteControlConfig)
|
|
194
194
|
#endif
|
|
@@ -205,7 +205,7 @@ public class PlayerModule: Module {
|
|
|
205
205
|
}.runOnQueue(.main)
|
|
206
206
|
AsyncFunction("loadSource") { [weak self] (nativeId: NativeId, sourceNativeId: NativeId) in
|
|
207
207
|
guard let player = PlayerRegistry.getPlayer(nativeId: nativeId),
|
|
208
|
-
let sourceModule = self?.appContext?.moduleRegistry.get(SourceModule.self),
|
|
208
|
+
let sourceModule = self?.appContext?.moduleRegistry.get(SourceModule.self),
|
|
209
209
|
let source = sourceModule.retrieve(sourceNativeId) else { return }
|
|
210
210
|
player.load(source: source)
|
|
211
211
|
}.runOnQueue(.main)
|
|
@@ -219,7 +219,7 @@ public class PlayerModule: Module {
|
|
|
219
219
|
|
|
220
220
|
private func setupRemoteControlConfig(_ remoteControlConfig: RemoteControlConfig) {
|
|
221
221
|
remoteControlConfig.prepareSource = { [weak self] _, sourceConfig in
|
|
222
|
-
guard let sourceModule = self?.appContext?.moduleRegistry.get(SourceModule.self),
|
|
222
|
+
guard let sourceModule = self?.appContext?.moduleRegistry.get(SourceModule.self),
|
|
223
223
|
let sourceNativeId = sourceModule.nativeId(where: { $0.sourceConfig === sourceConfig }),
|
|
224
224
|
let castSourceConfig = sourceModule.retrieveCastSourceConfig(sourceNativeId) else {
|
|
225
225
|
return nil
|
|
@@ -196,7 +196,7 @@ extension RCTConvert {
|
|
|
196
196
|
return nil
|
|
197
197
|
}
|
|
198
198
|
var request = HttpRequest(url: url, method: method)
|
|
199
|
-
request.headers =
|
|
199
|
+
request.headers = headers
|
|
200
200
|
|
|
201
201
|
if let bodyBase64EncodedString = json["body"] as? String {
|
|
202
202
|
request.body = Data(base64Encoded: bodyBase64EncodedString)
|
|
@@ -28,9 +28,9 @@ Pod::Spec.new do |s|
|
|
|
28
28
|
s.static_framework = true
|
|
29
29
|
|
|
30
30
|
s.dependency 'ExpoModulesCore'
|
|
31
|
-
s.dependency "BitmovinPlayer", "3.
|
|
32
|
-
s.ios.dependency "GoogleAds-IMA-iOS-SDK", "3.
|
|
33
|
-
s.tvos.dependency "GoogleAds-IMA-tvOS-SDK", "4.
|
|
31
|
+
s.dependency "BitmovinPlayer", "3.97.2"
|
|
32
|
+
s.ios.dependency "GoogleAds-IMA-iOS-SDK", "3.26.1"
|
|
33
|
+
s.tvos.dependency "GoogleAds-IMA-tvOS-SDK", "4.15.1"
|
|
34
34
|
|
|
35
35
|
if podfile_properties['BITMOVIN_GOOGLE_CAST_SDK_VERSION'].to_s != ''
|
|
36
36
|
s.ios.dependency "google-cast-sdk", podfile_properties['BITMOVIN_GOOGLE_CAST_SDK_VERSION'].to_s
|
package/ios/RNPlayerView.swift
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bitmovin-player-react-native",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Official React Native bindings for Bitmovin's mobile Player SDKs.",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -94,6 +94,6 @@
|
|
|
94
94
|
"./scripts/format-android.sh",
|
|
95
95
|
"./scripts/lint-android.sh"
|
|
96
96
|
],
|
|
97
|
-
"*.(ts|tsx|js|jsx|md|json|yml|yaml)": "prettier --write"
|
|
97
|
+
"*.(ts|tsx|js|jsx|md|json|yml|yaml)": "prettier --write --no-error-on-unmatched-pattern"
|
|
98
98
|
}
|
|
99
99
|
}
|
package/plugin/build/index.d.ts
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
import Features from './Features';
|
|
2
2
|
export { Features as FeatureFlags };
|
|
3
|
-
declare const _default: import("expo/config-plugins").ConfigPlugin<
|
|
4
|
-
playerLicenseKey: string;
|
|
5
|
-
featureFlags: Features;
|
|
6
|
-
}>;
|
|
3
|
+
declare const _default: import("expo/config-plugins").ConfigPlugin<import("./withBitmovinConfig").BitmovinConfigOptions>;
|
|
7
4
|
export default _default;
|
|
@@ -19,6 +19,30 @@ const withAppGradleDependencies = (config, props) => {
|
|
|
19
19
|
if (filteredDependencies.length === 0) {
|
|
20
20
|
return config;
|
|
21
21
|
}
|
|
22
|
+
const androidBlockStart = config.modResults.contents.search(/^android \{$/m);
|
|
23
|
+
if (androidBlockStart === -1) {
|
|
24
|
+
config_plugins_1.WarningAggregator.addWarningAndroid('withAppGradleDependencies', `Cannot configure app/build.gradle as no android block start was found`);
|
|
25
|
+
return config;
|
|
26
|
+
}
|
|
27
|
+
const fromAndroid = config.modResults.contents.substring(androidBlockStart);
|
|
28
|
+
const androidBlockEnd = fromAndroid.search(/^\}$/m);
|
|
29
|
+
if (androidBlockEnd === -1) {
|
|
30
|
+
config_plugins_1.WarningAggregator.addWarningAndroid('withAppGradleDependencies', `Cannot configure app/build.gradle as no android block end was found`);
|
|
31
|
+
return config;
|
|
32
|
+
}
|
|
33
|
+
const androidPosition = androidBlockStart + androidBlockEnd;
|
|
34
|
+
const compileOptions = [];
|
|
35
|
+
compileOptions.push(`${combinedProps.spacing}compileOptions {`);
|
|
36
|
+
compileOptions.push('\n');
|
|
37
|
+
compileOptions.push(`${combinedProps.spacing}setCoreLibraryDesugaringEnabled(true)`);
|
|
38
|
+
compileOptions.push('\n');
|
|
39
|
+
compileOptions.push(`${combinedProps.spacing}}`);
|
|
40
|
+
compileOptions.push('\n');
|
|
41
|
+
config.modResults.contents = [
|
|
42
|
+
config.modResults.contents.slice(0, androidPosition),
|
|
43
|
+
...compileOptions,
|
|
44
|
+
config.modResults.contents.slice(androidPosition),
|
|
45
|
+
].join('');
|
|
22
46
|
const dependenciesBlockStart = config.modResults.contents.search(/^dependencies \{$/m);
|
|
23
47
|
if (dependenciesBlockStart === -1) {
|
|
24
48
|
config_plugins_1.WarningAggregator.addWarningAndroid('withAppGradleDependencies', `Cannot configure app/build.gradle as no dependency block start was found`);
|
|
@@ -33,6 +57,8 @@ const withAppGradleDependencies = (config, props) => {
|
|
|
33
57
|
const position = dependenciesBlockStart + dependenciesBlockEnd;
|
|
34
58
|
let insertedDependencies = [];
|
|
35
59
|
insertedDependencies.push('\n');
|
|
60
|
+
insertedDependencies.push(`${combinedProps.spacing}coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5'`);
|
|
61
|
+
insertedDependencies.push('\n');
|
|
36
62
|
filteredDependencies.forEach((dependency) => {
|
|
37
63
|
insertedDependencies.push(`${combinedProps.spacing}implementation '${dependency}'\n`);
|
|
38
64
|
});
|
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
import { ConfigPlugin } from 'expo/config-plugins';
|
|
2
|
-
import
|
|
3
|
-
declare const withBitmovinAndroidConfig: ConfigPlugin<
|
|
4
|
-
playerLicenseKey: string;
|
|
5
|
-
features: Features;
|
|
6
|
-
}>;
|
|
2
|
+
import { BitmovinConfigOptions } from './withBitmovinConfig';
|
|
3
|
+
declare const withBitmovinAndroidConfig: ConfigPlugin<BitmovinConfigOptions>;
|
|
7
4
|
export default withBitmovinAndroidConfig;
|
|
@@ -5,7 +5,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const config_plugins_1 = require("expo/config-plugins");
|
|
7
7
|
const withAppGradleDependencies_1 = __importDefault(require("./withAppGradleDependencies"));
|
|
8
|
-
const withBitmovinAndroidConfig = (config,
|
|
8
|
+
const withBitmovinAndroidConfig = (config, options) => {
|
|
9
|
+
const { playerLicenseKey = '', features = {} } = options || {};
|
|
9
10
|
const offlineFeatureConfig = typeof features.offline === 'object'
|
|
10
11
|
? features.offline
|
|
11
12
|
: {
|
|
@@ -36,7 +37,9 @@ const withBitmovinAndroidConfig = (config, { playerLicenseKey, features }) => {
|
|
|
36
37
|
}
|
|
37
38
|
config = (0, config_plugins_1.withAndroidManifest)(config, (config) => {
|
|
38
39
|
const mainApplication = config_plugins_1.AndroidConfig.Manifest.getMainApplicationOrThrow(config.modResults);
|
|
39
|
-
|
|
40
|
+
if (playerLicenseKey) {
|
|
41
|
+
config_plugins_1.AndroidConfig.Manifest.addMetaDataItemToMainApplication(mainApplication, 'BITMOVIN_PLAYER_LICENSE_KEY', playerLicenseKey);
|
|
42
|
+
}
|
|
40
43
|
config.modResults.manifest['uses-permission'] =
|
|
41
44
|
config.modResults.manifest['uses-permission'] || [];
|
|
42
45
|
// Configure Picture-in-Picture support
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { ConfigPlugin } from 'expo/config-plugins';
|
|
2
2
|
import Features from './Features';
|
|
3
|
+
export interface BitmovinConfigOptions {
|
|
4
|
+
playerLicenseKey?: string;
|
|
5
|
+
features?: Features;
|
|
6
|
+
}
|
|
3
7
|
/**
|
|
4
8
|
* Expo Config Plugin for Bitmovin Player.
|
|
5
9
|
* This plugin configures the Bitmovin Player for both iOS and Android platforms.
|
|
@@ -16,7 +20,7 @@ import Features from './Features';
|
|
|
16
20
|
* 'bitmovin-player-react-native',
|
|
17
21
|
* {
|
|
18
22
|
* playerLicenseKey: 'YOUR_BITMOVIN_PLAYER_LICENSE_KEY',
|
|
19
|
-
*
|
|
23
|
+
* features: {
|
|
20
24
|
* airPlay: true,
|
|
21
25
|
* backgroundPlayback: true,
|
|
22
26
|
* googleCastSDK: { android: '21.3.0', ios: '4.8.1.2' },
|
|
@@ -28,8 +32,5 @@ import Features from './Features';
|
|
|
28
32
|
* ]
|
|
29
33
|
* };
|
|
30
34
|
*/
|
|
31
|
-
declare const withBitmovinConfig: ConfigPlugin<
|
|
32
|
-
playerLicenseKey: string;
|
|
33
|
-
featureFlags: Features;
|
|
34
|
-
}>;
|
|
35
|
+
declare const withBitmovinConfig: ConfigPlugin<BitmovinConfigOptions>;
|
|
35
36
|
export default withBitmovinConfig;
|
|
@@ -5,7 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const withBitmovinIosConfig_1 = __importDefault(require("./withBitmovinIosConfig"));
|
|
7
7
|
const withBitmovinAndroidConfig_1 = __importDefault(require("./withBitmovinAndroidConfig"));
|
|
8
|
-
const
|
|
8
|
+
const defaultFeatures = {
|
|
9
9
|
airPlay: false,
|
|
10
10
|
backgroundPlayback: false,
|
|
11
11
|
googleCastSDK: undefined,
|
|
@@ -28,7 +28,7 @@ const defaultFeatureFlags = {
|
|
|
28
28
|
* 'bitmovin-player-react-native',
|
|
29
29
|
* {
|
|
30
30
|
* playerLicenseKey: 'YOUR_BITMOVIN_PLAYER_LICENSE_KEY',
|
|
31
|
-
*
|
|
31
|
+
* features: {
|
|
32
32
|
* airPlay: true,
|
|
33
33
|
* backgroundPlayback: true,
|
|
34
34
|
* googleCastSDK: { android: '21.3.0', ios: '4.8.1.2' },
|
|
@@ -40,10 +40,11 @@ const defaultFeatureFlags = {
|
|
|
40
40
|
* ]
|
|
41
41
|
* };
|
|
42
42
|
*/
|
|
43
|
-
const withBitmovinConfig = (config,
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
config = (0,
|
|
43
|
+
const withBitmovinConfig = (config, options) => {
|
|
44
|
+
const { playerLicenseKey, features } = options;
|
|
45
|
+
const mergedFeatures = { ...defaultFeatures, ...(features || {}) };
|
|
46
|
+
config = (0, withBitmovinIosConfig_1.default)(config, { playerLicenseKey, features: mergedFeatures });
|
|
47
|
+
config = (0, withBitmovinAndroidConfig_1.default)(config, { playerLicenseKey, features: mergedFeatures });
|
|
47
48
|
return config;
|
|
48
49
|
};
|
|
49
50
|
exports.default = withBitmovinConfig;
|
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
import { ConfigPlugin } from 'expo/config-plugins';
|
|
2
|
-
import
|
|
3
|
-
declare const withBitmovinIosConfig: ConfigPlugin<
|
|
4
|
-
playerLicenseKey: string;
|
|
5
|
-
features: Features;
|
|
6
|
-
}>;
|
|
2
|
+
import { BitmovinConfigOptions } from './withBitmovinConfig';
|
|
3
|
+
declare const withBitmovinIosConfig: ConfigPlugin<BitmovinConfigOptions>;
|
|
7
4
|
export default withBitmovinIosConfig;
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const config_plugins_1 = require("expo/config-plugins");
|
|
4
4
|
const isTV = !!process.env.EXPO_TV;
|
|
5
|
-
const withBitmovinIosConfig = (config,
|
|
5
|
+
const withBitmovinIosConfig = (config, options) => {
|
|
6
|
+
const { playerLicenseKey = '', features = {} } = options || {};
|
|
6
7
|
const offlineFeatureConfig = typeof features.offline === 'object'
|
|
7
8
|
? features.offline
|
|
8
9
|
: {
|
|
@@ -18,7 +19,9 @@ const withBitmovinIosConfig = (config, { playerLicenseKey, features }) => {
|
|
|
18
19
|
: features.googleCastSDK.ios
|
|
19
20
|
: null;
|
|
20
21
|
config = (0, config_plugins_1.withInfoPlist)(config, (config) => {
|
|
21
|
-
|
|
22
|
+
if (playerLicenseKey) {
|
|
23
|
+
config.modResults['BitmovinPlayerLicenseKey'] = playerLicenseKey;
|
|
24
|
+
}
|
|
22
25
|
if (features.backgroundPlayback ||
|
|
23
26
|
features.airPlay ||
|
|
24
27
|
features.pictureInPicture) {
|
|
@@ -38,6 +38,39 @@ const withAppGradleDependencies: ConfigPlugin<PluginProps> = (
|
|
|
38
38
|
return config;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
const androidBlockStart = config.modResults.contents.search(/^android \{$/m);
|
|
42
|
+
if (androidBlockStart === -1) {
|
|
43
|
+
WarningAggregator.addWarningAndroid(
|
|
44
|
+
'withAppGradleDependencies',
|
|
45
|
+
`Cannot configure app/build.gradle as no android block start was found`
|
|
46
|
+
);
|
|
47
|
+
return config;
|
|
48
|
+
}
|
|
49
|
+
const fromAndroid = config.modResults.contents.substring(
|
|
50
|
+
androidBlockStart
|
|
51
|
+
);
|
|
52
|
+
const androidBlockEnd = fromAndroid.search(/^\}$/m);
|
|
53
|
+
if (androidBlockEnd === -1) {
|
|
54
|
+
WarningAggregator.addWarningAndroid(
|
|
55
|
+
'withAppGradleDependencies',
|
|
56
|
+
`Cannot configure app/build.gradle as no android block end was found`
|
|
57
|
+
);
|
|
58
|
+
return config;
|
|
59
|
+
}
|
|
60
|
+
const androidPosition = androidBlockStart + androidBlockEnd;
|
|
61
|
+
const compileOptions = []
|
|
62
|
+
compileOptions.push(`${combinedProps.spacing}compileOptions {`)
|
|
63
|
+
compileOptions.push('\n');
|
|
64
|
+
compileOptions.push(`${combinedProps.spacing}setCoreLibraryDesugaringEnabled(true)`)
|
|
65
|
+
compileOptions.push('\n');
|
|
66
|
+
compileOptions.push(`${combinedProps.spacing}}`)
|
|
67
|
+
compileOptions.push('\n');
|
|
68
|
+
config.modResults.contents = [
|
|
69
|
+
config.modResults.contents.slice(0, androidPosition),
|
|
70
|
+
... compileOptions,
|
|
71
|
+
config.modResults.contents.slice(androidPosition),
|
|
72
|
+
].join('');
|
|
73
|
+
|
|
41
74
|
const dependenciesBlockStart =
|
|
42
75
|
config.modResults.contents.search(/^dependencies \{$/m);
|
|
43
76
|
if (dependenciesBlockStart === -1) {
|
|
@@ -61,6 +94,8 @@ const withAppGradleDependencies: ConfigPlugin<PluginProps> = (
|
|
|
61
94
|
const position = dependenciesBlockStart + dependenciesBlockEnd;
|
|
62
95
|
let insertedDependencies: string[] = [];
|
|
63
96
|
insertedDependencies.push('\n');
|
|
97
|
+
insertedDependencies.push(`${combinedProps.spacing}coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5'`)
|
|
98
|
+
insertedDependencies.push('\n');
|
|
64
99
|
filteredDependencies.forEach((dependency) => {
|
|
65
100
|
insertedDependencies.push(
|
|
66
101
|
`${combinedProps.spacing}implementation '${dependency}'\n`
|
|
@@ -4,15 +4,13 @@ import {
|
|
|
4
4
|
withAndroidManifest,
|
|
5
5
|
withGradleProperties,
|
|
6
6
|
} from 'expo/config-plugins';
|
|
7
|
-
import Features from './Features';
|
|
8
7
|
import withAppGradleDependencies from './withAppGradleDependencies';
|
|
8
|
+
import { BitmovinConfigOptions } from './withBitmovinConfig';
|
|
9
9
|
|
|
10
10
|
type ManifestActivity = AndroidConfig.Manifest.ManifestActivity;
|
|
11
11
|
|
|
12
|
-
const withBitmovinAndroidConfig: ConfigPlugin<{
|
|
13
|
-
playerLicenseKey
|
|
14
|
-
features: Features;
|
|
15
|
-
}> = (config, { playerLicenseKey, features }) => {
|
|
12
|
+
const withBitmovinAndroidConfig: ConfigPlugin<BitmovinConfigOptions> = (config, options) => {
|
|
13
|
+
const { playerLicenseKey = '', features = {} } = options || {};
|
|
16
14
|
const offlineFeatureConfig =
|
|
17
15
|
typeof features.offline === 'object'
|
|
18
16
|
? features.offline
|
|
@@ -50,11 +48,13 @@ const withBitmovinAndroidConfig: ConfigPlugin<{
|
|
|
50
48
|
config.modResults
|
|
51
49
|
);
|
|
52
50
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
51
|
+
if (playerLicenseKey) {
|
|
52
|
+
AndroidConfig.Manifest.addMetaDataItemToMainApplication(
|
|
53
|
+
mainApplication,
|
|
54
|
+
'BITMOVIN_PLAYER_LICENSE_KEY',
|
|
55
|
+
playerLicenseKey
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
58
|
|
|
59
59
|
config.modResults.manifest['uses-permission'] =
|
|
60
60
|
config.modResults.manifest['uses-permission'] || [];
|
|
@@ -3,7 +3,7 @@ import withBitmovinIosConfig from './withBitmovinIosConfig';
|
|
|
3
3
|
import Features from './Features';
|
|
4
4
|
import withBitmovinAndroidConfig from './withBitmovinAndroidConfig';
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
const defaultFeatures: Features = {
|
|
7
7
|
airPlay: false,
|
|
8
8
|
backgroundPlayback: false,
|
|
9
9
|
googleCastSDK: undefined,
|
|
@@ -11,6 +11,11 @@ const defaultFeatureFlags: Features = {
|
|
|
11
11
|
pictureInPicture: false,
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
+
export interface BitmovinConfigOptions {
|
|
15
|
+
playerLicenseKey?: string;
|
|
16
|
+
features?: Features;
|
|
17
|
+
}
|
|
18
|
+
|
|
14
19
|
/**
|
|
15
20
|
* Expo Config Plugin for Bitmovin Player.
|
|
16
21
|
* This plugin configures the Bitmovin Player for both iOS and Android platforms.
|
|
@@ -18,7 +23,7 @@ const defaultFeatureFlags: Features = {
|
|
|
18
23
|
* @param config - The Expo config object.
|
|
19
24
|
* @param options - An object containing the player license key and feature flags.
|
|
20
25
|
* @returns The modified Expo config object with Bitmovin Player configurations.
|
|
21
|
-
*
|
|
26
|
+
*
|
|
22
27
|
* @example
|
|
23
28
|
* // app.config.js
|
|
24
29
|
* module.exports = {
|
|
@@ -27,7 +32,7 @@ const defaultFeatureFlags: Features = {
|
|
|
27
32
|
* 'bitmovin-player-react-native',
|
|
28
33
|
* {
|
|
29
34
|
* playerLicenseKey: 'YOUR_BITMOVIN_PLAYER_LICENSE_KEY',
|
|
30
|
-
*
|
|
35
|
+
* features: {
|
|
31
36
|
* airPlay: true,
|
|
32
37
|
* backgroundPlayback: true,
|
|
33
38
|
* googleCastSDK: { android: '21.3.0', ios: '4.8.1.2' },
|
|
@@ -39,13 +44,11 @@ const defaultFeatureFlags: Features = {
|
|
|
39
44
|
* ]
|
|
40
45
|
* };
|
|
41
46
|
*/
|
|
42
|
-
const withBitmovinConfig: ConfigPlugin<{
|
|
43
|
-
playerLicenseKey
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
config = withBitmovinIosConfig(config, { playerLicenseKey, features });
|
|
48
|
-
config = withBitmovinAndroidConfig(config, { playerLicenseKey, features });
|
|
47
|
+
const withBitmovinConfig: ConfigPlugin<BitmovinConfigOptions> = (config, options) => {
|
|
48
|
+
const { playerLicenseKey, features } = options;
|
|
49
|
+
const mergedFeatures = { ...defaultFeatures, ...(features || {}) };
|
|
50
|
+
config = withBitmovinIosConfig(config, { playerLicenseKey, features: mergedFeatures });
|
|
51
|
+
config = withBitmovinAndroidConfig(config, { playerLicenseKey, features: mergedFeatures });
|
|
49
52
|
return config;
|
|
50
53
|
};
|
|
51
54
|
|
|
@@ -3,14 +3,12 @@ import {
|
|
|
3
3
|
withInfoPlist,
|
|
4
4
|
withPodfileProperties,
|
|
5
5
|
} from 'expo/config-plugins';
|
|
6
|
-
import
|
|
6
|
+
import { BitmovinConfigOptions } from './withBitmovinConfig';
|
|
7
7
|
|
|
8
8
|
const isTV = !!process.env.EXPO_TV;
|
|
9
9
|
|
|
10
|
-
const withBitmovinIosConfig: ConfigPlugin<{
|
|
11
|
-
playerLicenseKey
|
|
12
|
-
features: Features;
|
|
13
|
-
}> = (config, { playerLicenseKey, features }) => {
|
|
10
|
+
const withBitmovinIosConfig: ConfigPlugin<BitmovinConfigOptions> = (config, options) => {
|
|
11
|
+
const { playerLicenseKey = '', features = {} } = options || {};
|
|
14
12
|
const offlineFeatureConfig =
|
|
15
13
|
typeof features.offline === 'object'
|
|
16
14
|
? features.offline
|
|
@@ -28,7 +26,9 @@ const withBitmovinIosConfig: ConfigPlugin<{
|
|
|
28
26
|
: null;
|
|
29
27
|
|
|
30
28
|
config = withInfoPlist(config, (config) => {
|
|
31
|
-
|
|
29
|
+
if (playerLicenseKey) {
|
|
30
|
+
config.modResults['BitmovinPlayerLicenseKey'] = playerLicenseKey;
|
|
31
|
+
}
|
|
32
32
|
if (
|
|
33
33
|
features.backgroundPlayback ||
|
|
34
34
|
features.airPlay ||
|
|
@@ -8,19 +8,18 @@
|
|
|
8
8
|
// Fail install if required app-level deps are missing
|
|
9
9
|
const required = ['expo', 'expo-crypto'];
|
|
10
10
|
const has = (name) => {
|
|
11
|
-
try {
|
|
12
|
-
require.resolve(`${name}/package.json`, { paths: [process.cwd()] });
|
|
13
|
-
return true;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
return false;
|
|
11
|
+
try {
|
|
12
|
+
require.resolve(`${name}/package.json`, { paths: [process.cwd()] });
|
|
13
|
+
return true;
|
|
14
|
+
} catch {
|
|
15
|
+
return false;
|
|
17
16
|
}
|
|
18
17
|
};
|
|
19
18
|
const missing = required.filter((r) => !has(r));
|
|
20
19
|
if (missing.length) {
|
|
21
20
|
console.error(
|
|
22
21
|
`\n[bitmovin-player-react-native] Missing required deps in your app: ${missing.join(', ')}\n` +
|
|
23
|
-
|
|
22
|
+
`Install: npx expo install ${missing.join(' ')}\n`
|
|
24
23
|
);
|
|
25
24
|
process.exit(1);
|
|
26
|
-
}
|
|
25
|
+
}
|
package/scripts/setup-hooks.sh
CHANGED
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
3
5
|
# Setup git pre-commit hooks for the project
|
|
4
6
|
echo "Setting up pre-commit hooks..."
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
if [ ! -d ".git" ]; then
|
|
8
|
-
echo "Error: Not a git repository. Please run this from the root of the project."
|
|
9
|
-
exit 1
|
|
10
|
-
fi
|
|
8
|
+
HOOK_DIR=$(git rev-parse --git-path hooks)
|
|
11
9
|
|
|
12
10
|
# Check if hooks directory exists
|
|
13
|
-
if [ ! -d "
|
|
14
|
-
echo "Creating
|
|
15
|
-
mkdir -p
|
|
11
|
+
if [ ! -d "$HOOK_DIR" ]; then
|
|
12
|
+
echo "Creating $HOOK_DIR directory..."
|
|
13
|
+
mkdir -p "$HOOK_DIR"
|
|
16
14
|
fi
|
|
17
15
|
|
|
18
16
|
# Get the directory where this script is located
|
|
@@ -26,7 +24,7 @@ if [ ! -f "$PRE_COMMIT_SOURCE" ]; then
|
|
|
26
24
|
fi
|
|
27
25
|
|
|
28
26
|
# Check if pre-commit hook already exists and compare content
|
|
29
|
-
HOOK_PATH="
|
|
27
|
+
HOOK_PATH="$HOOK_DIR/pre-commit"
|
|
30
28
|
NEEDS_UPDATE=true
|
|
31
29
|
|
|
32
30
|
if [ -f "$HOOK_PATH" ]; then
|
package/src/hooks/useProxy.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { RefObject, useCallback } from 'react';
|
|
2
2
|
import { Event } from '../events';
|
|
3
3
|
import { findNodeHandle } from 'react-native';
|
|
4
|
+
import { normalizeNonFinite } from '../utils/normalizeNonFinite';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* A function that takes a generic event as argument.
|
|
@@ -26,7 +27,8 @@ export function useProxy(
|
|
|
26
27
|
return;
|
|
27
28
|
}
|
|
28
29
|
const { target, ...eventWithoutTarget } = event.nativeEvent as any;
|
|
29
|
-
|
|
30
|
+
const sanitized = normalizeNonFinite(eventWithoutTarget);
|
|
31
|
+
callback?.(sanitized as E);
|
|
30
32
|
},
|
|
31
33
|
[viewRef]
|
|
32
34
|
);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function normalizeNonFinite<T>(input: T): T {
|
|
2
|
+
const sentinelPrefix = 'BMP_';
|
|
3
|
+
function walk(v: any): any {
|
|
4
|
+
if (v === `${sentinelPrefix}Infinity`) return Infinity;
|
|
5
|
+
if (v === `${sentinelPrefix}-Infinity`) return -Infinity;
|
|
6
|
+
if (v === `${sentinelPrefix}NaN`) return NaN;
|
|
7
|
+
if (Array.isArray(v)) return v.map(walk);
|
|
8
|
+
if (v && typeof v === 'object') {
|
|
9
|
+
const out: any = {};
|
|
10
|
+
for (const k of Object.keys(v)) out[k] = walk((v as any)[k]);
|
|
11
|
+
return out;
|
|
12
|
+
}
|
|
13
|
+
return v;
|
|
14
|
+
}
|
|
15
|
+
return walk(input);
|
|
16
|
+
}
|