Skip to content

Commit

Permalink
Merge branch 'improve-player-testing-setup' into add-ads-player-tests
Browse files Browse the repository at this point in the history
  • Loading branch information
rolandkakonyi committed Dec 13, 2023
2 parents 19fff75 + e9ee5c1 commit d833a12
Show file tree
Hide file tree
Showing 31 changed files with 1,416 additions and 1,913 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## [unreleased]

### Changed

- React Native version to `0.72.6`
- React Native peer dependency version to `0.65.0+`

## [0.14.2] (2023-11-27)

### Added
Expand All @@ -14,6 +21,7 @@
- iOS: `onEvent` on iOS has incomplete payload information
- tvOS: Picture in Picture sample screen has unwanted padding
- iOS: hide home indicator when entering fullscreen mode in the example application
- iOS: invalid `loadingState` value in `SeekEvent`, `SourceLoadEvent`, `SourceLoadedEvent` and in `SourceUnloadedEvent`

## [0.14.1] (2023-11-16)

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ This is an open-source project created to enable customers to integrate the Bitm

## Platform Support

This library requires at least React Native 0.64+ and React 17+ to work properly. The **officially supported** platforms are:
This library requires at least React Native 0.65+ and React 17+ to work properly. The **officially supported** platforms are:

- **iOS/iPadOS/tvOS:** 14.0+
- **Android:** 5.0+
Expand Down
2 changes: 1 addition & 1 deletion RNBitmovinPlayer.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Pod::Spec.new do |s|
s.source_files = "ios/**/*.{h,m,mm,swift}"

s.dependency "React-Core"
s.dependency "BitmovinPlayer", "3.49.0"
s.dependency "BitmovinPlayer", "3.50.0"
s.ios.dependency "GoogleAds-IMA-iOS-SDK", "3.18.4"
s.tvos.dependency "GoogleAds-IMA-tvOS-SDK", "4.8.2"
end
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -103,5 +103,5 @@ dependencies {
// Bitmovin
implementation 'com.google.ads.interactivemedia.v3:interactivemedia:3.31.0'
implementation 'com.google.android.gms:play-services-ads-identifier:18.0.1'
implementation 'com.bitmovin.player:player:3.51.0+jason'
implementation 'com.bitmovin.player:player:3.53.0+jason'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package com.bitmovin.player.reactnative

import android.util.Log
import com.bitmovin.player.api.Player
import com.bitmovin.player.api.source.Source
import com.bitmovin.player.reactnative.extensions.drmModule
import com.bitmovin.player.reactnative.extensions.offlineModule
import com.bitmovin.player.reactnative.extensions.playerModule
import com.bitmovin.player.reactnative.extensions.sourceModule
import com.bitmovin.player.reactnative.extensions.uiManagerModule
import com.facebook.react.bridge.*
import com.facebook.react.uimanager.UIManagerModule

private const val MODULE_NAME = "BitmovinBaseModule"

/**
* Base for Bitmovin React modules.
*
* Provides many helper methods that are promise exception safe.
*
* In general, code should not throw while resolving a [Promise]. Instead, [Promise.reject] should be used.
* This doesn't match Kotlin's error style, which uses exception. The helper methods in this class, provide such
* convenience, they can only be called in a context that will catch any Exception and reject the [Promise].
*
*/
abstract class BitmovinBaseModule(
protected val context: ReactApplicationContext,
) : ReactContextBaseJavaModule(context) {
/**
* Runs [block] on the UI thread with [UIManagerModule.addUIBlock] and [TPromise.resolve] [this] with
* its return value. If [block] throws, [Promise.reject] [this] with the [Throwable].
*/
protected inline fun <T, R : T> TPromise<T>.resolveOnUiThread(
crossinline block: RejectPromiseOnExceptionBlock.() -> R,
) {
val uiManager = runAndRejectOnException { uiManager } ?: return
uiManager.addUIBlock {
resolveOnCurrentThread { block() }
}
}

protected val RejectPromiseOnExceptionBlock.playerModule: PlayerModule get() = context.playerModule
?: throw IllegalArgumentException("PlayerModule not found")

protected val RejectPromiseOnExceptionBlock.uiManager: UIManagerModule get() = context.uiManagerModule
?: throw IllegalStateException("UIManager not found")

protected val RejectPromiseOnExceptionBlock.sourceModule: SourceModule get() = context.sourceModule
?: throw IllegalStateException("SourceModule not found")

protected val RejectPromiseOnExceptionBlock.offlineModule: OfflineModule get() = context.offlineModule
?: throw IllegalStateException("OfflineModule not found")

protected val RejectPromiseOnExceptionBlock.drmModule: DrmModule get() = context.drmModule
?: throw IllegalStateException("DrmModule not found")

fun RejectPromiseOnExceptionBlock.getPlayer(
nativeId: NativeId,
playerModule: PlayerModule = this.playerModule,
): Player = playerModule.getPlayerOrNull(nativeId) ?: throw IllegalArgumentException("Invalid PlayerId $nativeId")

fun RejectPromiseOnExceptionBlock.getSource(
nativeId: NativeId,
sourceModule: SourceModule = this.sourceModule,
): Source = sourceModule.getSourceOrNull(nativeId) ?: throw IllegalArgumentException("Invalid SourceId $nativeId")
}

/** Run [block], returning it's return value. If [block] throws, [Promise.reject] [this] and return null. */
inline fun <T, R> TPromise<T>.runAndRejectOnException(block: RejectPromiseOnExceptionBlock.() -> R): R? = try {
RejectPromiseOnExceptionBlock.block()
} catch (e: Exception) {
reject(e)
null
}

/**
* [TPromise.resolve] [this] with [block] return value.
* If [block] throws, [Promise.reject] [this] with the [Throwable].
*/
inline fun <T> TPromise<T>.resolveOnCurrentThread(
crossinline block: RejectPromiseOnExceptionBlock.() -> T,
): Unit = runAndRejectOnException { this@resolveOnCurrentThread.resolve(block()) } ?: Unit

/** Receiver of code that can safely throw when resolving a [Promise]. */
object RejectPromiseOnExceptionBlock

/** Compile time wrapper for Promises to type check the resolved type [T]. */
@JvmInline
value class TPromise<T>(val promise: Promise) {
// Promise only support built-in types. Functions that return [Unit] must resolve to `null`.
fun resolve(value: T): Unit = promise.resolve(value.takeUnless { it is Unit })
fun reject(throwable: Throwable) {
Log.e(MODULE_NAME, "Failed to execute Bitmovin method", throwable)
promise.reject(throwable)
}
}

inline val Promise.int get() = TPromise<Int>(this)
inline val Promise.unit get() = TPromise<Unit>(this)
inline val Promise.string get() = TPromise<String>(this)
inline val Promise.double get() = TPromise<Double>(this)
inline val Promise.float get() = TPromise<Float>(this)
inline val Promise.bool get() = TPromise<Boolean>(this)
inline val Promise.map get() = TPromise<ReadableMap>(this)
inline val Promise.array get() = TPromise<ReadableArray>(this)
inline val <T> TPromise<T>.nullable get() = TPromise<T?>(promise)
Original file line number Diff line number Diff line change
@@ -1,70 +1,54 @@
package com.bitmovin.player.reactnative

import com.bitmovin.player.casting.BitmovinCastManager
import com.bitmovin.player.reactnative.converter.JsonConverter
import com.bitmovin.player.reactnative.converter.toCastOptions
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.module.annotations.ReactModule
import com.facebook.react.uimanager.UIManagerModule

private const val MODULE_NAME = "BitmovinCastManagerModule"

@ReactModule(name = MODULE_NAME)
class BitmovinCastManagerModule(
private val context: ReactApplicationContext,
) : ReactContextBaseJavaModule(context) {
class BitmovinCastManagerModule(context: ReactApplicationContext) : BitmovinBaseModule(context) {
override fun getName() = MODULE_NAME

/**
* Returns whether the [BitmovinCastManager] is initialized.
*/
@ReactMethod
fun isInitialized(promise: Promise) = uiManager?.addUIBlock {
promise.resolve(BitmovinCastManager.isInitialized())
fun isInitialized(promise: Promise) = promise.unit.resolveOnUiThread {
BitmovinCastManager.isInitialized()
}

/**
* Initializes the [BitmovinCastManager] with the given options.
*/
@ReactMethod
fun initializeCastManager(options: ReadableMap?, promise: Promise) {
val castOptions = JsonConverter.toCastOptions(options)
uiManager?.addUIBlock {
BitmovinCastManager.initialize(
castOptions?.applicationId,
castOptions?.messageNamespace,
)
promise.resolve(null)
}
fun initializeCastManager(options: ReadableMap?, promise: Promise) = promise.unit.resolveOnUiThread {
val castOptions = options?.toCastOptions()
BitmovinCastManager.initialize(
castOptions?.applicationId,
castOptions?.messageNamespace,
)
}

/**
* Sends a message to the receiver.
*/
@ReactMethod
fun sendMessage(message: String, messageNamespace: String?, promise: Promise) {
uiManager?.addUIBlock {
BitmovinCastManager.getInstance().sendMessage(message, messageNamespace)
promise.resolve(null)
}
fun sendMessage(message: String, messageNamespace: String?, promise: Promise) = promise.unit.resolveOnUiThread {
BitmovinCastManager.getInstance().sendMessage(message, messageNamespace)
}

/**
* Updates the context of the [BitmovinCastManager] to the current activity.
*/
@ReactMethod
fun updateContext(promise: Promise) {
uiManager?.addUIBlock {
BitmovinCastManager.getInstance().updateContext(currentActivity)
promise.resolve(null)
}
fun updateContext(promise: Promise) = promise.unit.resolveOnUiThread {
BitmovinCastManager.getInstance().updateContext(currentActivity)
}

private val uiManager: UIManagerModule?
get() = context.getNativeModule(UIManagerModule::class.java)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,66 +2,50 @@ package com.bitmovin.player.reactnative

import com.bitmovin.player.api.buffer.BufferLevel
import com.bitmovin.player.api.media.MediaType
import com.bitmovin.player.reactnative.converter.JsonConverter
import com.bitmovin.player.reactnative.converter.toBufferType
import com.bitmovin.player.reactnative.converter.toJson
import com.facebook.react.bridge.*
import com.facebook.react.module.annotations.ReactModule
import com.facebook.react.uimanager.UIManagerModule

private const val MODULE_NAME = "BufferModule"
private const val INVALID_BUFFER_TYPE = "Invalid buffer type"

@ReactModule(name = MODULE_NAME)
class BufferModule(private val context: ReactApplicationContext) : ReactContextBaseJavaModule(context) {
class BufferModule(context: ReactApplicationContext) : BitmovinBaseModule(context) {
override fun getName() = MODULE_NAME

/**
* Gets the [BufferLevel] from the Player
* @param nativeId Target player id.
* @param type The [type of buffer][JsonConverter.toBufferType] to return the level for.
* @param type The [type of buffer][toBufferType] to return the level for.
* @param promise JS promise object.
*/
@ReactMethod
fun getLevel(nativeId: NativeId, type: String, promise: Promise) {
uiManager()?.addUIBlock { _ ->
val player = playerModule()?.getPlayer(nativeId) ?: return@addUIBlock
val bufferType = JsonConverter.toBufferType(type)
if (bufferType == null) {
promise.reject("Error: ", "Invalid buffer type")
return@addUIBlock
}
val bufferLevels = RNBufferLevels(
player.buffer.getLevel(bufferType, MediaType.Audio),
player.buffer.getLevel(bufferType, MediaType.Video),
)
JsonConverter.fromRNBufferLevels(bufferLevels).let {
promise.resolve(it)
}
promise.map.resolveOnUiThread {
val player = getPlayer(nativeId)
val bufferType = type.toBufferTypeOrThrow()
RNBufferLevels(
audio = player.buffer.getLevel(bufferType, MediaType.Audio),
video = player.buffer.getLevel(bufferType, MediaType.Video),
).toJson()
}
}

/**
* Sets the target buffer level for the chosen buffer type across all media types.
* @param nativeId Target player id.
* @param type The [type of buffer][JsonConverter.toBufferType] to set the target level for.
* @param type The [type of buffer][toBufferType] to set the target level for.
* @param value The value to set.
*/
@ReactMethod
fun setTargetLevel(nativeId: NativeId, type: String, value: Double) {
uiManager()?.addUIBlock { _ ->
val player = playerModule()?.getPlayer(nativeId) ?: return@addUIBlock
val bufferType = JsonConverter.toBufferType(type) ?: return@addUIBlock
player.buffer.setTargetLevel(bufferType, value)
fun setTargetLevel(nativeId: NativeId, type: String, value: Double, promise: Promise) {
promise.unit.resolveOnUiThread {
getPlayer(nativeId).buffer.setTargetLevel(type.toBufferTypeOrThrow(), value)
}
}

/**
* Helper function that gets the instantiated `UIManagerModule` from modules registry.
*/
private fun uiManager(): UIManagerModule? = context.getNativeModule(UIManagerModule::class.java)

/**
* Helper function that gets the instantiated `PlayerModule` from modules registry.
*/
private fun playerModule(): PlayerModule? = context.getNativeModule(PlayerModule::class.java)
private fun String.toBufferTypeOrThrow() = toBufferType() ?: throw IllegalArgumentException(INVALID_BUFFER_TYPE)
}

/**
Expand Down
Loading

0 comments on commit d833a12

Please sign in to comment.