casting: update SDK to 0.2.1

This commit is contained in:
Marcus Hanestad 2025-08-21 17:20:47 +02:00
commit 5a74b714b8
5 changed files with 41 additions and 26 deletions

View file

@ -233,5 +233,8 @@ dependencies {
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
//Rust casting SDK //Rust casting SDK
implementation "net.java.dev.jna:jna:5.12.0@aar" implementation('org.futo.gitlab.videostreaming.fcast-sdk-jitpack:sender-sdk-minimal:0.2.1') {
// Polycentricandroid includes this
exclude group: 'net.java.dev.jna'
}
} }

View file

@ -108,7 +108,12 @@ class ConnectedCastingDialog(context: Context?) : AlertDialog(context) {
_buttonClose.setOnClickListener { dismiss(); }; _buttonClose.setOnClickListener { dismiss(); };
_buttonDisconnect.setOnClickListener { _buttonDisconnect.setOnClickListener {
if (Settings.instance.casting.experimentalCasting) { if (Settings.instance.casting.experimentalCasting) {
ExpStateCasting.instance.activeDevice?.device?.stopCasting() try {
ExpStateCasting.instance.activeDevice?.device?.stopPlayback()
ExpStateCasting.instance.activeDevice?.device?.disconnect()
} catch (e: Throwable) {
// Ignored
}
} else { } else {
StateCasting.instance.activeDevice?.stopCasting(); StateCasting.instance.activeDevice?.stopCasting();
} }

View file

@ -19,6 +19,7 @@ import java.net.InetAddress
import org.fcast.sender_sdk.CastingDevice as RsCastingDevice; import org.fcast.sender_sdk.CastingDevice as RsCastingDevice;
import org.fcast.sender_sdk.DeviceEventHandler as RsDeviceEventHandler; import org.fcast.sender_sdk.DeviceEventHandler as RsDeviceEventHandler;
import org.fcast.sender_sdk.DeviceConnectionState import org.fcast.sender_sdk.DeviceConnectionState
import org.fcast.sender_sdk.LoadRequest
class CastingDeviceHandle { class CastingDeviceHandle {
class EventHandler : RsDeviceEventHandler { class EventHandler : RsDeviceEventHandler {
@ -64,6 +65,10 @@ class CastingDeviceHandle {
override fun mediaEvent(event: GenericMediaEvent) { override fun mediaEvent(event: GenericMediaEvent) {
// Unreachable // Unreachable
} }
override fun playbackError(message: String) {
Logger.e(TAG, "Playback error: $message")
}
} }
val eventHandler = EventHandler() val eventHandler = EventHandler()
@ -91,17 +96,16 @@ class CastingDeviceHandle {
eventHandler.onConnectionStateChanged.subscribe { newState -> eventHandler.onConnectionStateChanged.subscribe { newState ->
if (newState == DeviceConnectionState.Disconnected) { if (newState == DeviceConnectionState.Disconnected) {
try { try {
Logger.i("CastingDeviceHandle", "Stopping device") Logger.i(TAG, "Stopping device")
device.disconnect() device.disconnect()
} catch (e: Throwable) { } catch (e: Throwable) {
Logger.e("CastingDeviceHandle", "Failed to stop device: $e") Logger.e(TAG, "Failed to stop device: $e")
} }
} }
} }
} }
fun loadVideo( fun loadVideo(
streamType: String,
contentType: String, contentType: String,
contentId: String, contentId: String,
resumePosition: Double, resumePosition: Double,
@ -109,9 +113,9 @@ class CastingDeviceHandle {
speed: Double? speed: Double?
) { ) {
try { try {
device.loadVideo(contentType, contentId, resumePosition, speed) device.load(LoadRequest.Video(contentType, contentId, resumePosition, speed, duration))
} catch (e: Throwable) { } catch (e: Throwable) {
Logger.e("CastingDevice", "Failed to load video: $e") Logger.e(TAG, "Failed to load video: $e")
} }
} }
@ -123,11 +127,15 @@ class CastingDeviceHandle {
speed: Double? speed: Double?
) { ) {
try { try {
device.loadContent(contentType, content, resumePosition, duration, speed) device.load(LoadRequest.Content(contentType, content, resumePosition, duration, speed))
} catch (e: Throwable) { } catch (e: Throwable) {
Logger.e("CastingDevice", "Failed to load content: $e") Logger.e(TAG, "Failed to load content: $e")
} }
} }
companion object {
private val TAG = "ExperimentalCastingDevice"
}
} }
enum class CastConnectionState { enum class CastConnectionState {

View file

@ -7,6 +7,7 @@ import android.os.Looper
import android.util.Log import android.util.Log
import androidx.annotation.OptIn import androidx.annotation.OptIn
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import com.futo.platformplayer.BuildConfig
import com.futo.platformplayer.R import com.futo.platformplayer.R
import com.futo.platformplayer.Settings import com.futo.platformplayer.Settings
import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.UIDialogs
@ -50,6 +51,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.fcast.sender_sdk.ApplicationInfo
import java.net.Inet6Address import java.net.Inet6Address
import java.net.InetAddress import java.net.InetAddress
import java.net.URLDecoder import java.net.URLDecoder
@ -110,7 +112,9 @@ class ExpStateCasting {
} }
init { init {
org.fcast.sender_sdk.initLogger(org.fcast.sender_sdk.LogLevelFilter.DEBUG) if (BuildConfig.DEBUG) {
org.fcast.sender_sdk.initLogger(org.fcast.sender_sdk.LogLevelFilter.DEBUG)
}
} }
fun handleUrl(context: Context, url: String) { fun handleUrl(context: Context, url: String) {
@ -382,7 +386,13 @@ class ExpStateCasting {
} }
try { try {
device.device.connect(device.eventHandler) device.device.connect(
ApplicationInfo(
"grayjay android",
"${BuildConfig.VERSION_NAME}-${BuildConfig.FLAVOR}",
"Grayjay"
), device.eventHandler
)
Logger.i(TAG, "Requested manager to start device") Logger.i(TAG, "Requested manager to start device")
} catch (e: Throwable) { } catch (e: Throwable) {
Logger.w(TAG, "Failed to connect to device."); Logger.w(TAG, "Failed to connect to device.");
@ -415,7 +425,7 @@ class ExpStateCasting {
val rsDeviceInfo = device.device.getDeviceInfo() val rsDeviceInfo = device.device.getDeviceInfo()
val deviceInfo = CastingDeviceInfo( val deviceInfo = CastingDeviceInfo(
name = device.device.name(), name = device.device.name(),
type = when (rsDeviceInfo.type) { type = when (rsDeviceInfo.protocol) {
ProtocolType.CHROMECAST -> com.futo.platformplayer.casting.CastProtocolType.CHROMECAST ProtocolType.CHROMECAST -> com.futo.platformplayer.casting.CastProtocolType.CHROMECAST
ProtocolType.F_CAST -> com.futo.platformplayer.casting.CastProtocolType.FCAST ProtocolType.F_CAST -> com.futo.platformplayer.casting.CastProtocolType.FCAST
}, },
@ -547,7 +557,6 @@ class ExpStateCasting {
val videoUrl = if (proxyStreams) url + videoPath else videoSource.getVideoUrl(); val videoUrl = if (proxyStreams) url + videoPath else videoSource.getVideoUrl();
Logger.i(TAG, "Casting as singular video"); Logger.i(TAG, "Casting as singular video");
ad.loadVideo( ad.loadVideo(
if (video.isLive) "LIVE" else "BUFFERED",
videoSource.container, videoSource.container,
videoUrl, videoUrl,
resumePosition, resumePosition,
@ -559,7 +568,6 @@ class ExpStateCasting {
val audioUrl = if (proxyStreams) url + audioPath else audioSource.getAudioUrl(); val audioUrl = if (proxyStreams) url + audioPath else audioSource.getAudioUrl();
Logger.i(TAG, "Casting as singular audio"); Logger.i(TAG, "Casting as singular audio");
ad.loadVideo( ad.loadVideo(
if (video.isLive) "LIVE" else "BUFFERED",
audioSource.container, audioSource.container,
audioUrl, audioUrl,
resumePosition, resumePosition,
@ -579,7 +587,6 @@ class ExpStateCasting {
} else { } else {
Logger.i(TAG, "Casting as non-proxied HLS"); Logger.i(TAG, "Casting as non-proxied HLS");
ad.loadVideo( ad.loadVideo(
if (video.isLive) "LIVE" else "BUFFERED",
videoSource.container, videoSource.container,
videoSource.url, videoSource.url,
resumePosition, resumePosition,
@ -600,7 +607,6 @@ class ExpStateCasting {
} else { } else {
Logger.i(TAG, "Casting as non-proxied audio HLS"); Logger.i(TAG, "Casting as non-proxied audio HLS");
ad.loadVideo( ad.loadVideo(
if (video.isLive) "LIVE" else "BUFFERED",
audioSource.container, audioSource.container,
audioSource.url, audioSource.url,
resumePosition, resumePosition,
@ -716,7 +722,6 @@ class ExpStateCasting {
Logger.i(TAG, "Casting local video (videoUrl: $videoUrl)."); Logger.i(TAG, "Casting local video (videoUrl: $videoUrl).");
ad.loadVideo( ad.loadVideo(
"BUFFERED",
videoSource.container, videoSource.container,
videoUrl, videoUrl,
resumePosition, resumePosition,
@ -747,7 +752,6 @@ class ExpStateCasting {
Logger.i(TAG, "Casting local audio (audioUrl: $audioUrl)."); Logger.i(TAG, "Casting local audio (audioUrl: $audioUrl).");
ad.loadVideo( ad.loadVideo(
"BUFFERED",
audioSource.container, audioSource.container,
audioUrl, audioUrl,
resumePosition, resumePosition,
@ -941,7 +945,6 @@ class ExpStateCasting {
"added new castLocalHls handlers (hlsPath: $hlsPath, videoPath: $videoPath, audioPath: $audioPath, subtitlePath: $subtitlePath)." "added new castLocalHls handlers (hlsPath: $hlsPath, videoPath: $videoPath, audioPath: $audioPath, subtitlePath: $subtitlePath)."
) )
ad.loadVideo( ad.loadVideo(
"BUFFERED",
"application/vnd.apple.mpegurl", "application/vnd.apple.mpegurl",
hlsUrl, hlsUrl,
resumePosition, resumePosition,
@ -1021,7 +1024,6 @@ class ExpStateCasting {
"added new castLocalDash handlers (dashPath: $dashPath, videoPath: $videoPath, audioPath: $audioPath, subtitlePath: $subtitlePath)." "added new castLocalDash handlers (dashPath: $dashPath, videoPath: $videoPath, audioPath: $audioPath, subtitlePath: $subtitlePath)."
); );
ad.loadVideo( ad.loadVideo(
"BUFFERED",
"application/dash+xml", "application/dash+xml",
dashUrl, dashUrl,
resumePosition, resumePosition,
@ -1297,7 +1299,6 @@ class ExpStateCasting {
val hackfixResumePosition = val hackfixResumePosition =
if (ad.device.castingProtocol() == ProtocolType.CHROMECAST && !video.isLive && resumePosition == 0.0) 0.1 else resumePosition; if (ad.device.castingProtocol() == ProtocolType.CHROMECAST && !video.isLive && resumePosition == 0.0) 0.1 else resumePosition;
ad.loadVideo( ad.loadVideo(
if (video.isLive) "LIVE" else "BUFFERED",
"application/vnd.apple.mpegurl", "application/vnd.apple.mpegurl",
hlsUrl, hlsUrl,
hackfixResumePosition, hackfixResumePosition,
@ -1570,7 +1571,6 @@ class ExpStateCasting {
Logger.i(TAG, "added new castHls handlers (hlsPath: $hlsPath)."); Logger.i(TAG, "added new castHls handlers (hlsPath: $hlsPath).");
ad.loadVideo( ad.loadVideo(
if (video.isLive) "LIVE" else "BUFFERED",
"application/vnd.apple.mpegurl", "application/vnd.apple.mpegurl",
hlsUrl, hlsUrl,
resumePosition, resumePosition,
@ -1686,7 +1686,6 @@ class ExpStateCasting {
"added new castDash handlers (dashPath: $dashPath, videoPath: $videoPath, audioPath: $audioPath)." "added new castDash handlers (dashPath: $dashPath, videoPath: $videoPath, audioPath: $audioPath)."
); );
ad.loadVideo( ad.loadVideo(
if (video.isLive) "LIVE" else "BUFFERED",
"application/dash+xml", "application/dash+xml",
dashUrl, dashUrl,
resumePosition, resumePosition,
@ -1955,7 +1954,6 @@ class ExpStateCasting {
"added new castDash handlers (dashPath: $dashPath, videoPath: $videoPath, audioPath: $audioPath)." "added new castDash handlers (dashPath: $dashPath, videoPath: $videoPath, audioPath: $audioPath)."
); );
ad.loadVideo( ad.loadVideo(
if (video.isLive) "LIVE" else "BUFFERED",
"application/dash+xml", "application/dash+xml",
dashUrl, dashUrl,
resumePosition, resumePosition,
@ -1971,7 +1969,7 @@ class ExpStateCasting {
deviceInfo.addresses.map { org.fcast.sender_sdk.tryIpAddrFromStr(it) } // Throws! deviceInfo.addresses.map { org.fcast.sender_sdk.tryIpAddrFromStr(it) } // Throws!
val rsDeviceInfo = RsDeviceInfo( val rsDeviceInfo = RsDeviceInfo(
name = deviceInfo.name, name = deviceInfo.name,
type = when (deviceInfo.type) { protocol = when (deviceInfo.type) {
com.futo.platformplayer.casting.CastProtocolType.CHROMECAST -> ProtocolType.CHROMECAST com.futo.platformplayer.casting.CastProtocolType.CHROMECAST -> ProtocolType.CHROMECAST
com.futo.platformplayer.casting.CastProtocolType.FCAST -> ProtocolType.F_CAST com.futo.platformplayer.casting.CastProtocolType.FCAST -> ProtocolType.F_CAST
else -> throw IllegalArgumentException() else -> throw IllegalArgumentException()

View file

@ -74,7 +74,8 @@ class DeviceViewHolder : ViewHolder {
if (dev.handle.device.isReady()) { if (dev.handle.device.isReady()) {
// NOTE: we assume experimental casting is used // NOTE: we assume experimental casting is used
try { try {
ExpStateCasting.instance.activeDevice?.device?.stopCasting() ExpStateCasting.instance.activeDevice?.device?.stopPlayback()
ExpStateCasting.instance.activeDevice?.device?.disconnect()
} catch (e: Throwable) { } catch (e: Throwable) {
//Ignored //Ignored
} }