Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement BufferAPI via Player.buffer #298

Merged
merged 54 commits into from
Oct 25, 2023
Merged
Show file tree
Hide file tree
Changes from 48 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
033b227
feat(docs): update docs to use jsdoc references
123mpozzi Oct 17, 2023
90e0e4c
feat(bufferapi): implement bufferApi interface and types
123mpozzi Oct 17, 2023
1204768
feat(bufferapi): fix bufferapi imports
123mpozzi Oct 17, 2023
c3dbb47
feat(docs): update and fix docs formatting and referencing
123mpozzi Oct 17, 2023
7a961e9
feat(bufferapi): add buffer module for android
123mpozzi Oct 18, 2023
2c73d75
feat(bufferapi): add buffer api
123mpozzi Oct 18, 2023
0981e22
feat(formatting): apply ktlint rules
123mpozzi Oct 18, 2023
f65ecc7
feat(docs): fix typedocs references
123mpozzi Oct 18, 2023
acd6b8e
docs(bufferapi): add missing docs and improve formatting
123mpozzi Oct 18, 2023
68182ed
feat(bufferapi): add buffer module for ios
123mpozzi Oct 19, 2023
0c1660d
docs(bufferapi): fix docs in bufferApi.ts
123mpozzi Oct 19, 2023
18f5b05
feat(bufferapi): codestyle improvements
123mpozzi Oct 19, 2023
cde574d
feat(docs): remove docs changes
123mpozzi Oct 19, 2023
fb5b4b4
feat(docs): remove docs changes
123mpozzi Oct 19, 2023
ebcacca
feat(docs): remove docs changes
123mpozzi Oct 19, 2023
2ea57b4
docs(bufferapi): fix documentation
123mpozzi Oct 19, 2023
5e50a0e
feat(merge): merge commit 'c68292bf05b4f30c4a6146ba4db4401dbaec6827' …
123mpozzi Oct 19, 2023
5c7f68e
feat(bufferapi): fix swiftlint warnings
123mpozzi Oct 19, 2023
a33b50c
feat(bufferapi): add CHANGELOG entry
123mpozzi Oct 19, 2023
5cae0fe
feat(bufferapi): fix buffer module location
123mpozzi Oct 19, 2023
b0ca817
feat(bufferapi): fix `setTargetLevel` on iOS
123mpozzi Oct 19, 2023
d17254f
feat(bufferapi): merge commit 'cc32c2ee92b8f2b882118574dd3dcfdebe3f2c…
123mpozzi Oct 19, 2023
f578105
feat(bufferapi): move RN only types to the bottom of the Kotlin file
123mpozzi Oct 20, 2023
fb87736
feat(bufferapi): move RN-only types on the bottom of `RCTConvert+Bitm…
123mpozzi Oct 20, 2023
6e920fe
feat(bufferapi): fix docs
123mpozzi Oct 20, 2023
46d5e93
feat(bufferapi): nit docs
123mpozzi Oct 20, 2023
64ff2b6
feat(bufferapi): update docs on specific iOS behaviour
123mpozzi Oct 20, 2023
f270ddd
feat(bufferapi): add tvOS in BufferMediaTypeConfig docs
123mpozzi Oct 20, 2023
b94cbb6
feat(bufferapi): update docs with tvOS too
123mpozzi Oct 20, 2023
5781d6f
feat(bufferapi): merge commit 'c432d32c333281765d30db8eeced884b011c22…
123mpozzi Oct 20, 2023
5c0d1c3
feat(bufferapi): move RNBufferLevels to correct place
123mpozzi Oct 20, 2023
ed8ab2f
feat(bufferapi): fix `RNBufferLevels` imports
123mpozzi Oct 20, 2023
eeda724
feat(bufferapi): make getter one-line
123mpozzi Oct 20, 2023
cabdb30
feat(bufferapi): remove docs from module `getName` as it is explicit …
123mpozzi Oct 20, 2023
e24d3f7
feat(bufferapi): fix docs
123mpozzi Oct 20, 2023
0800385
feat(bufferapi): make getter one-line
123mpozzi Oct 20, 2023
b54a7ac
feat(bufferapi): remove docs from `RNBufferLevels`
123mpozzi Oct 20, 2023
de6762a
feat(bufferapi): simplify entry on CHANGELOG
123mpozzi Oct 20, 2023
6e7ca13
feat(bufferapi): map bufferType to Strings instead of Int
123mpozzi Oct 23, 2023
5b8f449
feat(bufferapi): update bufferApi with bufferMedia mapping using Str…
123mpozzi Oct 23, 2023
a0b00ce
feat(bufferapi): remove optionals when not needed on swift
123mpozzi Oct 23, 2023
24f6205
feat(bufferapi): add `toMediaType` on Android
123mpozzi Oct 23, 2023
245d413
feat(bufferapi): remove optionals when not needed on Android
123mpozzi Oct 23, 2023
f868d9d
feat(bufferapi): fix swiftlint warnings
123mpozzi Oct 23, 2023
1aa5620
feat(bufferapi): fix doc return value
123mpozzi Oct 23, 2023
eb22eed
feat(bufferapi): fix unnecessary check on safe call on Android
123mpozzi Oct 23, 2023
a9af0b6
feat(bufferapi): add new docs to BufferLevels
123mpozzi Oct 23, 2023
96d27e2
feat(bufferapi): update docs of `BufferLevels`
123mpozzi Oct 23, 2023
8f4340e
feat(bufferapi): remove nullable in `fromMediaType`
123mpozzi Oct 24, 2023
d2ff9ea
feat(bufferapi): make `toMediaType` and `toBufferType` nullable
123mpozzi Oct 24, 2023
0ada2f8
feat(bufferapi): fix docs
123mpozzi Oct 24, 2023
18ecdcb
feat(bufferapi): make `toBufferType` nullable in Swift
123mpozzi Oct 24, 2023
539e8e8
Merge commit 'c965354c328fbed58b55dae8702fd6c1955a4da9' into bufferap…
123mpozzi Oct 25, 2023
6f38a5b
feat(bufferapi): remove nullable in `fromBufferType`
123mpozzi Oct 25, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## [Unreleased]

### Added

- `Player.buffer` to control buffer preferences and to query the current buffer state

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

### Added
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
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)
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)
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,14 +19,17 @@ 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
import com.bitmovin.player.api.event.SourceEvent
import com.bitmovin.player.api.event.data.CastPayload
import com.bitmovin.player.api.event.data.SeekPosition
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 @@ -44,6 +47,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 @@ -57,10 +61,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 @@ -1169,6 +1170,78 @@ class JsonConverter {
playerViewConfig = toPlayerViewConfig(json),
pictureInPictureConfig = toPictureInPictureConfig(json.getMap("pictureInPictureConfig")),
)

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

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

@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.
* If the string is not recognized, it returns [BufferType.ForwardDuration].
* @param json JS string representing the [BufferType].
* @return The [BufferType] corresponding to [json].
*/
@JvmStatic
fun toBufferType(json: String?): BufferType = when (json) {
"forwardDuration" -> BufferType.ForwardDuration
"backwardDuration" -> BufferType.BackwardDuration
else -> BufferType.ForwardDuration
}

/**
* Maps a JS string into the corresponding [MediaType] value.
* If the string is not recognized, it returns [MediaType.Audio].
* @param json JS string representing the [MediaType].
* @return The [MediaType] corresponding to [json].
*/
@JvmStatic
fun toMediaType(json: String?): MediaType = when (json) {
"audio" -> MediaType.Audio
"video" -> MediaType.Video
else -> MediaType.Audio
}
}
}

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
75 changes: 75 additions & 0 deletions ios/BufferModule.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
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
}
let bufferType = RCTConvert.bufferType(type)
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)
}
}
}
71 changes: 71 additions & 0 deletions ios/RCTConvert+BitmovinPlayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1151,6 +1151,66 @@ extension RCTConvert {
playbackSpeedSelectionEnabled: json["playbackSpeedSelectionEnabled"] as? Bool ?? true
)
}

/**
* Maps a JS string into the corresponding `BufferType` value.
* If the string is not recognized, it returns `BufferType.forwardDuration`.
* - Parameter json: JS string representing the `BufferType`.
* - Returns: The `BufferType` corresponding to `json`
*/
static func bufferType(_ json: String) -> BufferType {
switch json {
case "forwardDuration":
return .forwardDuration
case "backwardDuration":
return .backwardDuration
default:
return .forwardDuration
}
}

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

/**
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 @@ -1184,3 +1244,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