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

Add basic cast support #64

Merged
merged 31 commits into from
Oct 25, 2023
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
d09cc61
Add Player's cast methods
krocard Oct 5, 2023
b836168
Add RemoteControlConfig
krocard Oct 6, 2023
eb5d8fe
Add casting manager support
krocard Oct 9, 2023
88b89e8
Fix casting support
krocard Oct 9, 2023
b1fd91e
Fix formatting
krocard Oct 9, 2023
c521dd2
Fix formatting
krocard Oct 9, 2023
575af8c
Ignore rethrow lint error
krocard Oct 9, 2023
ff0b839
Display error instead of crash
krocard Oct 10, 2023
aece06d
Fix formating
krocard Oct 10, 2023
bce998d
Fix Kotlin formating
krocard Oct 10, 2023
850de0d
Improve comment doc
krocard Oct 10, 2023
d3f9c80
Fix documentation
krocard Oct 24, 2023
a9bf90b
Fix documentation formating
krocard Oct 24, 2023
8e145ca
Add generated files
krocard Oct 24, 2023
8c5e905
Add comment final dot
krocard Oct 24, 2023
641b564
Merge remote-tracking branch 'origin/PFL-69/fix-casting-support' into…
krocard Oct 24, 2023
05965c5
Remove update context from Dart API
krocard Oct 24, 2023
bc3edab
Rename cast to casting
krocard Oct 24, 2023
f07e36b
Replace code quotes by references
krocard Oct 24, 2023
4af2290
Move casting_manager.dart to casting/bitmovin_cast_manager.dart
krocard Oct 24, 2023
69c6b2a
Remove unneeded async suffix
krocard Oct 24, 2023
376170f
Match file name to class name
krocard Oct 24, 2023
e008c24
Remove incorrect doc
krocard Oct 24, 2023
d508a82
add generated files
krocard Oct 24, 2023
b1eaacb
Fix lint warnings
krocard Oct 24, 2023
11d78b9
Rename BitmovinCastManagerSendMessage to CustomCastMessage
krocard Oct 24, 2023
6800513
Fix import after rename
krocard Oct 24, 2023
6735a04
Disable casting in non casting examples
krocard Oct 24, 2023
d011976
Fix example naming
krocard Oct 24, 2023
f1cfae7
Rename route
krocard Oct 25, 2023
f75722e
remove unused import
krocard 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
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ allprojects {
}

dependencies {
implementation 'com.bitmovin.player:player:3.45.0+jason'
api 'com.bitmovin.player:player:3.45.0+jason'
implementation 'com.fasterxml.jackson.module:jackson-module-kotlin:2.14.1'
implementation 'androidx.concurrent:concurrent-futures-ktx:1.1.0'
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ class FlutterPlayer(
Methods.GET_SUBTITLE -> subtitle?.let { JSubtitleTrack(it).toJsonString() } ?: Unit
Methods.SET_SUBTITLE -> setSubtitle(arg.asOptionalString)
Methods.REMOVE_SUBTITLE -> removeSubtitle(arg.asString)
Methods.IS_CAST_AVAILABLE -> isCastAvailable
Methods.IS_CASTING -> isCasting
Methods.CAST_VIDEO -> castVideo()
Methods.CAST_STOP -> castStop()
else -> throw NotImplementedError()
}

Expand Down
13 changes: 12 additions & 1 deletion android/src/main/kotlin/com/bitmovin/player/flutter/Methods.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package com.bitmovin.player.flutter

class Methods {
companion object {
// Player related methods
// Static methods
const val CREATE_PLAYER = "createPlayer"

// Player related methods
const val LOAD_WITH_SOURCE_CONFIG = "loadWithSourceConfig"
const val LOAD_WITH_SOURCE = "loadWithSource"
const val PLAY = "play"
Expand All @@ -24,6 +26,10 @@ class Methods {
const val SET_SUBTITLE = "setSubtitle"
const val GET_SUBTITLE = "getSubtitle"
const val REMOVE_SUBTITLE = "removeSubtitle"
const val IS_CAST_AVAILABLE = "isCastAvailable"
const val IS_CASTING = "isCasting"
const val CAST_VIDEO = "castVideo"
const val CAST_STOP = "castSTop"
krocard marked this conversation as resolved.
Show resolved Hide resolved

// Player view related methods
const val DESTROY_PLAYER_VIEW = "destroyPlayerView"
Expand All @@ -33,5 +39,10 @@ class Methods {
// Widevine DRM related methods
const val WIDEVINE_PREPARE_MESSAGE = "widevinePrepareMessage"
const val WIDEVINE_PREPARE_LICENSE = "widevinePrepareLicense"

// Cast related methods
const val CAST_MANAGER_INITIALIZE = "castManagerInitialize"
const val CAST_MANAGER_UPDATE_CONTEXT = "castManagerUpdateContext"
const val CAST_MANAGER_SEND_MESSAGE = "castManagerSendMessage"
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package com.bitmovin.player.flutter

import android.app.Activity
import com.bitmovin.player.casting.BitmovinCastManager
import com.bitmovin.player.flutter.json.JBitmovinCastManagerOptions
import com.bitmovin.player.flutter.json.JBitmovinCastManagerSendMessageArgs
import com.bitmovin.player.flutter.json.JCreatePlayerArgs
import com.bitmovin.player.flutter.json.JMethodArgs
import com.bitmovin.player.flutter.json.JsonMethodHandler
Expand All @@ -11,6 +15,7 @@ import java.lang.ref.WeakReference

class PlayerPlugin : FlutterPlugin, ActivityAware {
private var flutterPluginBindingReference = WeakReference<FlutterPlugin.FlutterPluginBinding>(null)
private var activity = WeakReference<Activity?>(null)

private fun register(registrar: FlutterPlugin.FlutterPluginBinding) {
registrar
Expand All @@ -24,6 +29,9 @@ class PlayerPlugin : FlutterPlugin, ActivityAware {
): Any =
when (method) {
Methods.CREATE_PLAYER -> createPlayer(arguments.asCreatePlayerArgs) != null
Methods.CAST_MANAGER_INITIALIZE -> initializeCastManager(arguments.asCastManagerOptions)
Methods.CAST_MANAGER_UPDATE_CONTEXT -> castManagerUpdateContext()
Methods.CAST_MANAGER_SEND_MESSAGE -> sendCastMessage(arguments.asCastSendMessageArgs)
else -> throw NotImplementedError()
}

Expand All @@ -43,6 +51,22 @@ class PlayerPlugin : FlutterPlugin, ActivityAware {
)
}

private fun initializeCastManager(options: JBitmovinCastManagerOptions) {
val wasInitialized = BitmovinCastManager.isInitialized()
BitmovinCastManager.initialize(options.applicationId, options.messageNamespace)
if (!wasInitialized) castManagerUpdateContext()
}

private fun castManagerUpdateContext() {
activity.get()?.let {
BitmovinCastManager.getInstance().updateContext(it)
}
}

private fun sendCastMessage(options: JBitmovinCastManagerSendMessageArgs) {
BitmovinCastManager.getInstance().sendMessage(options.message, options.messageNamespace)
}

override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
flutterPluginBindingReference = WeakReference(flutterPluginBinding)
val handler = JsonMethodHandler(this::onMethodCall)
Expand All @@ -54,14 +78,20 @@ class PlayerPlugin : FlutterPlugin, ActivityAware {
}

override fun onAttachedToActivity(binding: ActivityPluginBinding) {
activity = WeakReference(binding.activity)
flutterPluginBindingReference.get()?.let {
register(it)
}
if (BitmovinCastManager.isInitialized()) {
castManagerUpdateContext()
}
hawk23 marked this conversation as resolved.
Show resolved Hide resolved
}

override fun onDetachedFromActivityForConfigChanges() {}

override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {}

override fun onDetachedFromActivity() {}
override fun onDetachedFromActivity() {
activity = WeakReference(null)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.bitmovin.player.api.media.subtitle.SubtitleTrack
import com.bitmovin.player.api.source.SourceType
import com.bitmovin.player.api.source.TimelineReferencePoint
import com.bitmovin.player.api.ui.ScalingMode
import com.bitmovin.player.casting.BitmovinCastManager
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import io.flutter.plugin.common.MethodCall
import java.security.InvalidParameterException
Expand Down Expand Up @@ -162,6 +163,16 @@ internal class JPlayerConfig(override var map: Map<*, *>) : JStruct {
val licensingConfig by structGetter(::JLicensingConfig)
val liveConfig by structGetter(::JLiveConfig)
val analyticsConfig by structGetter(::JAnalyticsConfig)
val remoteControlConfig by structGetter(::JRemoteControlConfig)
}

internal class JRemoteControlConfig(override var map: Map<*, *>) : JStruct {
val receiverStylesheetUrl by GetString
val customReceiverConfig by GetStringMap
val isCastEnabled by GetBool
val sendManifestRequestsWithCredentials by GetBool
val sendSegmentRequestsWithCredentials by GetBool
val sendDrmLicenseRequestsWithCredentials by GetBool
}

internal class JSubtitleTrack(override var map: Map<*, *>) : JStruct {
Expand Down Expand Up @@ -191,9 +202,23 @@ internal class JSubtitleTrack(override var map: Map<*, *>) : JStruct {
internal value class JMethodArgs(private val call: MethodCall) {
val asCreatePlayerArgs get() = JCreatePlayerArgs(asMap)
val asPlayerMethodArgs get() = JPlayerMethodArg(asMap)
val asCastManagerOptions get() = JBitmovinCastManagerOptions(asMap)
val asCastSendMessageArgs get() = JBitmovinCastManagerSendMessageArgs(asMap)
private val asMap get() = call.arguments as Map<*, *>
}

/** Arguments for [BitmovinCastManager.initialize]*/
krocard marked this conversation as resolved.
Show resolved Hide resolved
internal class JBitmovinCastManagerOptions(override var map: Map<*, *>) : JStruct {
val applicationId by GetString
val messageNamespace by GetString
}

/** Arguments for [BitmovinCastManager.sendMessage]*/
krocard marked this conversation as resolved.
Show resolved Hide resolved
internal class JBitmovinCastManagerSendMessageArgs(override var map: Map<*, *>) : JStruct {
val message by GetString
val messageNamespace by GetString
}

/** Arguments for [Player.create]. */
internal class JCreatePlayerArgs(override var map: Map<*, *>) : JStruct {
val id by GetString.require()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.bitmovin.analytics.api.SourceMetadata
import com.bitmovin.player.api.LicensingConfig
import com.bitmovin.player.api.PlaybackConfig
import com.bitmovin.player.api.PlayerConfig
import com.bitmovin.player.api.casting.RemoteControlConfig
import com.bitmovin.player.api.drm.DrmConfig
import com.bitmovin.player.api.drm.WidevineConfig
import com.bitmovin.player.api.live.LiveConfig
Expand Down Expand Up @@ -71,6 +72,7 @@ internal fun JPlayerConfig.toNative() =
playbackConfig?.let { config.playbackConfig = it.toNative() }
licensingConfig?.let { config.licensingConfig = it.toNative() }
liveConfig?.let { config.liveConfig = it.toNative() }
remoteControlConfig?.let { config.remoteControlConfig = it.toNative() }
}

internal fun JAnalyticsConfig.toNative(): AnalyticsConfig =
Expand Down Expand Up @@ -159,6 +161,18 @@ internal fun JLiveConfig.toNative() =
liveEdgeOffset?.let { config.liveEdgeOffset = it }
}

internal fun JRemoteControlConfig.toNative() =
RemoteControlConfig().also { config ->
receiverStylesheetUrl?.let { config.receiverStylesheetUrl = it }
customReceiverConfig?.let { config.customReceiverConfig = it }
isCastEnabled?.let { config.isCastEnabled = it }
sendManifestRequestsWithCredentials?.let { config.sendManifestRequestsWithCredentials = it }
sendSegmentRequestsWithCredentials?.let { config.sendSegmentRequestsWithCredentials = it }
sendDrmLicenseRequestsWithCredentials?.let {
config.sendDrmLicenseRequestsWithCredentials = it
}
}

internal fun JSubtitleTrack.toNative() =
SubtitleTrack(
url = url,
Expand Down
4 changes: 4 additions & 0 deletions example/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,8 @@ flutter {
dependencies {
implementation "androidx.appcompat:appcompat:1.6.1"
implementation "androidx.multidex:multidex:2.0.1"

// only needed if the casting feature is used
krocard marked this conversation as resolved.
Show resolved Hide resolved
implementation "com.google.android.gms:play-services-cast-framework:21.3.0"
implementation "androidx.mediarouter:mediarouter:1.3.1"
}
24 changes: 23 additions & 1 deletion example/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:theme="@style/Theme.AppCompat.NoActionBar"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
Expand All @@ -35,5 +35,27 @@
<meta-data
android:name="flutterEmbedding"
android:value="2" />


<!-- Cast control activity. Only needed for casting. -->
<activity
android:name="com.bitmovin.player.casting.ExpandedControllerActivity"
android:exported="true"
android:label="@string/app_name"
android:launchMode="singleTask"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.bitmovin.player.flutter.example.MainActivity" />
</activity>

<!-- Meta-data needed for Google cast usage -->
<meta-data
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="com.bitmovin.player.casting.BitmovinCastOptionsProvider" />

</application>
</manifest>
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
package com.bitmovin.player.flutter.example

import io.flutter.embedding.android.FlutterActivity
import android.os.Bundle
import android.util.Log
import com.google.android.gms.cast.framework.CastContext
import io.flutter.embedding.android.FlutterFragmentActivity

class MainActivity : FlutterActivity()
// Bitmovin Cast SDK requires the Activity to be a subclass of `FragmentActivity`.
krocard marked this conversation as resolved.
Show resolved Hide resolved
class MainActivity : FlutterFragmentActivity() {
@Override
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
try {
// Load Google Cast context eagerly in order to ensure that
// the cast state is updated correctly.
CastContext.getSharedInstance(this, Runnable::run)
} catch (e: Exception) {
Log.w("MainActivity", "Could not initialize cast context", e)
}
}
}
2 changes: 2 additions & 0 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:bitmovin_player_example/pages/analytics.dart';
import 'package:bitmovin_player_example/pages/audio_only.dart';
import 'package:bitmovin_player_example/pages/basic_playback.dart';
import 'package:bitmovin_player_example/pages/cast.dart';
import 'package:bitmovin_player_example/pages/custom_html_ui.dart';
import 'package:bitmovin_player_example/pages/drm_playback.dart';
import 'package:bitmovin_player_example/pages/event_subscription.dart';
Expand Down Expand Up @@ -33,6 +34,7 @@ class _MyAppState extends State<MyApp> {
EventSubscription.routeName: (_) => const EventSubscription(),
CustomHtmlUi.routeName: (_) => const CustomHtmlUi(),
FullscreenHandling.routeName: (_) => const FullscreenHandling(),
Cast.routeName: (_) => const Cast(),
hawk23 marked this conversation as resolved.
Show resolved Hide resolved
},
home: const Scaffold(
body: Home(),
Expand Down
Loading