mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2025-08-13 11:39:49 +00:00
Added casting controls to connected dialog.
This commit is contained in:
parent
0432f06eb3
commit
2ac8e0e621
10 changed files with 306 additions and 58 deletions
|
@ -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) {
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>();
|
||||||
|
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue