mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2026-05-16 13:02:39 +02:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dccdf72c73 | |||
| ca15983a72 | |||
| 4b6a2c9829 | |||
| 1755d03a6b |
@@ -216,9 +216,14 @@ private fun ByteArray.toInetAddress(): InetAddress {
|
||||
return InetAddress.getByAddress(this);
|
||||
}
|
||||
|
||||
fun getConnectedSocket(addresses: List<InetAddress>, port: Int): Socket? {
|
||||
fun getConnectedSocket(attemptAddresses: List<InetAddress>, port: Int): Socket? {
|
||||
val timeout = 2000
|
||||
|
||||
|
||||
val addresses = if(!Settings.instance.casting.allowIpv6) attemptAddresses.filterIsInstance<Inet4Address>() else attemptAddresses;
|
||||
if(addresses.isEmpty())
|
||||
throw IllegalStateException("No valid addresses found (ipv6: ${(if(Settings.instance.casting.allowIpv6) "enabled" else "disabled")})");
|
||||
|
||||
if (addresses.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -583,10 +583,15 @@ class Settings : FragmentedStorageFileJson() {
|
||||
@Serializable(with = FlexibleBooleanSerializer::class)
|
||||
var keepScreenOn: Boolean = true;
|
||||
|
||||
@FormField(R.string.always_proxy_requests, FieldForm.TOGGLE, R.string.always_proxy_requests_description, 1)
|
||||
@FormField(R.string.always_proxy_requests, FieldForm.TOGGLE, R.string.always_proxy_requests_description, 3)
|
||||
@Serializable(with = FlexibleBooleanSerializer::class)
|
||||
var alwaysProxyRequests: Boolean = false;
|
||||
|
||||
|
||||
@FormField(R.string.allow_ipv6, FieldForm.TOGGLE, R.string.allow_ipv6_description, 4)
|
||||
@Serializable(with = FlexibleBooleanSerializer::class)
|
||||
var allowIpv6: Boolean = false;
|
||||
|
||||
/*TODO: Should we have a different casting quality?
|
||||
@FormField("Preferred Casting Quality", FieldForm.DROPDOWN, "", 3)
|
||||
@DropdownFieldOptionsId(R.array.preferred_quality_array)
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.app.AlertDialog
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.Animatable
|
||||
import android.net.Uri
|
||||
import android.text.Layout
|
||||
import android.text.method.ScrollingMovementMethod
|
||||
@@ -199,16 +200,21 @@ class UIDialogs {
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
fun showDialog(context: Context, icon: Int, text: String, textDetails: String? = null, code: String? = null, defaultCloseAction: Int, vararg actions: Action) {
|
||||
fun showDialog(context: Context, icon: Int, text: String, textDetails: String? = null, code: String? = null, defaultCloseAction: Int, vararg actions: Action): AlertDialog {
|
||||
return showDialog(context, icon, false, text, textDetails, code, defaultCloseAction, *actions);
|
||||
}
|
||||
fun showDialog(context: Context, icon: Int, animated: Boolean, text: String, textDetails: String? = null, code: String? = null, defaultCloseAction: Int, vararg actions: Action): AlertDialog {
|
||||
val builder = AlertDialog.Builder(context);
|
||||
val view = LayoutInflater.from(context).inflate(R.layout.dialog_multi_button, null);
|
||||
builder.setView(view);
|
||||
|
||||
builder.setCancelable(defaultCloseAction > -2);
|
||||
val dialog = builder.create();
|
||||
registerDialogOpened(dialog);
|
||||
|
||||
view.findViewById<ImageView>(R.id.dialog_icon).apply {
|
||||
this.setImageResource(icon);
|
||||
if(animated)
|
||||
this.drawable.assume<Animatable, Unit> { it.start() };
|
||||
}
|
||||
view.findViewById<TextView>(R.id.dialog_text).apply {
|
||||
this.text = text;
|
||||
@@ -275,6 +281,7 @@ class UIDialogs {
|
||||
registerDialogClosed(dialog);
|
||||
}
|
||||
dialog.show();
|
||||
return dialog;
|
||||
}
|
||||
|
||||
fun showGeneralErrorDialog(context: Context, msg: String, ex: Throwable? = null, button: String = "Ok", onOk: (()->Unit)? = null) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.futo.platformplayer.casting
|
||||
import android.os.Looper
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import com.futo.platformplayer.Settings
|
||||
import com.futo.platformplayer.UIDialogs
|
||||
import com.futo.platformplayer.casting.models.FCastDecryptedMessage
|
||||
import com.futo.platformplayer.casting.models.FCastEncryptedMessage
|
||||
@@ -32,6 +33,7 @@ import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.math.BigInteger
|
||||
import java.net.Inet4Address
|
||||
import java.net.InetAddress
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.Socket
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.futo.platformplayer.casting
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
@@ -9,6 +10,7 @@ import android.util.Log
|
||||
import android.util.Xml
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import com.futo.platformplayer.R
|
||||
import com.futo.platformplayer.Settings
|
||||
import com.futo.platformplayer.UIDialogs
|
||||
import com.futo.platformplayer.api.http.ManagedHttpClient
|
||||
@@ -239,6 +241,9 @@ class StateCasting {
|
||||
Logger.i(TAG, "CastingService stopped.")
|
||||
}
|
||||
|
||||
private val _castingDialogLock = Any();
|
||||
private var _currentDialog: AlertDialog? = null;
|
||||
|
||||
@Synchronized
|
||||
fun connectDevice(device: CastingDevice) {
|
||||
if (activeDevice == device)
|
||||
@@ -272,10 +277,41 @@ class StateCasting {
|
||||
invokeInMainScopeIfRequired {
|
||||
StateApp.withContext(false) { context ->
|
||||
context.let {
|
||||
Logger.i(TAG, "Casting state changed to ${castConnectionState}");
|
||||
when (castConnectionState) {
|
||||
CastConnectionState.CONNECTED -> UIDialogs.toast(it, "Connected to device")
|
||||
CastConnectionState.CONNECTING -> UIDialogs.toast(it, "Connecting to device...")
|
||||
CastConnectionState.DISCONNECTED -> UIDialogs.toast(it, "Disconnected from device")
|
||||
CastConnectionState.CONNECTED -> {
|
||||
Logger.i(TAG, "Casting connected to [${device.name}]");
|
||||
UIDialogs.appToast("Connected to device")
|
||||
synchronized(_castingDialogLock) {
|
||||
if(_currentDialog != null) {
|
||||
_currentDialog?.hide();
|
||||
_currentDialog = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
CastConnectionState.CONNECTING -> {
|
||||
Logger.i(TAG, "Casting connecting to [${device.name}]");
|
||||
UIDialogs.toast(it, "Connecting to device...")
|
||||
synchronized(_castingDialogLock) {
|
||||
if(_currentDialog == null) {
|
||||
_currentDialog = UIDialogs.showDialog(context, R.drawable.ic_loader_animated, true,
|
||||
"Connecting to [${device.name}]",
|
||||
"Make sure you are on the same network\n\nVPNs and guest networks can cause issues", null, -2,
|
||||
UIDialogs.Action("Disconnect", {
|
||||
device.stop();
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
CastConnectionState.DISCONNECTED -> {
|
||||
UIDialogs.toast(it, "Disconnected from device")
|
||||
synchronized(_castingDialogLock) {
|
||||
if(_currentDialog != null) {
|
||||
_currentDialog?.hide();
|
||||
_currentDialog = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -73,11 +73,11 @@ class ConnectCastingDialog(context: Context?) : AlertDialog(context) {
|
||||
};
|
||||
_rememberedAdapter.onConnect.subscribe { _ ->
|
||||
dismiss()
|
||||
UIDialogs.showCastingDialog(context)
|
||||
//UIDialogs.showCastingDialog(context)
|
||||
}
|
||||
_adapter.onConnect.subscribe { _ ->
|
||||
dismiss()
|
||||
UIDialogs.showCastingDialog(context)
|
||||
//UIDialogs.showCastingDialog(context)
|
||||
}
|
||||
_recyclerRememberedDevices.adapter = _rememberedAdapter;
|
||||
_recyclerRememberedDevices.layoutManager = LinearLayoutManager(context);
|
||||
|
||||
+17
-8
@@ -1,9 +1,11 @@
|
||||
package com.futo.platformplayer.fragment.mainactivity.main
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.Animatable
|
||||
import android.util.TypedValue
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageButton
|
||||
import android.widget.ImageView
|
||||
@@ -48,6 +50,11 @@ abstract class VideoListEditorView : LinearLayout {
|
||||
private var _loadedVideos: List<IPlatformVideo>? = null;
|
||||
private var _loadedVideosCanEdit: Boolean = false;
|
||||
|
||||
fun hideSearchKeyboard() {
|
||||
(context?.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager)?.hideSoftInputFromWindow(_search.textSearch.windowToken, 0)
|
||||
_search.textSearch.clearFocus();
|
||||
}
|
||||
|
||||
constructor(inflater: LayoutInflater) : super(inflater.context) {
|
||||
inflater.inflate(R.layout.fragment_video_list_editor, this);
|
||||
|
||||
@@ -79,6 +86,7 @@ abstract class VideoListEditorView : LinearLayout {
|
||||
_search.textSearch.text = "";
|
||||
updateVideoFilters();
|
||||
_buttonSearch.setImageResource(R.drawable.ic_search);
|
||||
hideSearchKeyboard();
|
||||
}
|
||||
else {
|
||||
_search.visibility = View.VISIBLE;
|
||||
@@ -89,23 +97,23 @@ abstract class VideoListEditorView : LinearLayout {
|
||||
_buttonShare = findViewById(R.id.button_share);
|
||||
val onShare = _onShare;
|
||||
if(onShare != null) {
|
||||
_buttonShare.setOnClickListener { onShare.invoke() };
|
||||
_buttonShare.setOnClickListener { hideSearchKeyboard(); onShare.invoke() };
|
||||
_buttonShare.visibility = View.VISIBLE;
|
||||
}
|
||||
else
|
||||
_buttonShare.visibility = View.GONE;
|
||||
|
||||
buttonPlayAll.setOnClickListener { onPlayAllClick(); };
|
||||
buttonShuffle.setOnClickListener { onShuffleClick(); };
|
||||
buttonPlayAll.setOnClickListener { hideSearchKeyboard();onPlayAllClick(); hideSearchKeyboard(); };
|
||||
buttonShuffle.setOnClickListener { hideSearchKeyboard();onShuffleClick(); hideSearchKeyboard(); };
|
||||
|
||||
_buttonEdit.setOnClickListener { onEditClick(); };
|
||||
_buttonEdit.setOnClickListener { hideSearchKeyboard(); onEditClick(); };
|
||||
setButtonExportVisible(false);
|
||||
setButtonDownloadVisible(canEdit());
|
||||
|
||||
videoListEditorView.onVideoOrderChanged.subscribe(::onVideoOrderChanged);
|
||||
videoListEditorView.onVideoRemoved.subscribe(::onVideoRemoved);
|
||||
videoListEditorView.onVideoOptions.subscribe(::onVideoOptions);
|
||||
videoListEditorView.onVideoClicked.subscribe(::onVideoClicked);
|
||||
videoListEditorView.onVideoClicked.subscribe { hideSearchKeyboard(); onVideoClicked(it)};
|
||||
|
||||
_videoListEditorView = videoListEditorView;
|
||||
}
|
||||
@@ -113,6 +121,7 @@ abstract class VideoListEditorView : LinearLayout {
|
||||
fun setOnShare(onShare: (()-> Unit)? = null) {
|
||||
_onShare = onShare;
|
||||
_buttonShare.setOnClickListener {
|
||||
hideSearchKeyboard();
|
||||
onShare?.invoke();
|
||||
};
|
||||
_buttonShare.visibility = View.VISIBLE;
|
||||
@@ -145,7 +154,7 @@ abstract class VideoListEditorView : LinearLayout {
|
||||
setButtonExportVisible(false);
|
||||
_buttonDownload.setImageResource(R.drawable.ic_loader_animated);
|
||||
_buttonDownload.drawable.assume<Animatable, Unit> { it.start() };
|
||||
_buttonDownload.setOnClickListener {
|
||||
_buttonDownload.setOnClickListener { hideSearchKeyboard();
|
||||
UIDialogs.showConfirmationDialog(context, context.getString(R.string.are_you_sure_you_want_to_delete_the_downloaded_videos), {
|
||||
StateDownloads.instance.deleteCachedPlaylist(playlistId);
|
||||
});
|
||||
@@ -154,7 +163,7 @@ abstract class VideoListEditorView : LinearLayout {
|
||||
else if(isDownloaded) {
|
||||
setButtonExportVisible(true)
|
||||
_buttonDownload.setImageResource(R.drawable.ic_download_off);
|
||||
_buttonDownload.setOnClickListener {
|
||||
_buttonDownload.setOnClickListener { hideSearchKeyboard();
|
||||
UIDialogs.showConfirmationDialog(context, context.getString(R.string.are_you_sure_you_want_to_delete_the_downloaded_videos), {
|
||||
StateDownloads.instance.deleteCachedPlaylist(playlistId);
|
||||
});
|
||||
@@ -163,7 +172,7 @@ abstract class VideoListEditorView : LinearLayout {
|
||||
else {
|
||||
setButtonExportVisible(false);
|
||||
_buttonDownload.setImageResource(R.drawable.ic_download);
|
||||
_buttonDownload.setOnClickListener {
|
||||
_buttonDownload.setOnClickListener { hideSearchKeyboard();
|
||||
onDownload();
|
||||
//UISlideOverlays.showDownloadPlaylistOverlay(playlist, overlayContainer);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.futo.platformplayer.views
|
||||
|
||||
import android.content.Context
|
||||
import android.text.TextWatcher
|
||||
import android.util.AttributeSet
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageButton
|
||||
import android.widget.ImageView
|
||||
@@ -18,6 +20,9 @@ class SearchView : FrameLayout {
|
||||
val buttonClear: ImageButton;
|
||||
|
||||
var onSearchChanged = Event1<String>();
|
||||
var onEnter = Event1<String>();
|
||||
|
||||
val text: String get() = textSearch.text.toString();
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) {
|
||||
inflate(context, R.layout.view_search_bar, this);
|
||||
|
||||
@@ -9,6 +9,7 @@ import android.view.View
|
||||
import android.widget.ImageView
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.futo.platformplayer.R
|
||||
import com.futo.platformplayer.constructs.Event1
|
||||
import com.futo.platformplayer.getDataLinkFromUrl
|
||||
@@ -81,12 +82,14 @@ class CreatorThumbnail : ConstraintLayout {
|
||||
Glide.with(_imageChannelThumbnail)
|
||||
.load(url)
|
||||
.placeholder(R.drawable.placeholder_channel_thumbnail)
|
||||
.diskCacheStrategy(DiskCacheStrategy.DATA)
|
||||
.crossfade()
|
||||
.into(_imageChannelThumbnail);
|
||||
} else {
|
||||
Glide.with(_imageChannelThumbnail)
|
||||
.load(url)
|
||||
.placeholder(R.drawable.placeholder_channel_thumbnail)
|
||||
.diskCacheStrategy(DiskCacheStrategy.DATA)
|
||||
.into(_imageChannelThumbnail);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,6 +144,9 @@
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginLeft="15dp"
|
||||
android:layout_marginRight="15dp"
|
||||
android:inputType="text"
|
||||
android:imeOptions="actionDone"
|
||||
android:singleLine="true"
|
||||
android:background="@drawable/background_button_round"
|
||||
android:hint="Search.." />
|
||||
|
||||
|
||||
@@ -72,6 +72,8 @@
|
||||
<string name="keep_screen_on_while_casting">Keep screen on while casting</string>
|
||||
<string name="always_proxy_requests">Always proxy requests</string>
|
||||
<string name="always_proxy_requests_description">Always proxy requests when casting data through the device.</string>
|
||||
<string name="allow_ipv6">Allow IPV6</string>
|
||||
<string name="allow_ipv6_description">If casting over IPV6 is allowed, can cause issues on some networks</string>
|
||||
<string name="discover">Discover</string>
|
||||
<string name="find_new_video_sources_to_add">Find new video sources to add</string>
|
||||
<string name="these_sources_have_been_disabled">These sources have been disabled</string>
|
||||
|
||||
Reference in New Issue
Block a user