Added casting controls to connected dialog.

This commit is contained in:
Koen 2023-12-13 15:02:49 +01:00
commit 2ac8e0e621
10 changed files with 306 additions and 58 deletions

View file

@ -11,12 +11,27 @@ import android.view.Gravity
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.widget.* import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.futo.platformplayer.activities.MainActivity import com.futo.platformplayer.activities.MainActivity
import com.futo.platformplayer.api.media.models.comments.IPlatformComment import com.futo.platformplayer.api.media.models.comments.IPlatformComment
import com.futo.platformplayer.casting.StateCasting import com.futo.platformplayer.casting.StateCasting
import com.futo.platformplayer.dialogs.* import com.futo.platformplayer.dialogs.AutoUpdateDialog
import com.futo.platformplayer.dialogs.AutomaticBackupDialog
import com.futo.platformplayer.dialogs.AutomaticRestoreDialog
import com.futo.platformplayer.dialogs.CastingAddDialog
import com.futo.platformplayer.dialogs.CastingHelpDialog
import com.futo.platformplayer.dialogs.ChangelogDialog
import com.futo.platformplayer.dialogs.CommentDialog
import com.futo.platformplayer.dialogs.ConnectCastingDialog
import com.futo.platformplayer.dialogs.ConnectedCastingDialog
import com.futo.platformplayer.dialogs.ImportDialog
import com.futo.platformplayer.dialogs.ImportOptionsDialog
import com.futo.platformplayer.dialogs.MigrateDialog
import com.futo.platformplayer.dialogs.ProgressDialog
import com.futo.platformplayer.engine.exceptions.PluginException import com.futo.platformplayer.engine.exceptions.PluginException
import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.states.StateApp
@ -341,11 +356,17 @@ class UIDialogs {
val d = StateCasting.instance.activeDevice; val d = StateCasting.instance.activeDevice;
if (d != null) { if (d != null) {
val dialog = ConnectedCastingDialog(context); val dialog = ConnectedCastingDialog(context);
if (context is Activity) {
dialog.setOwnerActivity(context)
}
registerDialogOpened(dialog); registerDialogOpened(dialog);
dialog.setOnDismissListener { registerDialogClosed(dialog) }; dialog.setOnDismissListener { registerDialogClosed(dialog) };
dialog.show(); dialog.show();
} else { } else {
val dialog = ConnectCastingDialog(context); val dialog = ConnectCastingDialog(context);
if (context is Activity) {
dialog.setOwnerActivity(context)
}
registerDialogOpened(dialog); registerDialogOpened(dialog);
val c = context val c = context
if (c is Activity) { if (c is Activity) {

View file

@ -56,7 +56,8 @@ class AirPlayCastingDevice : CastingDevice {
Logger.i(FCastCastingDevice.TAG, "Start streaming (streamType: $streamType, contentType: $contentType, contentId: $contentId, resumePosition: $resumePosition, duration: $duration, speed: $speed)"); Logger.i(FCastCastingDevice.TAG, "Start streaming (streamType: $streamType, contentType: $contentType, contentId: $contentId, resumePosition: $resumePosition, duration: $duration, speed: $speed)");
time = resumePosition; setTime(resumePosition);
setDuration(duration);
if (resumePosition > 0.0) { if (resumePosition > 0.0) {
val pos = resumePosition / duration; val pos = resumePosition / duration;
Logger.i(TAG, "resumePosition: $resumePosition, duration: ${duration}, pos: $pos") Logger.i(TAG, "resumePosition: $resumePosition, duration: ${duration}, pos: $pos")
@ -170,8 +171,16 @@ class AirPlayCastingDevice : CastingDevice {
} }
val progress = progressInfo.substring(progressIndex + "position: ".length).toDoubleOrNull() ?: continue; val progress = progressInfo.substring(progressIndex + "position: ".length).toDoubleOrNull() ?: continue;
setTime(progress);
time = progress;
val durationIndex = progressInfo.lowercase().indexOf("duration: ");
if (durationIndex == -1) {
continue;
}
val duration = progressInfo.substring(durationIndex + "duration: ".length).toDoubleOrNull() ?: continue;
setDuration(duration);
} catch (e: Throwable) { } catch (e: Throwable) {
Logger.w(TAG, "Failed to get server info from AirPlay device.", e) Logger.w(TAG, "Failed to get server info from AirPlay device.", e)
} }
@ -196,7 +205,7 @@ class AirPlayCastingDevice : CastingDevice {
} }
override fun changeSpeed(speed: Double) { override fun changeSpeed(speed: Double) {
this.speed = speed setSpeed(speed)
post("rate?value=$speed") post("rate?value=$speed")
} }

View file

@ -1,7 +1,6 @@
package com.futo.platformplayer.casting package com.futo.platformplayer.casting
import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.getNowDiffMiliseconds
import com.futo.platformplayer.models.CastingDeviceInfo import com.futo.platformplayer.models.CastingDeviceInfo
import kotlinx.serialization.KSerializer import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@ -11,7 +10,6 @@ import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.encoding.Encoder
import java.net.InetAddress import java.net.InetAddress
import java.time.OffsetDateTime
enum class CastConnectionState { enum class CastConnectionState {
DISCONNECTED, DISCONNECTED,
@ -59,36 +57,58 @@ abstract class CastingDevice {
onPlayChanged.emit(value); onPlayChanged.emit(value);
} }
}; };
var timeReceivedAt: OffsetDateTime = OffsetDateTime.now()
private set; private var lastTimeChangeTime_ms: Long = 0
var time: Double = 0.0 var time: Double = 0.0
set(value) { private set
val changed = value != field;
field = value; protected fun setTime(value: Double, changeTime_ms: Long = System.currentTimeMillis()) {
if (changed) { if (changeTime_ms > lastTimeChangeTime_ms && value != time) {
timeReceivedAt = OffsetDateTime.now(); time = value
onTimeChanged.emit(value); lastTimeChangeTime_ms = changeTime_ms
} onTimeChanged.emit(value)
}; }
}
private var lastDurationChangeTime_ms: Long = 0
var duration: Double = 0.0
private set
protected fun setDuration(value: Double, changeTime_ms: Long = System.currentTimeMillis()) {
if (changeTime_ms > lastDurationChangeTime_ms && value != duration) {
duration = value
lastDurationChangeTime_ms = changeTime_ms
onDurationChanged.emit(value)
}
}
private var lastVolumeChangeTime_ms: Long = 0
var volume: Double = 1.0 var volume: Double = 1.0
set(value) { private set
val changed = value != field;
field = value; protected fun setVolume(value: Double, changeTime_ms: Long = System.currentTimeMillis()) {
if (changed) { if (changeTime_ms > lastVolumeChangeTime_ms && value != volume) {
onVolumeChanged.emit(value); volume = value
} lastVolumeChangeTime_ms = changeTime_ms
}; onVolumeChanged.emit(value)
}
}
private var lastSpeedChangeTime_ms: Long = 0
var speed: Double = 1.0 var speed: Double = 1.0
set(value) { private set
val changed = value != field;
field = value; protected fun setSpeed(value: Double, changeTime_ms: Long = System.currentTimeMillis()) {
if (changed) { if (changeTime_ms > lastSpeedChangeTime_ms && value != speed) {
onSpeedChanged.emit(value); speed = value
} lastSpeedChangeTime_ms = changeTime_ms
}; onSpeedChanged.emit(value)
}
}
val expectedCurrentTime: Double val expectedCurrentTime: Double
get() { get() {
val diff = timeReceivedAt.getNowDiffMiliseconds().toDouble() / 1000.0; val diff = (System.currentTimeMillis() - lastTimeChangeTime_ms).toDouble() / 1000.0;
return time + diff; return time + diff;
}; };
var connectionState: CastConnectionState = CastConnectionState.DISCONNECTED var connectionState: CastConnectionState = CastConnectionState.DISCONNECTED
@ -104,6 +124,7 @@ abstract class CastingDevice {
var onConnectionStateChanged = Event1<CastConnectionState>(); var onConnectionStateChanged = Event1<CastConnectionState>();
var onPlayChanged = Event1<Boolean>(); var onPlayChanged = Event1<Boolean>();
var onTimeChanged = Event1<Double>(); var onTimeChanged = Event1<Double>();
var onDurationChanged = Event1<Double>();
var onVolumeChanged = Event1<Double>(); var onVolumeChanged = Event1<Double>();
var onSpeedChanged = Event1<Double>(); var onSpeedChanged = Event1<Double>();

View file

@ -74,7 +74,8 @@ class ChromecastCastingDevice : CastingDevice {
Logger.i(TAG, "Start streaming (streamType: $streamType, contentType: $contentType, contentId: $contentId, resumePosition: $resumePosition, duration: $duration, speed: $speed)"); Logger.i(TAG, "Start streaming (streamType: $streamType, contentType: $contentType, contentId: $contentId, resumePosition: $resumePosition, duration: $duration, speed: $speed)");
time = resumePosition; setTime(resumePosition);
setDuration(duration);
_streamType = streamType; _streamType = streamType;
_contentType = contentType; _contentType = contentType;
_contentId = contentId; _contentId = contentId;
@ -136,7 +137,7 @@ class ChromecastCastingDevice : CastingDevice {
return; return;
} }
this.volume = volume setVolume(volume)
val setVolumeObject = JSONObject(); val setVolumeObject = JSONObject();
setVolumeObject.put("type", "SET_VOLUME"); setVolumeObject.put("type", "SET_VOLUME");
@ -490,7 +491,7 @@ class ChromecastCastingDevice : CastingDevice {
if (!sessionIsRunning) { if (!sessionIsRunning) {
_sessionId = null; _sessionId = null;
_mediaSessionId = null; _mediaSessionId = null;
time = 0.0; setTime(0.0);
_transportId = null; _transportId = null;
Logger.w(TAG, "Session not found."); Logger.w(TAG, "Session not found.");
@ -510,7 +511,7 @@ class ChromecastCastingDevice : CastingDevice {
val volumeLevel = volume.getString("level").toDouble(); val volumeLevel = volume.getString("level").toDouble();
val volumeMuted = volume.getBoolean("muted"); val volumeMuted = volume.getBoolean("muted");
//val volumeStepInterval = volume.getString("stepInterval").toFloat(); //val volumeStepInterval = volume.getString("stepInterval").toFloat();
this.volume = if (volumeMuted) 0.0 else volumeLevel; setVolume(if (volumeMuted) 0.0 else volumeLevel);
Logger.i(TAG, "Status update received volume (level: $volumeLevel, muted: $volumeMuted)"); Logger.i(TAG, "Status update received volume (level: $volumeLevel, muted: $volumeMuted)");
} else if (type == "MEDIA_STATUS") { } else if (type == "MEDIA_STATUS") {
@ -521,10 +522,16 @@ class ChromecastCastingDevice : CastingDevice {
val playerState = status.getString("playerState"); val playerState = status.getString("playerState");
val currentTime = status.getDouble("currentTime"); val currentTime = status.getDouble("currentTime");
if (status.has("media")) {
val media = status.getJSONObject("media")
if (media.has("duration")) {
setDuration(media.getDouble("duration"))
}
}
isPlaying = playerState == "PLAYING"; isPlaying = playerState == "PLAYING";
if (isPlaying) { if (isPlaying) {
time = currentTime; setTime(currentTime);
} }
val playbackRate = status.getInt("playbackRate"); val playbackRate = status.getInt("playbackRate");

View file

@ -92,7 +92,8 @@ class FCastCastingDevice : CastingDevice {
Logger.i(TAG, "Start streaming (streamType: $streamType, contentType: $contentType, contentId: $contentId, resumePosition: $resumePosition, duration: $duration, speed: $speed)"); Logger.i(TAG, "Start streaming (streamType: $streamType, contentType: $contentType, contentId: $contentId, resumePosition: $resumePosition, duration: $duration, speed: $speed)");
time = resumePosition; setTime(resumePosition);
setDuration(duration);
sendMessage(Opcode.PLAY, FCastPlayMessage( sendMessage(Opcode.PLAY, FCastPlayMessage(
container = contentType, container = contentType,
url = contentId, url = contentId,
@ -100,7 +101,7 @@ class FCastCastingDevice : CastingDevice {
speed = speed speed = speed
)); ));
this.speed = speed ?: 1.0 setSpeed(speed ?: 1.0);
} }
override fun loadContent(contentType: String, content: String, resumePosition: Double, duration: Double, speed: Double?) { override fun loadContent(contentType: String, content: String, resumePosition: Double, duration: Double, speed: Double?) {
@ -115,7 +116,8 @@ class FCastCastingDevice : CastingDevice {
Logger.i(TAG, "Start streaming content (contentType: $contentType, resumePosition: $resumePosition, duration: $duration, speed: $speed)"); Logger.i(TAG, "Start streaming content (contentType: $contentType, resumePosition: $resumePosition, duration: $duration, speed: $speed)");
time = resumePosition; setTime(resumePosition);
setDuration(duration);
sendMessage(Opcode.PLAY, FCastPlayMessage( sendMessage(Opcode.PLAY, FCastPlayMessage(
container = contentType, container = contentType,
content = content, content = content,
@ -123,7 +125,7 @@ class FCastCastingDevice : CastingDevice {
speed = speed speed = speed
)); ));
this.speed = speed ?: 1.0 setSpeed(speed ?: 1.0);
} }
override fun changeVolume(volume: Double) { override fun changeVolume(volume: Double) {
@ -131,7 +133,7 @@ class FCastCastingDevice : CastingDevice {
return; return;
} }
this.volume = volume setVolume(volume);
sendMessage(Opcode.SET_VOLUME, FCastSetVolumeMessage(volume)) sendMessage(Opcode.SET_VOLUME, FCastSetVolumeMessage(volume))
} }
@ -140,7 +142,7 @@ class FCastCastingDevice : CastingDevice {
return; return;
} }
this.speed = speed setSpeed(speed);
sendMessage(Opcode.SET_SPEED, FCastSetSpeedMessage(speed)) sendMessage(Opcode.SET_SPEED, FCastSetSpeedMessage(speed))
} }
@ -330,7 +332,8 @@ class FCastCastingDevice : CastingDevice {
} }
val playbackUpdate = FCastCastingDevice.json.decodeFromString<FCastPlaybackUpdateMessage>(json); val playbackUpdate = FCastCastingDevice.json.decodeFromString<FCastPlaybackUpdateMessage>(json);
time = playbackUpdate.time; setTime(playbackUpdate.time, playbackUpdate.generationTime);
setDuration(playbackUpdate.duration, playbackUpdate.generationTime);
isPlaying = when (playbackUpdate.state) { isPlaying = when (playbackUpdate.state) {
1 -> true 1 -> true
else -> false else -> false
@ -343,7 +346,7 @@ class FCastCastingDevice : CastingDevice {
} }
val volumeUpdate = FCastCastingDevice.json.decodeFromString<FCastVolumeUpdateMessage>(json); val volumeUpdate = FCastCastingDevice.json.decodeFromString<FCastVolumeUpdateMessage>(json);
volume = volumeUpdate.volume; setVolume(volumeUpdate.volume, volumeUpdate.generationTime);
} }
Opcode.PLAYBACK_ERROR -> { Opcode.PLAYBACK_ERROR -> {
if (json == null) { if (json == null) {

View file

@ -66,6 +66,8 @@ class StateCasting {
val onActiveDeviceConnectionStateChanged = Event2<CastingDevice, CastConnectionState>(); val onActiveDeviceConnectionStateChanged = Event2<CastingDevice, CastConnectionState>();
val onActiveDevicePlayChanged = Event1<Boolean>(); val onActiveDevicePlayChanged = Event1<Boolean>();
val onActiveDeviceTimeChanged = Event1<Double>(); val onActiveDeviceTimeChanged = Event1<Double>();
val onActiveDeviceDurationChanged = Event1<Double>();
val onActiveDeviceVolumeChanged = Event1<Double>();
var activeDevice: CastingDevice? = null; var activeDevice: CastingDevice? = null;
private val _client = ManagedHttpClient(); private val _client = ManagedHttpClient();
var _resumeCastingDevice: CastingDeviceInfo? = null; var _resumeCastingDevice: CastingDeviceInfo? = null;
@ -297,9 +299,11 @@ class StateCasting {
val ad = activeDevice; val ad = activeDevice;
if (ad != null) { if (ad != null) {
Logger.i(TAG, "Stopping previous device because a new one is being connected.") Logger.i(TAG, "Stopping previous device because a new one is being connected.")
ad.onPlayChanged.clear(); device.onConnectionStateChanged.clear();
ad.onTimeChanged.clear(); device.onPlayChanged.clear();
ad.onConnectionStateChanged.clear(); device.onTimeChanged.clear();
device.onVolumeChanged.clear();
device.onDurationChanged.clear();
ad.stop(); ad.stop();
} }
@ -309,9 +313,11 @@ class StateCasting {
if (castConnectionState == CastConnectionState.DISCONNECTED) { if (castConnectionState == CastConnectionState.DISCONNECTED) {
Logger.i(TAG, "Clearing events: $castConnectionState"); Logger.i(TAG, "Clearing events: $castConnectionState");
device.onConnectionStateChanged.clear();
device.onPlayChanged.clear(); device.onPlayChanged.clear();
device.onTimeChanged.clear(); device.onTimeChanged.clear();
device.onConnectionStateChanged.clear(); device.onVolumeChanged.clear();
device.onDurationChanged.clear();
activeDevice = null; activeDevice = null;
} }
@ -331,6 +337,12 @@ class StateCasting {
device.onPlayChanged.subscribe { device.onPlayChanged.subscribe {
invokeInMainScopeIfRequired { onActiveDevicePlayChanged.emit(it) }; invokeInMainScopeIfRequired { onActiveDevicePlayChanged.emit(it) };
} }
device.onDurationChanged.subscribe {
invokeInMainScopeIfRequired { onActiveDeviceDurationChanged.emit(it) };
};
device.onVolumeChanged.subscribe {
invokeInMainScopeIfRequired { onActiveDeviceVolumeChanged.emit(it) };
};
device.onTimeChanged.subscribe { device.onTimeChanged.subscribe {
invokeInMainScopeIfRequired { onActiveDeviceTimeChanged.emit(it) }; invokeInMainScopeIfRequired { onActiveDeviceTimeChanged.emit(it) };
}; };
@ -345,6 +357,8 @@ class StateCasting {
device.onConnectionStateChanged.clear(); device.onConnectionStateChanged.clear();
device.onPlayChanged.clear(); device.onPlayChanged.clear();
device.onTimeChanged.clear(); device.onTimeChanged.clear();
device.onVolumeChanged.clear();
device.onDurationChanged.clear();
return; return;
} }

View file

@ -7,12 +7,20 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.widget.Button import android.widget.Button
import android.widget.ImageButton
import android.widget.ImageView import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.R import com.futo.platformplayer.R
import com.futo.platformplayer.casting.* import com.futo.platformplayer.activities.MainActivity
import com.futo.platformplayer.casting.AirPlayCastingDevice
import com.futo.platformplayer.casting.CastConnectionState
import com.futo.platformplayer.casting.CastingDevice
import com.futo.platformplayer.casting.ChromecastCastingDevice
import com.futo.platformplayer.casting.FCastCastingDevice
import com.futo.platformplayer.casting.StateCasting
import com.futo.platformplayer.fragment.mainactivity.main.VideoDetailFragment
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.states.StateApp
import com.google.android.material.slider.Slider import com.google.android.material.slider.Slider
import com.google.android.material.slider.Slider.OnChangeListener import com.google.android.material.slider.Slider.OnChangeListener
@ -27,8 +35,16 @@ class ConnectedCastingDialog(context: Context?) : AlertDialog(context) {
private lateinit var _textType: TextView; private lateinit var _textType: TextView;
private lateinit var _buttonDisconnect: LinearLayout; private lateinit var _buttonDisconnect: LinearLayout;
private lateinit var _sliderVolume: Slider; private lateinit var _sliderVolume: Slider;
private lateinit var _sliderPosition: Slider;
private lateinit var _layoutVolumeAdjustable: LinearLayout; private lateinit var _layoutVolumeAdjustable: LinearLayout;
private lateinit var _layoutVolumeFixed: LinearLayout; private lateinit var _layoutVolumeFixed: LinearLayout;
private lateinit var _buttonPrevious: ImageButton;
private lateinit var _buttonPlay: ImageButton;
private lateinit var _buttonPause: ImageButton;
private lateinit var _buttonStop: ImageButton;
private lateinit var _buttonNext: ImageButton;
private var _device: CastingDevice? = null; private var _device: CastingDevice? = null;
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -42,17 +58,61 @@ class ConnectedCastingDialog(context: Context?) : AlertDialog(context) {
_textType = findViewById(R.id.text_type); _textType = findViewById(R.id.text_type);
_buttonDisconnect = findViewById(R.id.button_disconnect); _buttonDisconnect = findViewById(R.id.button_disconnect);
_sliderVolume = findViewById(R.id.slider_volume); _sliderVolume = findViewById(R.id.slider_volume);
_sliderPosition = findViewById(R.id.slider_position);
_layoutVolumeAdjustable = findViewById(R.id.layout_volume_adjustable); _layoutVolumeAdjustable = findViewById(R.id.layout_volume_adjustable);
_layoutVolumeFixed = findViewById(R.id.layout_volume_fixed); _layoutVolumeFixed = findViewById(R.id.layout_volume_fixed);
_buttonPrevious = findViewById(R.id.button_previous);
_buttonPrevious.setOnClickListener {
(ownerActivity as MainActivity?)?.getFragment<VideoDetailFragment>()?.previousVideo()
}
_buttonPlay = findViewById(R.id.button_play);
_buttonPlay.setOnClickListener {
StateCasting.instance.activeDevice?.resumeVideo()
}
_buttonPause = findViewById(R.id.button_pause);
_buttonPause.setOnClickListener {
StateCasting.instance.activeDevice?.pauseVideo()
}
_buttonStop = findViewById(R.id.button_stop);
_buttonStop.setOnClickListener {
(ownerActivity as MainActivity?)?.getFragment<VideoDetailFragment>()?.closeVideoDetails()
StateCasting.instance.activeDevice?.stopVideo()
}
_buttonNext = findViewById(R.id.button_next);
_buttonNext.setOnClickListener {
(ownerActivity as MainActivity?)?.getFragment<VideoDetailFragment>()?.nextVideo()
}
_buttonClose.setOnClickListener { dismiss(); }; _buttonClose.setOnClickListener { dismiss(); };
_buttonDisconnect.setOnClickListener { _buttonDisconnect.setOnClickListener {
StateCasting.instance.activeDevice?.stopCasting(); StateCasting.instance.activeDevice?.stopCasting();
dismiss(); dismiss();
}; };
_sliderPosition.addOnChangeListener(OnChangeListener { _, value, fromUser ->
if (!fromUser) {
return@OnChangeListener
}
val activeDevice = StateCasting.instance.activeDevice ?: return@OnChangeListener;
try {
activeDevice.seekVideo(value.toDouble());
} catch (e: Throwable) {
Logger.e(TAG, "Failed to change volume.", e);
}
});
//TODO: Check if volume slider is properly hidden in all cases //TODO: Check if volume slider is properly hidden in all cases
_sliderVolume.addOnChangeListener(OnChangeListener { _, value, _ -> _sliderVolume.addOnChangeListener(OnChangeListener { _, value, fromUser ->
if (!fromUser) {
return@OnChangeListener
}
val activeDevice = StateCasting.instance.activeDevice ?: return@OnChangeListener; val activeDevice = StateCasting.instance.activeDevice ?: return@OnChangeListener;
if (activeDevice.canSetVolume) { if (activeDevice.canSetVolume) {
try { try {
@ -71,11 +131,21 @@ class ConnectedCastingDialog(context: Context?) : AlertDialog(context) {
super.show(); super.show();
Logger.i(TAG, "Dialog shown."); Logger.i(TAG, "Dialog shown.");
_device?.onVolumeChanged?.remove(this); StateCasting.instance.onActiveDeviceVolumeChanged.remove(this);
_device?.onVolumeChanged?.subscribe { StateCasting.instance.onActiveDeviceVolumeChanged.subscribe {
_sliderVolume.value = it.toFloat(); _sliderVolume.value = it.toFloat();
}; };
StateCasting.instance.onActiveDeviceTimeChanged.remove(this);
StateCasting.instance.onActiveDeviceTimeChanged.subscribe {
_sliderPosition.value = it.toFloat();
};
StateCasting.instance.onActiveDeviceDurationChanged.remove(this);
StateCasting.instance.onActiveDeviceDurationChanged.subscribe {
_sliderPosition.valueTo = it.toFloat();
};
_device = StateCasting.instance.activeDevice; _device = StateCasting.instance.activeDevice;
val d = _device; val d = _device;
val isConnected = d != null && d.connectionState == CastConnectionState.CONNECTED; val isConnected = d != null && d.connectionState == CastConnectionState.CONNECTED;
@ -89,7 +159,9 @@ class ConnectedCastingDialog(context: Context?) : AlertDialog(context) {
override fun dismiss() { override fun dismiss() {
super.dismiss(); super.dismiss();
_device?.onVolumeChanged?.remove(this); StateCasting.instance.onActiveDeviceVolumeChanged.remove(this);
StateCasting.instance.onActiveDeviceDurationChanged.remove(this);
StateCasting.instance.onActiveDeviceTimeChanged.remove(this);
_device = null; _device = null;
StateCasting.instance.onActiveDeviceConnectionStateChanged.remove(this); StateCasting.instance.onActiveDeviceConnectionStateChanged.remove(this);
} }
@ -110,6 +182,9 @@ class ConnectedCastingDialog(context: Context?) : AlertDialog(context) {
_textName.text = d.name; _textName.text = d.name;
_sliderVolume.value = d.volume.toFloat(); _sliderVolume.value = d.volume.toFloat();
_sliderPosition.valueFrom = 0.0f;
_sliderPosition.valueTo = d.duration.toFloat();
_sliderPosition.value = d.time.toFloat();
if (d.canSetVolume) { if (d.canSetVolume) {
_layoutVolumeAdjustable.visibility = View.VISIBLE; _layoutVolumeAdjustable.visibility = View.VISIBLE;

View file

@ -67,6 +67,14 @@ class VideoDetailFragment : MainFragment {
constructor() : super() { constructor() : super() {
} }
fun nextVideo() {
_viewDetail?.nextVideo(true, true, true);
}
fun previousVideo() {
_viewDetail?.prevVideo(true);
}
override fun onShownWithView(parameter: Any?, isBack: Boolean) { override fun onShownWithView(parameter: Any?, isBack: Boolean) {
super.onShownWithView(parameter, isBack); super.onShownWithView(parameter, isBack);
Logger.i(TAG, "onShownWithView parameter=$parameter") Logger.i(TAG, "onShownWithView parameter=$parameter")

View file

@ -51,7 +51,8 @@
android:layout_height="35dp" android:layout_height="35dp"
android:layout_marginStart="20dp" android:layout_marginStart="20dp"
android:layout_marginEnd="20dp" android:layout_marginEnd="20dp"
android:clickable="true"> android:clickable="true"
android:layout_marginTop="8dp">
<ImageView <ImageView
android:id="@+id/image_device" android:id="@+id/image_device"
@ -125,11 +126,12 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
android:gravity="center_vertical" android:gravity="center_vertical"
android:layout_marginStart="20dp"> android:layout_marginStart="20dp"
android:layout_marginTop="8dp">
<TextView <TextView
android:id="@+id/text_volume" android:id="@+id/text_volume"
android:layout_width="wrap_content" android:layout_width="60dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/volume" android:text="@string/volume"
android:textSize="14dp" android:textSize="14dp"
@ -150,6 +152,93 @@
android:layout_marginEnd="15dp" /> android:layout_marginEnd="15dp" />
</LinearLayout> </LinearLayout>
<LinearLayout
android:id="@+id/layout_position_adjustable"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginStart="20dp"
android:layout_marginTop="8dp">
<TextView
android:id="@+id/text_position"
android:layout_width="60dp"
android:layout_height="wrap_content"
android:text="@string/position"
android:textSize="14dp"
android:textColor="@color/white"
android:fontFamily="@font/inter_regular" />
<com.google.android.material.slider.Slider
android:id="@+id/slider_position"
android:layout_width="match_parent"
android:layout_height="20dp"
app:thumbColor="@color/colorPrimary"
app:trackColorActive="@color/colorPrimary"
app:trackColorInactive="@color/gray_67"
android:value="0.2"
android:valueFrom="0"
android:valueTo="1"
android:layout_marginStart="15dp"
android:layout_marginEnd="15dp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:divider="@drawable/divider_transparent_8dp"
android:showDividers="middle"
android:gravity="center"
android:layout_marginTop="8dp">
<ImageButton
android:id="@id/button_previous"
android:layout_width="60dp"
android:layout_height="60dp"
android:scaleType="centerCrop"
android:clickable="true"
android:padding="10dp"
app:srcCompat="@drawable/ic_skip_previous" />
<ImageButton
android:id="@+id/button_play"
android:layout_width="60dp"
android:layout_height="60dp"
android:padding="20dp"
android:scaleType="fitCenter"
android:clickable="true"
app:srcCompat="@drawable/ic_play_white_nopad" />
<ImageButton
android:id="@+id/button_pause"
android:layout_width="60dp"
android:layout_height="60dp"
android:padding="10dp"
android:scaleType="fitCenter"
android:clickable="true"
app:srcCompat="@drawable/ic_pause_white" />
<ImageButton
android:id="@+id/button_stop"
android:layout_width="60dp"
android:layout_height="60dp"
android:scaleType="fitCenter"
android:padding="5dp"
android:clickable="true"
app:srcCompat="@drawable/ic_stop_notif" />
<ImageButton
android:id="@id/button_next"
android:layout_width="60dp"
android:layout_height="60dp"
android:clickable="true"
android:scaleType="centerCrop"
android:padding="10dp"
app:srcCompat="@drawable/ic_skip_next" />
</LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/layout_volume_fixed" android:id="@+id/layout_volume_fixed"
android:layout_width="match_parent" android:layout_width="match_parent"

View file

@ -715,6 +715,7 @@
<string name="login_to_view_your_comments">Login to view your comments</string> <string name="login_to_view_your_comments">Login to view your comments</string>
<string name="polycentric_is_disabled">Polycentric is disabled</string> <string name="polycentric_is_disabled">Polycentric is disabled</string>
<string name="play_pause">Play Pause</string> <string name="play_pause">Play Pause</string>
<string name="position">Position</string>
<string-array name="home_screen_array"> <string-array name="home_screen_array">
<item>Recommendations</item> <item>Recommendations</item>
<item>Subscriptions</item> <item>Subscriptions</item>