Skip to content

Commit

Permalink
Merge branch 'development' into improve-start-release-workflow
Browse files Browse the repository at this point in the history
  • Loading branch information
rolandkakonyi committed Dec 12, 2023
2 parents 0fbdcbc + e137689 commit 9abd670
Show file tree
Hide file tree
Showing 54 changed files with 2,520 additions and 1,995 deletions.
32 changes: 32 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,37 @@
# Changelog

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

### Fixed

- Android: `onEvent` callback not being called on `PlayerView`
- 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)

### Fixed

- Android: `PlayerView` destroys attached `Player` instance on destroy. `Player` lifecycle must be handled on the creation side

## [0.14.0] (2023-11-14)

### Added

- `LiveConfig.minTimeshiftBufferDepth` to control the minimum buffer depth of a stream needed to enable time shifting
- `Player.buffer` to control buffer preferences and to query the current buffer state
- `DownloadFinishedEvent` to signal when the download of specific content has finished
- `Player.videoQuality`, `Player.availableVideoQualities`, and `VideoDownloadQualityChangedEvent` to query current video qualities and listen to related changes
- `Player.playbackSpeed`, `Player.canPlayAtPlaybackSpeed`, `PlaybackSpeedChangedEvent` to query, control, and listen to changes to the speed of the playback
- Support for `UserInterfaceType.Subtitle` on Android

### Fixed

- Android: Playback doesn't pause when app goes to background
- Android: `PlayerView.onDestroy` not being called when the view is detached from the view hierarchy

## [0.13.0] (2023-10-20)

### Added
Expand Down
11 changes: 5 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,12 @@ 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 currently supported platforms are:
This library requires at least React Native 0.64+ and React 17+ to work properly. The **officially supported** platforms are:

- iOS 14.0+
- tvOS 14.0+
- Android API Level 21+
- Android TV API Level 24+
- Fire TV FireOS 5.0+
- **iOS/iPadOS/tvOS:** 14.0+
- **Android:** 5.0+
- **Android TV:** 7+
- **Fire TV:** Fire OS 6.0+ ([compatible](https://developer.bitmovin.com/playback/docs/supported-platforms-devices-player#support-levels) with Fire OS 5.0)

Please note that browsers and other browser-like environments such as webOS and Tizen are not supported. For more details regarding Bitmovin Player SDK platform and device support, please refer to the [Supported Platforms & Devices](https://developer.bitmovin.com/playback/docs/supported-platforms-devices-player) page of our documentation.

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.46.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 @@ -59,7 +59,7 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion"
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.48.0+jason'
implementation 'com.bitmovin.player:player:3.51.0+jason'
//noinspection GradleDynamicVersion
implementation 'com.facebook.react:react-native:+' // From node_modules
}
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
@@ -0,0 +1,55 @@
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.toBufferType
import com.bitmovin.player.reactnative.converter.toJson
import com.facebook.react.bridge.*
import com.facebook.react.module.annotations.ReactModule

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

@ReactModule(name = MODULE_NAME)
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][toBufferType] to return the level for.
* @param promise JS promise object.
*/
@ReactMethod
fun getLevel(nativeId: NativeId, type: String, promise: Promise) {
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][toBufferType] to set the target level for.
* @param value The value to set.
*/
@ReactMethod
fun setTargetLevel(nativeId: NativeId, type: String, value: Double, promise: Promise) {
promise.unit.resolveOnUiThread {
getPlayer(nativeId).buffer.setTargetLevel(type.toBufferTypeOrThrow(), value)
}
}

private fun String.toBufferTypeOrThrow() = toBufferType() ?: throw IllegalArgumentException(INVALID_BUFFER_TYPE)
}

/**
* Representation of the React Native API `BufferLevels` object.
* This is necessary as we need a unified representation of the different APIs from both Android and iOS.
*/
data class RNBufferLevels(val audio: BufferLevel, val video: BufferLevel)
Loading

0 comments on commit 9abd670

Please sign in to comment.