Skip to content

Commit

Permalink
Merge pull request #298 from bitmovin/bufferapi-namespace
Browse files Browse the repository at this point in the history
Implement BufferAPI via `Player.buffer`
  • Loading branch information
123mpozzi authored Oct 25, 2023
2 parents c965354 + 6f38a5b commit 8fce4a8
Show file tree
Hide file tree
Showing 11 changed files with 409 additions and 6 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### 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

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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
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.facebook.react.bridge.*
import com.facebook.react.module.annotations.ReactModule
import com.facebook.react.uimanager.UIManagerModule

private const val MODULE_NAME = "BufferModule"

@ReactModule(name = MODULE_NAME)
class BufferModule(private val context: ReactApplicationContext) : ReactContextBaseJavaModule(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 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)
}
}
}

/**
* 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 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)
}
}

/**
* 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)
}

/**
* 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)
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class RNPlayerViewPackage : ReactPackage {
FullscreenHandlerModule(reactContext),
CustomMessageHandlerModule(reactContext),
BitmovinCastManagerModule(reactContext),
BufferModule(reactContext),
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ import com.bitmovin.player.api.advertising.AdSource
import com.bitmovin.player.api.advertising.AdSourceType
import com.bitmovin.player.api.advertising.AdvertisingConfig
import com.bitmovin.player.api.buffer.BufferConfig
import com.bitmovin.player.api.buffer.BufferLevel
import com.bitmovin.player.api.buffer.BufferMediaTypeConfig
import com.bitmovin.player.api.buffer.BufferType
import com.bitmovin.player.api.casting.RemoteControlConfig
import com.bitmovin.player.api.drm.WidevineConfig
import com.bitmovin.player.api.event.PlayerEvent
Expand All @@ -28,6 +30,7 @@ import com.bitmovin.player.api.event.data.CastPayload
import com.bitmovin.player.api.event.data.SeekPosition
import com.bitmovin.player.api.live.LiveConfig
import com.bitmovin.player.api.media.AdaptationConfig
import com.bitmovin.player.api.media.MediaType
import com.bitmovin.player.api.media.audio.AudioTrack
import com.bitmovin.player.api.media.subtitle.SubtitleTrack
import com.bitmovin.player.api.media.thumbnail.Thumbnail
Expand All @@ -45,6 +48,7 @@ import com.bitmovin.player.api.ui.ScalingMode
import com.bitmovin.player.api.ui.StyleConfig
import com.bitmovin.player.api.ui.UiConfig
import com.bitmovin.player.reactnative.BitmovinCastManagerOptions
import com.bitmovin.player.reactnative.RNBufferLevels
import com.bitmovin.player.reactnative.RNPlayerViewConfigWrapper
import com.bitmovin.player.reactnative.extensions.getBooleanOrNull
import com.bitmovin.player.reactnative.extensions.getName
Expand All @@ -58,10 +62,7 @@ import com.bitmovin.player.reactnative.extensions.toList
import com.bitmovin.player.reactnative.extensions.toReadableArray
import com.bitmovin.player.reactnative.extensions.toReadableMap
import com.bitmovin.player.reactnative.ui.RNPictureInPictureHandler
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.bridge.WritableMap
import com.facebook.react.bridge.*
import java.util.UUID

/**
Expand Down Expand Up @@ -1192,6 +1193,74 @@ class JsonConverter {
}
return liveConfig
}

/**
* Converts any [MediaType] value into its json representation.
* @param mediaType [MediaType] value.
* @return The produced JS string.
*/
@JvmStatic
fun fromMediaType(mediaType: MediaType): String = when (mediaType) {
MediaType.Audio -> "audio"
MediaType.Video -> "video"
}

/**
* Converts any [BufferType] value into its json representation.
* @param bufferType [BufferType] value.
* @return The produced JS string.
*/
@JvmStatic
fun fromBufferType(bufferType: BufferType): String = when (bufferType) {
BufferType.ForwardDuration -> "forwardDuration"
BufferType.BackwardDuration -> "backwardDuration"
}

@JvmStatic
fun fromBufferLevel(bufferLevel: BufferLevel): WritableMap =
Arguments.createMap().apply {
putDouble("level", bufferLevel.level)
putDouble("targetLevel", bufferLevel.targetLevel)
putString(
"media",
fromMediaType(bufferLevel.media),
)
putString(
"type",
fromBufferType(bufferLevel.type),
)
}

@JvmStatic
fun fromRNBufferLevels(bufferLevels: RNBufferLevels): WritableMap =
Arguments.createMap().apply {
putMap("audio", fromBufferLevel(bufferLevels.audio))
putMap("video", fromBufferLevel(bufferLevels.video))
}

/**
* Maps a JS string into the corresponding [BufferType] value.
* @param json JS string representing the [BufferType].
* @return The [BufferType] corresponding to [json], or `null` if the conversion fails.
*/
@JvmStatic
fun toBufferType(json: String?): BufferType? = when (json) {
"forwardDuration" -> BufferType.ForwardDuration
"backwardDuration" -> BufferType.BackwardDuration
else -> null
}

/**
* Maps a JS string into the corresponding [MediaType] value.
* @param json JS string representing the [MediaType].
* @return The [MediaType] corresponding to [json], or `null` if the conversion fails.
*/
@JvmStatic
fun toMediaType(json: String?): MediaType? = when (json) {
"audio" -> MediaType.Audio
"video" -> MediaType.Video
else -> null
}
}
}

Expand Down
8 changes: 8 additions & 0 deletions ios/BufferModule.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#import <React/RCTBridgeModule.h>

@interface RCT_EXTERN_REMAP_MODULE(BufferModule, BufferModule, NSObject)

RCT_EXTERN_METHOD(getLevel:(NSString *)nativeId type:(nonnull NSString *)type resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
RCT_EXTERN_METHOD(setTargetLevel:(NSString *)nativeId type:(nonnull NSString *)type value:(nonnull NSNumber *)value)

@end
78 changes: 78 additions & 0 deletions ios/BufferModule.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import BitmovinPlayer

@objc(BufferModule)
public class BufferModule: NSObject, RCTBridgeModule {
// swiftlint:disable:next implicitly_unwrapped_optional
@objc public var bridge: RCTBridge!

/// PlayerModule instance fetched from the bridge's registry
@objc var playerModule: PlayerModule? {
bridge.module(for: PlayerModule.self) as? PlayerModule
}

// swiftlint:disable:next implicitly_unwrapped_optional
public static func moduleName() -> String! {
"BufferModule"
}

/// Module requires main thread initialization.
public static func requiresMainQueueSetup() -> Bool {
true
}

// swiftlint:disable:next implicitly_unwrapped_optional
public var methodQueue: DispatchQueue! {
bridge.uiManager.methodQueue
}

/**
- Gets the `BufferLevel` from the Player
- Parameter nativeId: Native Id of the the player instance.
- Parameter type: The type of buffer to return the level for.
- Parameter resolver: JS promise resolver.
- Parameter rejecter: JS promise rejecter.
*/
@objc(getLevel:type:resolver:rejecter:)
func getLevel(
_ playerId: NativeId,
type: String,
resolver resolve: @escaping RCTPromiseResolveBlock,
rejecter reject: @escaping RCTPromiseRejectBlock
) {
bridge.uiManager.addUIBlock { [weak self] _, _ in
guard let bufferApi = self?.playerModule?.retrieve(playerId)?.buffer else {
reject("[BufferModule]", "Could not find player with ID (\(playerId))", nil)
return
}
guard let bufferType = RCTConvert.bufferType(type) else {
reject("[BufferModule]", "Invalid buffer type: (\(type))", nil)
return
}
let level = bufferApi.getLevel(bufferType)
let bufferLevels = RNBufferLevels(audio: level, video: level)
resolve(RCTConvert.toJson(bufferLevels: bufferLevels))
}
}

/**
* Sets the target level in seconds for the forward buffer.
- Parameter nativeId: Target player id.
- Parameter type: The type of the buffer to set the target level for.
- Parameter value: The value to set.
*/
@objc(setTargetLevel:type:value:)
func setTargetLevel(_ playerId: NativeId, type: String, value: NSNumber) {
let targetLevel = value.doubleValue
bridge.uiManager.addUIBlock { [weak self] _, _ in
let bufferType = RCTConvert.bufferType(type)
guard
bufferType == .forwardDuration,
let bufferApi = self?.playerModule?.retrieve(playerId)?.buffer
else {
return
}

bufferApi.setTargetLevel(targetLevel)
}
}
}
68 changes: 68 additions & 0 deletions ios/RCTConvert+BitmovinPlayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1170,6 +1170,63 @@ extension RCTConvert {
playbackSpeedSelectionEnabled: json["playbackSpeedSelectionEnabled"] as? Bool ?? true
)
}

/**
* Maps a JS string into the corresponding `BufferType` value.
* - Parameter json: JS string representing the `BufferType`.
* - Returns: The `BufferType` corresponding to `json`, or `nil` if the conversion fails.
*/
static func bufferType(_ json: String) -> BufferType? {
switch json {
case "forwardDuration":
return .forwardDuration
case "backwardDuration":
return .backwardDuration
default:
return nil
}
}

/**
* Converts any `BufferType` value into its json representation.
* - Parameter bufferType: `BufferType` value.
* - Returns: The produced JS string.
*/
static func toJson(bufferType: BufferType) -> String {
switch bufferType {
case .forwardDuration:
return "forwardDuration"
case .backwardDuration:
return "backwardDuration"
}
}

/**
Utility method to get a json dictionary value from a `BufferLevel` object.
- Parameter bufferLevel: The `BufferLevel` to convert to json format.
- Parameter mediaType: The `MediaType` value to pass through.
- Returns: The generated json dictionary.
*/
static func toJson(bufferLevel: BufferLevel, mediaType: String) -> [String: Any] {
[
"level": bufferLevel.level,
"targetLevel": bufferLevel.targetLevel,
"media": mediaType,
"type": toJson(bufferType: bufferLevel.type)
]
}

/**
Utility method to get a json dictionary value from a `BufferModule.RNBufferLevels` object.
- Parameter bufferLevels: The `BufferModule.RNBufferLevels` to convert to json format.
- Returns: The generated json dictionary.
*/
static func toJson(bufferLevels: RNBufferLevels) -> [String: Any] {
[
"audio": toJson(bufferLevel: bufferLevels.audio, mediaType: "audio"),
"video": toJson(bufferLevel: bufferLevels.video, mediaType: "video"),
]
}
}
/**
* React native specific PlayerViewConfig.
Expand Down Expand Up @@ -1203,3 +1260,14 @@ internal struct RNPlayerViewConfig {
internal struct RNUiConfig {
let playbackSpeedSelectionEnabled: Bool
}

/**
* 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.
* - Parameter audio: `BufferLevel` for `MediaType.Audio`.
* - Parameter video: `BufferLevel` for `MediaType.Video`.
*/
internal struct RNBufferLevels {
let audio: BufferLevel
let video: BufferLevel
}
Loading

0 comments on commit 8fce4a8

Please sign in to comment.