Default language fixes.

This commit is contained in:
Koen 2023-12-07 17:00:47 +01:00
parent 5fffaf2f4e
commit 280feea06e
7 changed files with 128 additions and 48 deletions

View file

@ -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")
)
) {

View file

@ -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();
}

View file

@ -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();

View file

@ -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();
}

View file

@ -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 }
}
}

View file

@ -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());
}

View file

@ -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
)