mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2025-04-20 03:24:50 +00:00
Merge branch 'master' of gitlab.futo.org:videostreaming/grayjay
This commit is contained in:
commit
a3c8bbb21f
13 changed files with 159 additions and 51 deletions
|
@ -146,7 +146,7 @@ class DashBuilder : XMLBuilder {
|
|||
dashBuilder.withAdaptationSet(
|
||||
mapOf(
|
||||
Pair("mimeType", subtitleSource.format ?: "text/vtt"),
|
||||
Pair("lang", "en"),
|
||||
Pair("lang", "df"),
|
||||
Pair("default", "true")
|
||||
)
|
||||
) {
|
||||
|
|
|
@ -18,6 +18,7 @@ class AirPlayCastingDevice : CastingDevice {
|
|||
override var usedRemoteAddress: InetAddress? = null;
|
||||
override var localAddress: InetAddress? = null;
|
||||
override val canSetVolume: Boolean get() = false;
|
||||
override val canSetSpeed: Boolean get() = false; //TODO: Implement playback speed for AirPlay
|
||||
|
||||
var addresses: Array<InetAddress>? = null;
|
||||
var port: Int = 0;
|
||||
|
@ -43,12 +44,12 @@ class AirPlayCastingDevice : CastingDevice {
|
|||
return addresses?.toList() ?: listOf();
|
||||
}
|
||||
|
||||
override fun loadVideo(streamType: String, contentType: String, contentId: String, resumePosition: Double, duration: Double) {
|
||||
if (invokeInIOScopeIfRequired({ loadVideo(streamType, contentType, contentId, resumePosition, duration) })) {
|
||||
override fun loadVideo(streamType: String, contentType: String, contentId: String, resumePosition: Double, duration: Double, speed: Double?) {
|
||||
if (invokeInIOScopeIfRequired({ loadVideo(streamType, contentType, contentId, resumePosition, duration, speed) })) {
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.i(FCastCastingDevice.TAG, "Start streaming (streamType: $streamType, contentType: $contentType, contentId: $contentId, resumePosition: $resumePosition, duration: $duration)");
|
||||
Logger.i(FCastCastingDevice.TAG, "Start streaming (streamType: $streamType, contentType: $contentType, contentId: $contentId, resumePosition: $resumePosition, duration: $duration, speed: $speed)");
|
||||
|
||||
time = resumePosition;
|
||||
if (resumePosition > 0.0) {
|
||||
|
@ -60,7 +61,7 @@ class AirPlayCastingDevice : CastingDevice {
|
|||
}
|
||||
}
|
||||
|
||||
override fun loadContent(contentType: String, content: String, resumePosition: Double, duration: Double) {
|
||||
override fun loadContent(contentType: String, content: String, resumePosition: Double, duration: Double, speed: Double?) {
|
||||
throw NotImplementedError();
|
||||
}
|
||||
|
||||
|
|
|
@ -48,6 +48,7 @@ abstract class CastingDevice {
|
|||
abstract var usedRemoteAddress: InetAddress?;
|
||||
abstract var localAddress: InetAddress?;
|
||||
abstract val canSetVolume: Boolean;
|
||||
abstract val canSetSpeed: Boolean;
|
||||
|
||||
var name: String? = null;
|
||||
var isPlaying: Boolean = false
|
||||
|
@ -77,6 +78,14 @@ abstract class CastingDevice {
|
|||
onVolumeChanged.emit(value);
|
||||
}
|
||||
};
|
||||
var speed: Double = 1.0
|
||||
set(value) {
|
||||
val changed = value != field;
|
||||
speed = value;
|
||||
if (changed) {
|
||||
onSpeedChanged.emit(value);
|
||||
}
|
||||
};
|
||||
val expectedCurrentTime: Double
|
||||
get() {
|
||||
val diff = timeReceivedAt.getNowDiffMiliseconds().toDouble() / 1000.0;
|
||||
|
@ -96,6 +105,7 @@ abstract class CastingDevice {
|
|||
var onPlayChanged = Event1<Boolean>();
|
||||
var onTimeChanged = Event1<Double>();
|
||||
var onVolumeChanged = Event1<Double>();
|
||||
var onSpeedChanged = Event1<Double>();
|
||||
|
||||
abstract fun stopCasting();
|
||||
|
||||
|
@ -103,9 +113,10 @@ abstract class CastingDevice {
|
|||
abstract fun stopVideo();
|
||||
abstract fun pauseVideo();
|
||||
abstract fun resumeVideo();
|
||||
abstract fun loadVideo(streamType: String, contentType: String, contentId: String, resumePosition: Double, duration: Double);
|
||||
abstract fun loadContent(contentType: String, content: String, resumePosition: Double, duration: Double);
|
||||
abstract fun loadVideo(streamType: String, contentType: String, contentId: String, resumePosition: Double, duration: Double, speed: Double?);
|
||||
abstract fun loadContent(contentType: String, content: String, resumePosition: Double, duration: Double, speed: Double?);
|
||||
open fun changeVolume(volume: Double) { throw NotImplementedError() }
|
||||
open fun changeSpeed(speed: Double) { throw NotImplementedError() }
|
||||
|
||||
abstract fun start();
|
||||
abstract fun stop();
|
||||
|
|
|
@ -27,6 +27,7 @@ class ChromecastCastingDevice : CastingDevice {
|
|||
override var usedRemoteAddress: InetAddress? = null;
|
||||
override var localAddress: InetAddress? = null;
|
||||
override val canSetVolume: Boolean get() = true;
|
||||
override val canSetSpeed: Boolean get() = false; //TODO: Implement
|
||||
|
||||
var addresses: Array<InetAddress>? = null;
|
||||
var port: Int = 0;
|
||||
|
@ -62,12 +63,12 @@ class ChromecastCastingDevice : CastingDevice {
|
|||
return addresses?.toList() ?: listOf();
|
||||
}
|
||||
|
||||
override fun loadVideo(streamType: String, contentType: String, contentId: String, resumePosition: Double, duration: Double) {
|
||||
if (invokeInIOScopeIfRequired({ loadVideo(streamType, contentType, contentId, resumePosition, duration) })) {
|
||||
override fun loadVideo(streamType: String, contentType: String, contentId: String, resumePosition: Double, duration: Double, speed: Double?) {
|
||||
if (invokeInIOScopeIfRequired({ loadVideo(streamType, contentType, contentId, resumePosition, duration, speed) })) {
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.i(TAG, "Start streaming (streamType: $streamType, contentType: $contentType, contentId: $contentId, resumePosition: $resumePosition, duration: $duration)");
|
||||
Logger.i(TAG, "Start streaming (streamType: $streamType, contentType: $contentType, contentId: $contentId, resumePosition: $resumePosition, duration: $duration, speed: $speed)");
|
||||
|
||||
time = resumePosition;
|
||||
_streamType = streamType;
|
||||
|
@ -77,7 +78,7 @@ class ChromecastCastingDevice : CastingDevice {
|
|||
playVideo();
|
||||
}
|
||||
|
||||
override fun loadContent(contentType: String, content: String, resumePosition: Double, duration: Double) {
|
||||
override fun loadContent(contentType: String, content: String, resumePosition: Double, duration: Double, speed: Double?) {
|
||||
//TODO: Can maybe be implemented by sending data:contentType,base64...
|
||||
throw NotImplementedError();
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package com.futo.platformplayer.casting
|
|||
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import com.futo.platformplayer.UIDialogs
|
||||
import com.futo.platformplayer.casting.models.*
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
import com.futo.platformplayer.getConnectedSocket
|
||||
|
@ -27,7 +28,10 @@ enum class Opcode(val value: Byte) {
|
|||
SEEK(5),
|
||||
PLAYBACK_UPDATE(6),
|
||||
VOLUME_UPDATE(7),
|
||||
SET_VOLUME(8)
|
||||
SET_VOLUME(8),
|
||||
PLAYBACK_ERROR(9),
|
||||
SET_SPEED(10),
|
||||
VERSION(11)
|
||||
}
|
||||
|
||||
class FCastCastingDevice : CastingDevice {
|
||||
|
@ -38,6 +42,7 @@ class FCastCastingDevice : CastingDevice {
|
|||
override var usedRemoteAddress: InetAddress? = null;
|
||||
override var localAddress: InetAddress? = null;
|
||||
override val canSetVolume: Boolean get() = true;
|
||||
override val canSetSpeed: Boolean get() = true;
|
||||
|
||||
var addresses: Array<InetAddress>? = null;
|
||||
var port: Int = 0;
|
||||
|
@ -47,6 +52,7 @@ class FCastCastingDevice : CastingDevice {
|
|||
private var _inputStream: DataInputStream? = null;
|
||||
private var _scopeIO: CoroutineScope? = null;
|
||||
private var _started: Boolean = false;
|
||||
private var _version: Long = 1;
|
||||
|
||||
constructor(name: String, addresses: Array<InetAddress>, port: Int) : super() {
|
||||
this.name = name;
|
||||
|
@ -64,33 +70,45 @@ class FCastCastingDevice : CastingDevice {
|
|||
return addresses?.toList() ?: listOf();
|
||||
}
|
||||
|
||||
override fun loadVideo(streamType: String, contentType: String, contentId: String, resumePosition: Double, duration: Double) {
|
||||
if (invokeInIOScopeIfRequired({ loadVideo(streamType, contentType, contentId, resumePosition, duration) })) {
|
||||
override fun loadVideo(streamType: String, contentType: String, contentId: String, resumePosition: Double, duration: Double, speed: Double?) {
|
||||
if (invokeInIOScopeIfRequired({ loadVideo(streamType, contentType, contentId, resumePosition, duration, speed) })) {
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.i(TAG, "Start streaming (streamType: $streamType, contentType: $contentType, contentId: $contentId, resumePosition: $resumePosition, duration: $duration)");
|
||||
//TODO: Remove this later, temporary for the transition
|
||||
if (_version <= 1L) {
|
||||
UIDialogs.toast("Version not received, if you are experiencing issues, try updating FCast")
|
||||
}
|
||||
|
||||
Logger.i(TAG, "Start streaming (streamType: $streamType, contentType: $contentType, contentId: $contentId, resumePosition: $resumePosition, duration: $duration, speed: $speed)");
|
||||
|
||||
time = resumePosition;
|
||||
sendMessage(Opcode.PLAY, FCastPlayMessage(
|
||||
container = contentType,
|
||||
url = contentId,
|
||||
time = resumePosition.toInt()
|
||||
time = resumePosition,
|
||||
speed = speed
|
||||
));
|
||||
}
|
||||
|
||||
override fun loadContent(contentType: String, content: String, resumePosition: Double, duration: Double) {
|
||||
if (invokeInIOScopeIfRequired({ loadContent(contentType, content, resumePosition, duration) })) {
|
||||
override fun loadContent(contentType: String, content: String, resumePosition: Double, duration: Double, speed: Double?) {
|
||||
if (invokeInIOScopeIfRequired({ loadContent(contentType, content, resumePosition, duration, speed) })) {
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.i(TAG, "Start streaming content (contentType: $contentType, resumePosition: $resumePosition, duration: $duration)");
|
||||
//TODO: Remove this later, temporary for the transition
|
||||
if (_version <= 1L) {
|
||||
UIDialogs.toast("Version not received, if you are experiencing issues, try updating FCast")
|
||||
}
|
||||
|
||||
Logger.i(TAG, "Start streaming content (contentType: $contentType, resumePosition: $resumePosition, duration: $duration, speed: $speed)");
|
||||
|
||||
time = resumePosition;
|
||||
sendMessage(Opcode.PLAY, FCastPlayMessage(
|
||||
container = contentType,
|
||||
content = content,
|
||||
time = resumePosition.toInt()
|
||||
time = resumePosition,
|
||||
speed = speed
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -103,13 +121,22 @@ class FCastCastingDevice : CastingDevice {
|
|||
sendMessage(Opcode.SET_VOLUME, FCastSetVolumeMessage(volume))
|
||||
}
|
||||
|
||||
override fun changeSpeed(speed: Double) {
|
||||
if (invokeInIOScopeIfRequired({ changeSpeed(volume) })) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.speed = speed
|
||||
sendMessage(Opcode.SET_SPEED, FCastSetSpeedMessage(volume))
|
||||
}
|
||||
|
||||
override fun seekVideo(timeSeconds: Double) {
|
||||
if (invokeInIOScopeIfRequired({ seekVideo(timeSeconds) })) {
|
||||
return;
|
||||
}
|
||||
|
||||
sendMessage(Opcode.SEEK, FCastSeekMessage(
|
||||
time = timeSeconds.toInt()
|
||||
time = timeSeconds
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -282,8 +309,8 @@ class FCastCastingDevice : CastingDevice {
|
|||
return;
|
||||
}
|
||||
|
||||
val playbackUpdate = Json.decodeFromString<FCastPlaybackUpdateMessage>(json);
|
||||
time = playbackUpdate.time.toDouble();
|
||||
val playbackUpdate = FCastCastingDevice.json.decodeFromString<FCastPlaybackUpdateMessage>(json);
|
||||
time = playbackUpdate.time;
|
||||
isPlaying = when (playbackUpdate.state) {
|
||||
1 -> true
|
||||
else -> false
|
||||
|
@ -295,9 +322,28 @@ class FCastCastingDevice : CastingDevice {
|
|||
return;
|
||||
}
|
||||
|
||||
val volumeUpdate = Json.decodeFromString<FCastVolumeUpdateMessage>(json);
|
||||
val volumeUpdate = FCastCastingDevice.json.decodeFromString<FCastVolumeUpdateMessage>(json);
|
||||
volume = volumeUpdate.volume;
|
||||
}
|
||||
Opcode.PLAYBACK_ERROR -> {
|
||||
if (json == null) {
|
||||
Logger.w(TAG, "Got playback error without JSON, ignoring.");
|
||||
return;
|
||||
}
|
||||
|
||||
val playbackError = FCastCastingDevice.json.decodeFromString<FCastPlaybackErrorMessage>(json);
|
||||
Logger.e(TAG, "Remote casting playback error received: $playbackError")
|
||||
}
|
||||
Opcode.VERSION -> {
|
||||
if (json == null) {
|
||||
Logger.w(TAG, "Got version without JSON, ignoring.");
|
||||
return;
|
||||
}
|
||||
|
||||
val version = FCastCastingDevice.json.decodeFromString<FCastVersionMessage>(json);
|
||||
_version = version.version;
|
||||
Logger.i(TAG, "Remote version received: $version")
|
||||
}
|
||||
else -> { }
|
||||
}
|
||||
}
|
||||
|
@ -333,7 +379,7 @@ class FCastCastingDevice : CastingDevice {
|
|||
val data: ByteArray;
|
||||
var jsonString: String? = null;
|
||||
if (message != null) {
|
||||
jsonString = Json.encodeToString(message);
|
||||
jsonString = json.encodeToString(message);
|
||||
data = jsonString.encodeToByteArray();
|
||||
} else {
|
||||
data = ByteArray(0);
|
||||
|
@ -403,5 +449,6 @@ class FCastCastingDevice : CastingDevice {
|
|||
|
||||
companion object {
|
||||
val TAG = "FastCastCastingDevice";
|
||||
private val json = Json { ignoreUnknownKeys = true }
|
||||
}
|
||||
}
|
|
@ -395,17 +395,17 @@ class StateCasting {
|
|||
} else {
|
||||
if (videoSource is IVideoUrlSource) {
|
||||
Logger.i(TAG, "Casting as singular video");
|
||||
ad.loadVideo(if (video.isLive) "LIVE" else "BUFFERED", videoSource.container, videoSource.getVideoUrl(), resumePosition, video.duration.toDouble());
|
||||
ad.loadVideo(if (video.isLive) "LIVE" else "BUFFERED", videoSource.container, videoSource.getVideoUrl(), resumePosition, video.duration.toDouble(), null);
|
||||
} else if (audioSource is IAudioUrlSource) {
|
||||
Logger.i(TAG, "Casting as singular audio");
|
||||
ad.loadVideo(if (video.isLive) "LIVE" else "BUFFERED", audioSource.container, audioSource.getAudioUrl(), resumePosition, video.duration.toDouble());
|
||||
ad.loadVideo(if (video.isLive) "LIVE" else "BUFFERED", audioSource.container, audioSource.getAudioUrl(), resumePosition, video.duration.toDouble(), null);
|
||||
} else if(videoSource is IHLSManifestSource) {
|
||||
if (ad is ChromecastCastingDevice) {
|
||||
Logger.i(TAG, "Casting as proxied HLS");
|
||||
castProxiedHls(video, videoSource.url, videoSource.codec, resumePosition);
|
||||
} else {
|
||||
Logger.i(TAG, "Casting as non-proxied HLS");
|
||||
ad.loadVideo(if (video.isLive) "LIVE" else "BUFFERED", videoSource.container, videoSource.url, resumePosition, video.duration.toDouble());
|
||||
ad.loadVideo(if (video.isLive) "LIVE" else "BUFFERED", videoSource.container, videoSource.url, resumePosition, video.duration.toDouble(), null);
|
||||
}
|
||||
} else if(audioSource is IHLSManifestAudioSource) {
|
||||
if (ad is ChromecastCastingDevice) {
|
||||
|
@ -413,7 +413,7 @@ class StateCasting {
|
|||
castProxiedHls(video, audioSource.url, audioSource.codec, resumePosition);
|
||||
} else {
|
||||
Logger.i(TAG, "Casting as non-proxied audio HLS");
|
||||
ad.loadVideo(if (video.isLive) "LIVE" else "BUFFERED", audioSource.container, audioSource.url, resumePosition, video.duration.toDouble());
|
||||
ad.loadVideo(if (video.isLive) "LIVE" else "BUFFERED", audioSource.container, audioSource.url, resumePosition, video.duration.toDouble(), null);
|
||||
}
|
||||
} else if (videoSource is LocalVideoSource) {
|
||||
Logger.i(TAG, "Casting as local video");
|
||||
|
@ -480,7 +480,7 @@ class StateCasting {
|
|||
).withTag("cast");
|
||||
|
||||
Logger.i(TAG, "Casting local video (videoUrl: $videoUrl).");
|
||||
ad.loadVideo("BUFFERED", videoSource.container, videoUrl, resumePosition, video.duration.toDouble());
|
||||
ad.loadVideo("BUFFERED", videoSource.container, videoUrl, resumePosition, video.duration.toDouble(), null);
|
||||
|
||||
return listOf(videoUrl);
|
||||
}
|
||||
|
@ -499,7 +499,7 @@ class StateCasting {
|
|||
).withTag("cast");
|
||||
|
||||
Logger.i(TAG, "Casting local audio (audioUrl: $audioUrl).");
|
||||
ad.loadVideo("BUFFERED", audioSource.container, audioUrl, resumePosition, video.duration.toDouble());
|
||||
ad.loadVideo("BUFFERED", audioSource.container, audioUrl, resumePosition, video.duration.toDouble(), null);
|
||||
|
||||
return listOf(audioUrl);
|
||||
}
|
||||
|
@ -563,7 +563,7 @@ class StateCasting {
|
|||
.withHeader("Access-Control-Allow-Origin", "*"), true
|
||||
).withTag("castLocalHls")
|
||||
|
||||
mediaRenditions.add(HLS.MediaRendition("AUDIO", audioVariantPlaylistUrl, "audio", "en", "english", true, true, true))
|
||||
mediaRenditions.add(HLS.MediaRendition("AUDIO", audioVariantPlaylistUrl, "audio", "df", "default", true, true, true))
|
||||
}
|
||||
|
||||
if (subtitleSource != null) {
|
||||
|
@ -584,7 +584,7 @@ class StateCasting {
|
|||
.withHeader("Access-Control-Allow-Origin", "*"), true
|
||||
).withTag("castLocalHls")
|
||||
|
||||
mediaRenditions.add(HLS.MediaRendition("SUBTITLES", subtitleVariantPlaylistUrl, "subtitles", "en", "english", true, true, true))
|
||||
mediaRenditions.add(HLS.MediaRendition("SUBTITLES", subtitleVariantPlaylistUrl, "subtitles", "df", "default", true, true, true))
|
||||
}
|
||||
|
||||
val masterPlaylist = HLS.MasterPlaylist(variantPlaylistReferences, mediaRenditions, listOf(), true)
|
||||
|
@ -595,7 +595,7 @@ class StateCasting {
|
|||
).withTag("castLocalHls")
|
||||
|
||||
Logger.i(TAG, "added new castLocalHls handlers (hlsPath: $hlsPath, videoPath: $videoPath, audioPath: $audioPath, subtitlePath: $subtitlePath).")
|
||||
ad.loadVideo("BUFFERED", "application/vnd.apple.mpegurl", hlsUrl, resumePosition, video.duration.toDouble())
|
||||
ad.loadVideo("BUFFERED", "application/vnd.apple.mpegurl", hlsUrl, resumePosition, video.duration.toDouble(), null)
|
||||
|
||||
return listOf(hlsUrl, videoUrl, audioUrl, subtitleUrl)
|
||||
}
|
||||
|
@ -641,7 +641,7 @@ class StateCasting {
|
|||
}
|
||||
|
||||
Logger.i(TAG, "added new castLocalDash handlers (dashPath: $dashPath, videoPath: $videoPath, audioPath: $audioPath, subtitlePath: $subtitlePath).");
|
||||
ad.loadVideo("BUFFERED", "application/dash+xml", dashUrl, resumePosition, video.duration.toDouble());
|
||||
ad.loadVideo("BUFFERED", "application/dash+xml", dashUrl, resumePosition, video.duration.toDouble(), null);
|
||||
|
||||
return listOf(dashUrl, videoUrl, audioUrl, subtitleUrl);
|
||||
}
|
||||
|
@ -686,7 +686,7 @@ class StateCasting {
|
|||
val content = DashBuilder.generateOnDemandDash(videoSource, videoUrl, audioSource, audioUrl, subtitleSource, subtitlesUrl);
|
||||
|
||||
Logger.i(TAG, "Direct dash cast to casting device (videoUrl: $videoUrl, audioUrl: $audioUrl).");
|
||||
ad.loadContent("application/dash+xml", content, resumePosition, video.duration.toDouble());
|
||||
ad.loadContent("application/dash+xml", content, resumePosition, video.duration.toDouble(), null);
|
||||
|
||||
return listOf(videoSource?.getVideoUrl() ?: "", audioSource?.getAudioUrl() ?: "");
|
||||
}
|
||||
|
@ -812,7 +812,7 @@ class StateCasting {
|
|||
|
||||
//ChromeCast is sometimes funky with resume position 0
|
||||
val hackfixResumePosition = if (ad is ChromecastCastingDevice && !video.isLive && resumePosition == 0.0) 0.1 else resumePosition;
|
||||
ad.loadVideo(if (video.isLive) "LIVE" else "BUFFERED", "application/vnd.apple.mpegurl", hlsUrl, hackfixResumePosition, video.duration.toDouble());
|
||||
ad.loadVideo(if (video.isLive) "LIVE" else "BUFFERED", "application/vnd.apple.mpegurl", hlsUrl, hackfixResumePosition, video.duration.toDouble(), null);
|
||||
|
||||
return listOf(hlsUrl);
|
||||
}
|
||||
|
@ -892,7 +892,7 @@ class StateCasting {
|
|||
.withHeader("Access-Control-Allow-Origin", "*"), true
|
||||
).withTag("castHlsIndirectVariant");
|
||||
|
||||
mediaRenditions.add(HLS.MediaRendition("AUDIO", audioVariantPlaylistUrl, "audio", "en", "english", true, true, true))
|
||||
mediaRenditions.add(HLS.MediaRendition("AUDIO", audioVariantPlaylistUrl, "audio", "df", "default", true, true, true))
|
||||
|
||||
_castServer.addHandlerWithAllowAllOptions(
|
||||
HttpProxyHandler("GET", audioPath, audioSource.getAudioUrl(), true)
|
||||
|
@ -942,7 +942,7 @@ class StateCasting {
|
|||
.withHeader("Access-Control-Allow-Origin", "*"), true
|
||||
).withTag("castHlsIndirectVariant");
|
||||
|
||||
mediaRenditions.add(HLS.MediaRendition("SUBTITLES", subtitleVariantPlaylistUrl, "subtitles", "en", "english", true, true, true))
|
||||
mediaRenditions.add(HLS.MediaRendition("SUBTITLES", subtitleVariantPlaylistUrl, "subtitles", "df", "default", true, true, true))
|
||||
}
|
||||
|
||||
if (videoSource != null) {
|
||||
|
@ -986,7 +986,7 @@ class StateCasting {
|
|||
).withTag("castHlsIndirectMaster")
|
||||
|
||||
Logger.i(TAG, "added new castHls handlers (hlsPath: $hlsPath).");
|
||||
ad.loadVideo(if (video.isLive) "LIVE" else "BUFFERED", "application/vnd.apple.mpegurl", hlsUrl, resumePosition, video.duration.toDouble());
|
||||
ad.loadVideo(if (video.isLive) "LIVE" else "BUFFERED", "application/vnd.apple.mpegurl", hlsUrl, resumePosition, video.duration.toDouble(), null);
|
||||
|
||||
return listOf(hlsUrl, videoSource?.getVideoUrl() ?: "", audioSource?.getAudioUrl() ?: "", subtitlesUri.toString());
|
||||
}
|
||||
|
@ -1061,7 +1061,7 @@ class StateCasting {
|
|||
}
|
||||
|
||||
Logger.i(TAG, "added new castDash handlers (dashPath: $dashPath, videoPath: $videoPath, audioPath: $audioPath).");
|
||||
ad.loadVideo(if (video.isLive) "LIVE" else "BUFFERED", "application/dash+xml", dashUrl, resumePosition, video.duration.toDouble());
|
||||
ad.loadVideo(if (video.isLive) "LIVE" else "BUFFERED", "application/dash+xml", dashUrl, resumePosition, video.duration.toDouble(), null);
|
||||
|
||||
return listOf(dashUrl, videoUrl ?: "", audioUrl ?: "", subtitlesUrl ?: "", videoSource?.getVideoUrl() ?: "", audioSource?.getAudioUrl() ?: "", subtitlesUri.toString());
|
||||
}
|
||||
|
|
|
@ -2,32 +2,52 @@ package com.futo.platformplayer.casting.models
|
|||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@kotlinx.serialization.Serializable
|
||||
@Serializable
|
||||
data class FCastPlayMessage(
|
||||
val container: String,
|
||||
val url: String? = null,
|
||||
val content: String? = null,
|
||||
val time: Int? = null
|
||||
val time: Double? = null,
|
||||
val speed: Double? = null
|
||||
) { }
|
||||
|
||||
@kotlinx.serialization.Serializable
|
||||
@Serializable
|
||||
data class FCastSeekMessage(
|
||||
val time: Int
|
||||
val time: Double
|
||||
) { }
|
||||
|
||||
@kotlinx.serialization.Serializable
|
||||
@Serializable
|
||||
data class FCastPlaybackUpdateMessage(
|
||||
val time: Int,
|
||||
val state: Int
|
||||
val generationTime: Long,
|
||||
val time: Double,
|
||||
val duration: Double,
|
||||
val state: Int,
|
||||
val speed: Double
|
||||
) { }
|
||||
|
||||
|
||||
@Serializable
|
||||
data class FCastVolumeUpdateMessage(
|
||||
val generationTime: Long,
|
||||
val volume: Double
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class FCastSetVolumeMessage(
|
||||
val volume: Double
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class FCastSetSpeedMessage(
|
||||
val speed: Double
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class FCastPlaybackErrorMessage(
|
||||
val message: String
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class FCastVersionMessage(
|
||||
val version: Long
|
||||
)
|
|
@ -216,6 +216,7 @@ class VideoDetailFragment : MainFragment {
|
|||
}
|
||||
_view!!.setTransitionListener(object : MotionLayout.TransitionListener {
|
||||
override fun onTransitionChange(motionLayout: MotionLayout?, startId: Int, endId: Int, progress: Float) {
|
||||
_viewDetail?.stopAllGestures()
|
||||
|
||||
if (state != State.MINIMIZED && progress < 0.1) {
|
||||
state = State.MINIMIZED;
|
||||
|
|
|
@ -686,6 +686,11 @@ class VideoDetailView : ConstraintLayout {
|
|||
}
|
||||
}
|
||||
|
||||
fun stopAllGestures() {
|
||||
_player.stopAllGestures();
|
||||
_cast.stopAllGestures();
|
||||
}
|
||||
|
||||
fun updateMoreButtons() {
|
||||
val buttons = listOf(RoundButton(context, R.drawable.ic_add, context.getString(R.string.add), TAG_ADD) {
|
||||
(video ?: _searchVideo)?.let {
|
||||
|
@ -1944,6 +1949,7 @@ class VideoDetailView : ConstraintLayout {
|
|||
|
||||
video?.let { updateQualitySourcesOverlay(it, videoLocal); };
|
||||
|
||||
val changed = _isCasting != isCasting;
|
||||
_isCasting = isCasting;
|
||||
|
||||
if(isCasting) {
|
||||
|
@ -1951,8 +1957,7 @@ class VideoDetailView : ConstraintLayout {
|
|||
_player.stop();
|
||||
_player.hideControls(false);
|
||||
_cast.visibility = View.VISIBLE;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
StateCasting.instance.stopVideo();
|
||||
_cast.stopTimeJob();
|
||||
_cast.visibility = View.GONE;
|
||||
|
@ -1961,6 +1966,10 @@ class VideoDetailView : ConstraintLayout {
|
|||
_player.setPlaybackRate(Settings.instance.playback.getDefaultPlaybackSpeed());
|
||||
}
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
stopAllGestures();
|
||||
}
|
||||
}
|
||||
|
||||
fun setFullscreen(fullscreen : Boolean) {
|
||||
|
|
|
@ -330,11 +330,21 @@ class GestureControlView : LinearLayout {
|
|||
_controlsVisible = false;
|
||||
}
|
||||
|
||||
fun stopAllGestures() {
|
||||
stopAdjustingFullscreenDown()
|
||||
stopAdjustingBrightness()
|
||||
stopAdjustingSound()
|
||||
stopAdjustingFullscreenUp()
|
||||
stopFastForward()
|
||||
stopAutoFastForward()
|
||||
}
|
||||
|
||||
fun cleanup() {
|
||||
_jobExitFastForward?.cancel();
|
||||
_jobExitFastForward = null;
|
||||
_jobAutoFastForward?.cancel();
|
||||
_jobAutoFastForward = null;
|
||||
stopAllGestures();
|
||||
cancelHideJob();
|
||||
_scope.cancel();
|
||||
}
|
||||
|
|
|
@ -102,6 +102,10 @@ class CastView : ConstraintLayout {
|
|||
_updateTimeJob = null;
|
||||
}
|
||||
|
||||
fun stopAllGestures() {
|
||||
_gestureControlView.stopAllGestures();
|
||||
}
|
||||
|
||||
fun setIsPlaying(isPlaying: Boolean) {
|
||||
_updateTimeJob?.cancel();
|
||||
|
||||
|
|
|
@ -343,6 +343,10 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
|||
_currentChapterLoopActive = false;
|
||||
}
|
||||
|
||||
fun stopAllGestures() {
|
||||
gestureControl.stopAllGestures();
|
||||
}
|
||||
|
||||
fun attachPlayer() {
|
||||
exoPlayer?.attach(_videoView, PLAYER_STATE_NAME);
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 07aa5a9aab441657f89ae14ff3cfd9d9ca977fe6
|
||||
Subproject commit 8b8fd55f39a5039ed15bee3b7e8e9a6ef5a6f538
|
Loading…
Add table
Reference in a new issue