diff --git a/app/build.gradle b/app/build.gradle index d1d75fcf..84750967 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -232,7 +232,7 @@ dependencies { androidTestImplementation 'androidx.test.espresso:espresso-core:3.7.0' //Rust casting SDK - implementation('org.futo.gitlab.videostreaming.fcast-sdk-jitpack:sender-sdk-minimal:0.3.1') { + implementation('org.futo.gitlab.videostreaming.fcast-sdk-jitpack:sender-sdk-minimal:0.4.0') { // Polycentricandroid includes this exclude group: 'net.java.dev.jna' } diff --git a/app/src/main/java/com/futo/platformplayer/casting/CastingDevice.kt b/app/src/main/java/com/futo/platformplayer/casting/CastingDevice.kt index cb6c9ab0..084bbb21 100644 --- a/app/src/main/java/com/futo/platformplayer/casting/CastingDevice.kt +++ b/app/src/main/java/com/futo/platformplayer/casting/CastingDevice.kt @@ -1,5 +1,6 @@ package com.futo.platformplayer.casting +import com.futo.platformplayer.constructs.Event0 import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.models.CastingDeviceInfo import org.fcast.sender_sdk.Metadata @@ -16,6 +17,7 @@ abstract class CastingDevice { abstract val onDurationChanged: Event1 abstract val onVolumeChanged: Event1 abstract val onSpeedChanged: Event1 + abstract val onMediaItemEnd: Event0 abstract var connectionState: CastConnectionState abstract val protocolType: CastProtocolType abstract var isPlaying: Boolean diff --git a/app/src/main/java/com/futo/platformplayer/casting/CastingDeviceExp.kt b/app/src/main/java/com/futo/platformplayer/casting/CastingDeviceExp.kt index 84d96e02..5e96b2f1 100644 --- a/app/src/main/java/com/futo/platformplayer/casting/CastingDeviceExp.kt +++ b/app/src/main/java/com/futo/platformplayer/casting/CastingDeviceExp.kt @@ -2,12 +2,14 @@ package com.futo.platformplayer.casting import android.os.Build import com.futo.platformplayer.BuildConfig +import com.futo.platformplayer.constructs.Event0 import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.models.CastingDeviceInfo +import com.futo.polycentric.core.Event import org.fcast.sender_sdk.ApplicationInfo -import org.fcast.sender_sdk.GenericKeyEvent -import org.fcast.sender_sdk.GenericMediaEvent +import org.fcast.sender_sdk.KeyEvent +import org.fcast.sender_sdk.MediaEvent import org.fcast.sender_sdk.PlaybackState import org.fcast.sender_sdk.Source import java.net.InetAddress @@ -15,8 +17,10 @@ import org.fcast.sender_sdk.CastingDevice as RsCastingDevice; import org.fcast.sender_sdk.DeviceEventHandler as RsDeviceEventHandler; import org.fcast.sender_sdk.DeviceConnectionState import org.fcast.sender_sdk.DeviceFeature +import org.fcast.sender_sdk.EventSubscription import org.fcast.sender_sdk.IpAddr import org.fcast.sender_sdk.LoadRequest +import org.fcast.sender_sdk.MediaItemEventType import org.fcast.sender_sdk.Metadata import org.fcast.sender_sdk.ProtocolType import org.fcast.sender_sdk.urlFormatIpAddr @@ -63,6 +67,7 @@ class CastingDeviceExp(val device: RsCastingDevice) : CastingDevice() { var onDurationChanged = Event1() var onVolumeChanged = Event1() var onSpeedChanged = Event1() + var onMediaItemEnd = Event0() override fun connectionStateChanged(state: DeviceConnectionState) { onConnectionStateChanged.emit(state) @@ -92,12 +97,14 @@ class CastingDeviceExp(val device: RsCastingDevice) : CastingDevice() { // TODO } - override fun keyEvent(event: GenericKeyEvent) { + override fun keyEvent(event: KeyEvent) { // Unreachable } - override fun mediaEvent(event: GenericMediaEvent) { - // Unreachable + override fun mediaEvent(event: MediaEvent) { + if (event.type == MediaItemEventType.END) { + onMediaItemEnd.emit() + } } override fun playbackError(message: String) { @@ -127,6 +134,8 @@ class CastingDeviceExp(val device: RsCastingDevice) : CastingDevice() { get() = eventHandler.onVolumeChanged override val onSpeedChanged: Event1 get() = eventHandler.onSpeedChanged + override val onMediaItemEnd: Event0 + get() = eventHandler.onMediaItemEnd override fun resumePlayback() = device.resumePlayback() override fun pausePlayback() = device.pausePlayback() @@ -181,7 +190,8 @@ class CastingDeviceExp(val device: RsCastingDevice) : CastingDevice() { resumePosition = resumePosition, speed = speed, volume = volume, - metadata = metadata + metadata = metadata, + requestHeaders = null, ) ) @@ -200,6 +210,7 @@ class CastingDeviceExp(val device: RsCastingDevice) : CastingDevice() { speed = speed, volume = volume, metadata = metadata, + requestHeaders = null, ) ) @@ -227,6 +238,13 @@ class CastingDeviceExp(val device: RsCastingDevice) : CastingDevice() { eventHandler.onConnectionStateChanged.subscribe { newState -> when (newState) { is DeviceConnectionState.Connected -> { + if (device.supportsFeature(DeviceFeature.MEDIA_EVENT_SUBSCRIPTION)) { + try { + device.subscribeEvent(EventSubscription.MediaItemEnd) + } catch (e: Exception) { + Logger.e(TAG, "Failed to subscribe to MediaItemEnd events: $e") + } + } usedRemoteAddress = ipAddrToInetAddress(newState.usedRemoteAddr) localAddress = ipAddrToInetAddress(newState.localAddr) connectionState = CastConnectionState.CONNECTED diff --git a/app/src/main/java/com/futo/platformplayer/casting/CastingDeviceLegacy.kt b/app/src/main/java/com/futo/platformplayer/casting/CastingDeviceLegacy.kt index d9ed6956..abd27c90 100644 --- a/app/src/main/java/com/futo/platformplayer/casting/CastingDeviceLegacy.kt +++ b/app/src/main/java/com/futo/platformplayer/casting/CastingDeviceLegacy.kt @@ -1,5 +1,6 @@ package com.futo.platformplayer.casting +import com.futo.platformplayer.constructs.Event0 import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.models.CastingDeviceInfo import kotlinx.serialization.KSerializer @@ -181,6 +182,7 @@ class CastingDeviceLegacyWrapper(val inner: CastingDeviceLegacy) : CastingDevice override val onDurationChanged: Event1 get() = inner.onDurationChanged override val onVolumeChanged: Event1 get() = inner.onVolumeChanged override val onSpeedChanged: Event1 get() = inner.onSpeedChanged + override val onMediaItemEnd: Event0 = Event0() override var connectionState: CastConnectionState get() = inner.connectionState set(_) = Unit diff --git a/app/src/main/java/com/futo/platformplayer/casting/StateCasting.kt b/app/src/main/java/com/futo/platformplayer/casting/StateCasting.kt index 491b47ff..1002ffb9 100644 --- a/app/src/main/java/com/futo/platformplayer/casting/StateCasting.kt +++ b/app/src/main/java/com/futo/platformplayer/casting/StateCasting.kt @@ -40,6 +40,7 @@ import com.futo.platformplayer.api.media.platforms.local.models.sources.LocalAud import com.futo.platformplayer.api.media.platforms.local.models.sources.LocalVideoContentSource import com.futo.platformplayer.awaitCancelConverted import com.futo.platformplayer.builders.DashBuilder +import com.futo.platformplayer.constructs.Event0 import com.futo.platformplayer.models.CastingDeviceInfo import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.constructs.Event2 @@ -82,6 +83,7 @@ abstract class StateCasting { val onActiveDeviceTimeChanged = Event1(); val onActiveDeviceDurationChanged = Event1(); val onActiveDeviceVolumeChanged = Event1(); + val onActiveDeviceMediaItemEnd = Event0() var activeDevice: CastingDevice? = null; private var _videoExecutor: JSRequestExecutor? = null private var _audioExecutor: JSRequestExecutor? = null @@ -145,6 +147,7 @@ abstract class StateCasting { device.onTimeChanged.clear(); device.onVolumeChanged.clear(); device.onDurationChanged.clear(); + device.onMediaItemEnd.clear(); ad.disconnect() } @@ -159,6 +162,7 @@ abstract class StateCasting { device.onTimeChanged.clear(); device.onVolumeChanged.clear(); device.onDurationChanged.clear(); + device.onMediaItemEnd.clear(); activeDevice = null; } @@ -222,6 +226,9 @@ abstract class StateCasting { device.onTimeChanged.subscribe { invokeInMainScopeIfRequired { onActiveDeviceTimeChanged.emit(it) }; }; + device.onMediaItemEnd.subscribe { + invokeInMainScopeIfRequired { onActiveDeviceMediaItemEnd.emit() } + } try { device.connect(); @@ -232,6 +239,7 @@ abstract class StateCasting { device.onTimeChanged.clear(); device.onVolumeChanged.clear(); device.onDurationChanged.clear(); + device.onMediaItemEnd.clear(); return; } diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt index ca5af1f9..9eae9587 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt @@ -723,15 +723,17 @@ class VideoDetailView : ConstraintLayout { val activeDevice = StateCasting.instance.activeDevice; if (activeDevice != null) { handlePlayChanged(it); - - val v = video; - if (!it && v != null && v.duration - activeDevice.time.toLong() < 2L) { - Log.i(TAG, "Next video (loop?)") - nextVideo(); - } } }; + StateCasting.instance.onActiveDeviceMediaItemEnd.subscribe(this) { + val activeDevice = StateCasting.instance.activeDevice; + if (activeDevice != null) { + Log.i(TAG, "Next video (loop?)") + nextVideo(); + } + } + StateCasting.instance.onActiveDeviceTimeChanged.subscribe(this) { if (_isCasting) { setLastPositionMilliseconds((it * 1000.0).toLong(), true); @@ -1273,6 +1275,7 @@ class VideoDetailView : ConstraintLayout { StateCasting.instance.onActiveDevicePlayChanged.remove(this); StateCasting.instance.onActiveDeviceTimeChanged.remove(this); StateCasting.instance.onActiveDeviceConnectionStateChanged.remove(this); + StateCasting.instance.onActiveDeviceMediaItemEnd.remove(this) StateApp.instance.preventPictureInPicture.remove(this); StatePlayer.instance.onQueueChanged.remove(this); StatePlayer.instance.onVideoChanging.remove(this);