diff --git a/android/src/main/java/com/bitmovin/player/reactnative/BitmovinBaseModule.kt b/android/src/main/java/com/bitmovin/player/reactnative/BitmovinBaseModule.kt index a5157b73..624d7df4 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/BitmovinBaseModule.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/BitmovinBaseModule.kt @@ -8,6 +8,7 @@ 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.bitmovin.player.reactnative.offline.OfflineContentManagerBridge import com.facebook.react.bridge.* import com.facebook.react.uimanager.UIManagerModule @@ -30,69 +31,79 @@ abstract class BitmovinBaseModule( * 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 TPromise.resolveOnUiThread( - crossinline block: RejectPromiseOnExceptionBlock.() -> R, - ) { + protected inline fun TPromise.resolveOnUiThread(crossinline block: () -> R) { val uiManager = runAndRejectOnException { uiManager } ?: return uiManager.addUIBlock { resolveOnCurrentThread { block() } } } - protected val RejectPromiseOnExceptionBlock.playerModule: PlayerModule get() = context.playerModule + protected val playerModule: PlayerModule get() = context.playerModule ?: throw IllegalArgumentException("PlayerModule not found") - protected val RejectPromiseOnExceptionBlock.uiManager: UIManagerModule get() = context.uiManagerModule + protected val uiManager: UIManagerModule get() = context.uiManagerModule ?: throw IllegalStateException("UIManager not found") - protected val RejectPromiseOnExceptionBlock.sourceModule: SourceModule get() = context.sourceModule + protected val sourceModule: SourceModule get() = context.sourceModule ?: throw IllegalStateException("SourceModule not found") - protected val RejectPromiseOnExceptionBlock.offlineModule: OfflineModule get() = context.offlineModule + protected val offlineModule: OfflineModule get() = context.offlineModule ?: throw IllegalStateException("OfflineModule not found") - protected val RejectPromiseOnExceptionBlock.drmModule: DrmModule get() = context.drmModule + protected val drmModule: DrmModule get() = context.drmModule ?: throw IllegalStateException("DrmModule not found") - fun RejectPromiseOnExceptionBlock.getPlayer( + fun getPlayer( nativeId: NativeId, playerModule: PlayerModule = this.playerModule, ): Player = playerModule.getPlayerOrNull(nativeId) ?: throw IllegalArgumentException("Invalid PlayerId $nativeId") - fun RejectPromiseOnExceptionBlock.getSource( + fun 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 TPromise.runAndRejectOnException(block: RejectPromiseOnExceptionBlock.() -> R): R? = try { - RejectPromiseOnExceptionBlock.block() -} catch (e: Exception) { - reject(e) - null + fun getOfflineContentManagerBridge( + nativeId: NativeId, + offlineModule: OfflineModule = this.offlineModule, + ): OfflineContentManagerBridge = offlineModule.getOfflineContentManagerBridgeOrNull(nativeId) + ?: throw IllegalArgumentException("Invalid offline content manager bridge id: $nativeId") } -/** - * [TPromise.resolve] [this] with [block] return value. - * If [block] throws, [Promise.reject] [this] with the [Throwable]. - */ -inline fun TPromise.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(val promise: Promise) { + /** + * Resolve the promise with [value], see [Promise.resolve]. + * Prefer [resolveOnCurrentThread] to automatically reject promise if an Exception is thrown. + */ // 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 }) + + /** + * Reject the promise due to [throwable], see [Promise.reject]. + * Prefer [resolveOnCurrentThread] or [runAndRejectOnException] instead for automatic rejecting. + */ fun reject(throwable: Throwable) { Log.e(MODULE_NAME, "Failed to execute Bitmovin method", throwable) promise.reject(throwable) } + + /** + * [TPromise.resolve] with [block] return value. + * If [block] throws, [Promise.reject] with the [Throwable]. + */ + inline fun resolveOnCurrentThread( + crossinline block: () -> T, + ): Unit = runAndRejectOnException { resolve(block()) } ?: Unit + + /** Run [block], returning it's return value. If [block] throws, [Promise.reject] and return null. */ + inline fun runAndRejectOnException(block: () -> R): R? = try { + block() + } catch (e: Exception) { + reject(e) + null + } } inline val Promise.int get() = TPromise(this) diff --git a/android/src/main/java/com/bitmovin/player/reactnative/OfflineModule.kt b/android/src/main/java/com/bitmovin/player/reactnative/OfflineModule.kt index 956e46be..d7ec560c 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/OfflineModule.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/OfflineModule.kt @@ -33,11 +33,6 @@ class OfflineModule(context: ReactApplicationContext) : BitmovinBaseModule(conte nativeId: NativeId, ): OfflineContentManagerBridge? = offlineContentManagerBridges[nativeId] - private fun RejectPromiseOnExceptionBlock.getOfflineContentManagerBridge( - nativeId: NativeId, - ): OfflineContentManagerBridge = offlineContentManagerBridges[nativeId] - ?: throw IllegalArgumentException("No offline content manager bridge for id $nativeId") - /** * Callback when a new NativeEventEmitter is created from the Typescript layer. */ @@ -242,7 +237,7 @@ class OfflineModule(context: ReactApplicationContext) : BitmovinBaseModule(conte crossinline block: OfflineContentManagerBridge.() -> T, ) { resolveOnCurrentThread { - getOfflineContentManagerBridge(nativeId).block() + getOfflineContentManagerBridge(nativeId, this@OfflineModule).block() } } } diff --git a/android/src/main/java/com/bitmovin/player/reactnative/PlayerAnalyticsModule.kt b/android/src/main/java/com/bitmovin/player/reactnative/PlayerAnalyticsModule.kt index 5d6de800..0fe6288c 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/PlayerAnalyticsModule.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/PlayerAnalyticsModule.kt @@ -44,7 +44,8 @@ class PlayerAnalyticsModule(context: ReactApplicationContext) : BitmovinBaseModu playerId: NativeId, crossinline block: AnalyticsApi.() -> T, ) = resolveOnUiThread { - val analytics = getPlayer(playerId).analytics ?: throw IllegalStateException("Analytics is disabled") + val analytics = getPlayer(playerId).analytics + ?: throw IllegalStateException("Analytics is disabled for player $playerId") analytics.block() } } diff --git a/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt b/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt index 78522f27..a9aedd8c 100644 --- a/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt +++ b/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt @@ -90,8 +90,8 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex */ @ReactMethod fun loadSource(nativeId: NativeId, sourceNativeId: String, promise: Promise) { - promise.unit.resolveOnUiThread { - getPlayer(nativeId, this@PlayerModule).load(getSource(sourceNativeId)) + promise.unit.resolveOnUiThreadWithPlayer(nativeId) { + load(getSource(sourceNativeId)) } } @@ -108,12 +108,10 @@ class PlayerModule(context: ReactApplicationContext) : BitmovinBaseModule(contex options: ReadableMap?, promise: Promise, ) { - promise.unit.resolveOnUiThread { - offlineModule - .getOfflineContentManagerBridgeOrNull(offlineContentManagerBridgeId) - ?.offlineContentManager - ?.offlineSourceConfig - ?.let { getPlayer(nativeId).load(it) } + promise.unit.resolveOnUiThreadWithPlayer(nativeId) { + val offlineContentManagerBridge = getOfflineContentManagerBridge(offlineContentManagerBridgeId) + val offlineSourceConfig = offlineContentManagerBridge.offlineContentManager.offlineSourceConfig + load(offlineSourceConfig ?: throw IllegalStateException("Offline source has no config")) } }