mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2025-08-22 18:31:23 +00:00
Merge
This commit is contained in:
commit
de39451f67
14 changed files with 110 additions and 53 deletions
|
@ -241,8 +241,11 @@ fun getConnectedSocket(attemptAddresses: List<InetAddress>, port: Int): Socket?
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val sortedAddresses: List<InetAddress> = addresses
|
||||||
|
.sortedBy { addr -> addressScore(addr) }
|
||||||
|
|
||||||
val sockets: ArrayList<Socket> = arrayListOf();
|
val sockets: ArrayList<Socket> = arrayListOf();
|
||||||
for (i in addresses.indices) {
|
for (i in sortedAddresses.indices) {
|
||||||
sockets.add(Socket());
|
sockets.add(Socket());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,7 +253,7 @@ fun getConnectedSocket(attemptAddresses: List<InetAddress>, port: Int): Socket?
|
||||||
var connectedSocket: Socket? = null;
|
var connectedSocket: Socket? = null;
|
||||||
val threads: ArrayList<Thread> = arrayListOf();
|
val threads: ArrayList<Thread> = arrayListOf();
|
||||||
for (i in 0 until sockets.size) {
|
for (i in 0 until sockets.size) {
|
||||||
val address = addresses[i];
|
val address = sortedAddresses[i];
|
||||||
val socket = sockets[i];
|
val socket = sockets[i];
|
||||||
val thread = Thread {
|
val thread = Thread {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -681,6 +681,11 @@ class Settings : FragmentedStorageFileJson() {
|
||||||
@Serializable(with = FlexibleBooleanSerializer::class)
|
@Serializable(with = FlexibleBooleanSerializer::class)
|
||||||
var allowIpv6: Boolean = true;
|
var allowIpv6: Boolean = true;
|
||||||
|
|
||||||
|
@AdvancedField
|
||||||
|
@FormField(R.string.allow_ipv4, FieldForm.TOGGLE, R.string.allow_ipv4_description, 5)
|
||||||
|
@Serializable(with = FlexibleBooleanSerializer::class)
|
||||||
|
var allowLinkLocalIpv4: Boolean = false;
|
||||||
|
|
||||||
/*TODO: Should we have a different casting quality?
|
/*TODO: Should we have a different casting quality?
|
||||||
@FormField("Preferred Casting Quality", FieldForm.DROPDOWN, "", 3)
|
@FormField("Preferred Casting Quality", FieldForm.DROPDOWN, "", 3)
|
||||||
@DropdownFieldOptionsId(R.array.preferred_quality_array)
|
@DropdownFieldOptionsId(R.array.preferred_quality_array)
|
||||||
|
|
|
@ -434,7 +434,7 @@ private fun interfaceScore(nif: NetworkInterface): Int {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addressScore(addr: InetAddress): Int {
|
fun addressScore(addr: InetAddress): Int {
|
||||||
return when (addr) {
|
return when (addr) {
|
||||||
is Inet4Address -> {
|
is Inet4Address -> {
|
||||||
val octets = addr.address.map { it.toInt() and 0xFF }
|
val octets = addr.address.map { it.toInt() and 0xFF }
|
||||||
|
|
|
@ -115,6 +115,7 @@ import java.io.StringWriter
|
||||||
import java.lang.reflect.InvocationTargetException
|
import java.lang.reflect.InvocationTargetException
|
||||||
import java.util.LinkedList
|
import java.util.LinkedList
|
||||||
import java.util.Queue
|
import java.util.Queue
|
||||||
|
import java.util.UUID
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue
|
import java.util.concurrent.ConcurrentLinkedQueue
|
||||||
|
|
||||||
|
|
||||||
|
@ -218,6 +219,8 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val mainId = UUID.randomUUID().toString().substring(0, 5)
|
||||||
|
|
||||||
constructor() : super() {
|
constructor() : super() {
|
||||||
if (BuildConfig.DEBUG) {
|
if (BuildConfig.DEBUG) {
|
||||||
StrictMode.setVmPolicy(
|
StrictMode.setVmPolicy(
|
||||||
|
@ -269,8 +272,8 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
Logger.i(TAG, "MainActivity Starting");
|
Logger.w(TAG, "MainActivity Starting [$mainId]");
|
||||||
StateApp.instance.setGlobalContext(this, lifecycleScope);
|
StateApp.instance.setGlobalContext(this, lifecycleScope, mainId);
|
||||||
StateApp.instance.mainAppStarting(this);
|
StateApp.instance.mainAppStarting(this);
|
||||||
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
@ -671,13 +674,13 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
Logger.v(TAG, "onResume")
|
Logger.w(TAG, "onResume [$mainId]")
|
||||||
_isVisible = true;
|
_isVisible = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
super.onPause();
|
super.onPause();
|
||||||
Logger.v(TAG, "onPause")
|
Logger.w(TAG, "onPause [$mainId]")
|
||||||
_isVisible = false;
|
_isVisible = false;
|
||||||
|
|
||||||
_qrCodeLoadingDialog?.dismiss()
|
_qrCodeLoadingDialog?.dismiss()
|
||||||
|
@ -686,7 +689,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||||
|
|
||||||
override fun onStop() {
|
override fun onStop() {
|
||||||
super.onStop()
|
super.onStop()
|
||||||
Logger.v(TAG, "_wasStopped = true");
|
Logger.w(TAG, "onStop [$mainId]");
|
||||||
_wasStopped = true;
|
_wasStopped = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1103,8 +1106,8 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
Logger.v(TAG, "onDestroy")
|
Logger.w(TAG, "onDestroy [$mainId]")
|
||||||
StateApp.instance.mainAppDestroyed(this);
|
StateApp.instance.mainAppDestroyed(this, mainId);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun <reified T> isFragmentActive(): Boolean {
|
inline fun <reified T> isFragmentActive(): Boolean {
|
||||||
|
|
|
@ -166,10 +166,11 @@ class StateCasting {
|
||||||
Logger.i(TAG, "CastingService started.");
|
Logger.i(TAG, "CastingService started.");
|
||||||
|
|
||||||
_nsdManager = context.getSystemService(Context.NSD_SERVICE) as NsdManager
|
_nsdManager = context.getSystemService(Context.NSD_SERVICE) as NsdManager
|
||||||
|
startDiscovering()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun startDiscovering() {
|
private fun startDiscovering() {
|
||||||
_nsdManager?.apply {
|
_nsdManager?.apply {
|
||||||
_discoveryListeners.forEach {
|
_discoveryListeners.forEach {
|
||||||
discoverServices(it.key, NsdManager.PROTOCOL_DNS_SD, it.value)
|
discoverServices(it.key, NsdManager.PROTOCOL_DNS_SD, it.value)
|
||||||
|
@ -178,7 +179,7 @@ class StateCasting {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun stopDiscovering() {
|
private fun stopDiscovering() {
|
||||||
_nsdManager?.apply {
|
_nsdManager?.apply {
|
||||||
_discoveryListeners.forEach {
|
_discoveryListeners.forEach {
|
||||||
try {
|
try {
|
||||||
|
@ -1220,9 +1221,16 @@ class StateCasting {
|
||||||
|
|
||||||
private fun getLocalUrl(ad: CastingDevice): String {
|
private fun getLocalUrl(ad: CastingDevice): String {
|
||||||
var address = ad.localAddress!!
|
var address = ad.localAddress!!
|
||||||
if (address.isLinkLocalAddress) {
|
if (Settings.instance.casting.allowLinkLocalIpv4) {
|
||||||
address = findPreferredAddress() ?: address
|
if (address.isLinkLocalAddress && address is Inet6Address) {
|
||||||
Logger.i(TAG, "Selected casting address: $address")
|
address = findPreferredAddress() ?: address
|
||||||
|
Logger.i(TAG, "Selected casting address: $address")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (address.isLinkLocalAddress) {
|
||||||
|
address = findPreferredAddress() ?: address
|
||||||
|
Logger.i(TAG, "Selected casting address: $address")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return "http://${address.toUrlAddress().trim('/')}:${_castServer.port}";
|
return "http://${address.toUrlAddress().trim('/')}:${_castServer.port}";
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,7 +103,6 @@ class ConnectCastingDialog(context: Context?) : AlertDialog(context) {
|
||||||
super.show();
|
super.show();
|
||||||
Logger.i(TAG, "Dialog shown.");
|
Logger.i(TAG, "Dialog shown.");
|
||||||
|
|
||||||
StateCasting.instance.startDiscovering()
|
|
||||||
(_imageLoader.drawable as Animatable?)?.start();
|
(_imageLoader.drawable as Animatable?)?.start();
|
||||||
|
|
||||||
synchronized(StateCasting.instance.devices) {
|
synchronized(StateCasting.instance.devices) {
|
||||||
|
@ -148,7 +147,6 @@ class ConnectCastingDialog(context: Context?) : AlertDialog(context) {
|
||||||
override fun dismiss() {
|
override fun dismiss() {
|
||||||
super.dismiss()
|
super.dismiss()
|
||||||
(_imageLoader.drawable as Animatable?)?.stop()
|
(_imageLoader.drawable as Animatable?)?.stop()
|
||||||
StateCasting.instance.stopDiscovering()
|
|
||||||
StateCasting.instance.onDeviceAdded.remove(this)
|
StateCasting.instance.onDeviceAdded.remove(this)
|
||||||
StateCasting.instance.onDeviceChanged.remove(this)
|
StateCasting.instance.onDeviceChanged.remove(this)
|
||||||
StateCasting.instance.onDeviceRemoved.remove(this)
|
StateCasting.instance.onDeviceRemoved.remove(this)
|
||||||
|
|
|
@ -16,6 +16,8 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.futo.platformplayer.R
|
import com.futo.platformplayer.R
|
||||||
import com.futo.platformplayer.UISlideOverlays
|
import com.futo.platformplayer.UISlideOverlays
|
||||||
|
import com.futo.platformplayer.stores.FragmentedStorage
|
||||||
|
import com.futo.platformplayer.stores.StringStorage
|
||||||
import com.futo.platformplayer.views.adapters.SubscriptionAdapter
|
import com.futo.platformplayer.views.adapters.SubscriptionAdapter
|
||||||
|
|
||||||
class CreatorsFragment : MainFragment() {
|
class CreatorsFragment : MainFragment() {
|
||||||
|
@ -29,6 +31,8 @@ class CreatorsFragment : MainFragment() {
|
||||||
private var _editSearch: EditText? = null;
|
private var _editSearch: EditText? = null;
|
||||||
private var _textMeta: TextView? = null;
|
private var _textMeta: TextView? = null;
|
||||||
private var _buttonClearSearch: ImageButton? = null
|
private var _buttonClearSearch: ImageButton? = null
|
||||||
|
private var _ordering = FragmentedStorage.get<StringStorage>("creators_ordering")
|
||||||
|
|
||||||
|
|
||||||
override fun onCreateMainView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
override fun onCreateMainView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||||
val view = inflater.inflate(R.layout.fragment_creators, container, false);
|
val view = inflater.inflate(R.layout.fragment_creators, container, false);
|
||||||
|
@ -44,7 +48,7 @@ class CreatorsFragment : MainFragment() {
|
||||||
_buttonClearSearch?.visibility = View.INVISIBLE;
|
_buttonClearSearch?.visibility = View.INVISIBLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
val adapter = SubscriptionAdapter(inflater, getString(R.string.confirm_delete_subscription)) { subs ->
|
val adapter = SubscriptionAdapter(inflater, getString(R.string.confirm_delete_subscription), _ordering?.value?.toIntOrNull() ?: 5) { subs ->
|
||||||
_textMeta?.let {
|
_textMeta?.let {
|
||||||
it.text = "${subs.size} creator${if(subs.size > 1) "s" else ""}";
|
it.text = "${subs.size} creator${if(subs.size > 1) "s" else ""}";
|
||||||
}
|
}
|
||||||
|
@ -61,6 +65,7 @@ class CreatorsFragment : MainFragment() {
|
||||||
spinnerSortBy.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
spinnerSortBy.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||||
override fun onItemSelected(parent: AdapterView<*>, view: View?, pos: Int, id: Long) {
|
override fun onItemSelected(parent: AdapterView<*>, view: View?, pos: Int, id: Long) {
|
||||||
adapter.sortBy = pos;
|
adapter.sortBy = pos;
|
||||||
|
_ordering.setAndSave(pos.toString())
|
||||||
}
|
}
|
||||||
override fun onNothingSelected(parent: AdapterView<*>?) = Unit
|
override fun onNothingSelected(parent: AdapterView<*>?) = Unit
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,11 +8,14 @@ import android.text.method.LinkMovementMethod
|
||||||
import android.text.style.URLSpan
|
import android.text.style.URLSpan
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.futo.platformplayer.activities.MainActivity
|
import com.futo.platformplayer.activities.MainActivity
|
||||||
import com.futo.platformplayer.logging.Logger
|
import com.futo.platformplayer.logging.Logger
|
||||||
import com.futo.platformplayer.receivers.MediaControlReceiver
|
import com.futo.platformplayer.receivers.MediaControlReceiver
|
||||||
import com.futo.platformplayer.timestampRegex
|
import com.futo.platformplayer.timestampRegex
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
class PlatformLinkMovementMethod(private val _context: Context) : LinkMovementMethod() {
|
class PlatformLinkMovementMethod(private val _context: Context) : LinkMovementMethod() {
|
||||||
|
|
||||||
|
@ -60,31 +63,39 @@ class PlatformLinkMovementMethod(private val _context: Context) : LinkMovementMe
|
||||||
val dx = event.x - downX
|
val dx = event.x - downX
|
||||||
val dy = event.y - downY
|
val dy = event.y - downY
|
||||||
if (Math.abs(dx) <= touchSlop && Math.abs(dy) <= touchSlop && isTouchInside(widget, event)) {
|
if (Math.abs(dx) <= touchSlop && Math.abs(dy) <= touchSlop && isTouchInside(widget, event)) {
|
||||||
runBlocking {
|
for (link in pressedLinks!!) {
|
||||||
for (link in pressedLinks!!) {
|
Logger.i(TAG) { "Link clicked '${link.url}'." }
|
||||||
Logger.i(TAG) { "Link clicked '${link.url}'." }
|
|
||||||
|
|
||||||
if (_context is MainActivity) {
|
val c = _context
|
||||||
if (_context.handleUrl(link.url)) continue
|
if (c is MainActivity) {
|
||||||
if (timestampRegex.matches(link.url)) {
|
c.lifecycleScope.launch(Dispatchers.IO) {
|
||||||
val tokens = link.url.split(':')
|
if (c.handleUrl(link.url)) {
|
||||||
var time_s = -1L
|
return@launch
|
||||||
when (tokens.size) {
|
}
|
||||||
2 -> time_s = tokens[0].toLong() * 60 + tokens[1].toLong()
|
if (timestampRegex.matches(link.url)) {
|
||||||
3 -> time_s = tokens[0].toLong() * 3600 +
|
val tokens = link.url.split(':')
|
||||||
tokens[1].toLong() * 60 +
|
var time_s = -1L
|
||||||
tokens[2].toLong()
|
when (tokens.size) {
|
||||||
}
|
2 -> time_s = tokens[0].toLong() * 60 + tokens[1].toLong()
|
||||||
|
3 -> time_s = tokens[0].toLong() * 3600 +
|
||||||
|
tokens[1].toLong() * 60 +
|
||||||
|
tokens[2].toLong()
|
||||||
|
}
|
||||||
|
|
||||||
if (time_s != -1L) {
|
if (time_s != -1L) {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
MediaControlReceiver.onSeekToReceived.emit(time_s * 1000)
|
MediaControlReceiver.onSeekToReceived.emit(time_s * 1000)
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
return@launch
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(link.url)))
|
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
c.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(link.url)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
pressedLinks = null
|
pressedLinks = null
|
||||||
linkPressed = false
|
linkPressed = false
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -156,6 +156,8 @@ class StateApp {
|
||||||
return thisContext;
|
return thisContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var _mainId: String? = null;
|
||||||
|
|
||||||
//Files
|
//Files
|
||||||
private var _tempDirectory: File? = null;
|
private var _tempDirectory: File? = null;
|
||||||
private var _cacheDirectory: File? = null;
|
private var _cacheDirectory: File? = null;
|
||||||
|
@ -295,9 +297,12 @@ class StateApp {
|
||||||
}
|
}
|
||||||
|
|
||||||
//Lifecycle
|
//Lifecycle
|
||||||
fun setGlobalContext(context: Context, coroutineScope: CoroutineScope? = null) {
|
fun setGlobalContext(context: Context, coroutineScope: CoroutineScope? = null, mainId: String? = null) {
|
||||||
|
_mainId = mainId;
|
||||||
_context = context;
|
_context = context;
|
||||||
_scope = coroutineScope
|
_scope = coroutineScope
|
||||||
|
Logger.w(TAG, "Scope initialized ${(coroutineScope != null)}\n ${Log.getStackTraceString(Throwable())}")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun initializeFiles(force: Boolean = false) {
|
fun initializeFiles(force: Boolean = false) {
|
||||||
|
@ -719,7 +724,9 @@ class StateApp {
|
||||||
migrateStores(context, managedStores, index + 1);
|
migrateStores(context, managedStores, index + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
fun mainAppDestroyed(context: Context) {
|
fun mainAppDestroyed(context: Context, mainId: String? = null) {
|
||||||
|
if (mainId != null && (_mainId != mainId || _mainId == null))
|
||||||
|
return
|
||||||
Logger.i(TAG, "App ended");
|
Logger.i(TAG, "App ended");
|
||||||
_receiverBecomingNoisy?.let {
|
_receiverBecomingNoisy?.let {
|
||||||
_receiverBecomingNoisy = null;
|
_receiverBecomingNoisy = null;
|
||||||
|
@ -743,7 +750,8 @@ class StateApp {
|
||||||
|
|
||||||
fun dispose(){
|
fun dispose(){
|
||||||
_context = null;
|
_context = null;
|
||||||
_scope = null;
|
// _scope = null;
|
||||||
|
Logger.w(TAG, "StateApp disposed: ${Log.getStackTraceString(Throwable())}")
|
||||||
}
|
}
|
||||||
|
|
||||||
private val _connectivityEvents = object : ConnectivityManager.NetworkCallback() {
|
private val _connectivityEvents = object : ConnectivityManager.NetworkCallback() {
|
||||||
|
|
|
@ -31,10 +31,11 @@ class SubscriptionAdapter : RecyclerView.Adapter<SubscriptionViewHolder> {
|
||||||
updateDataset();
|
updateDataset();
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(inflater: LayoutInflater, confirmationMessage: String, onDatasetChanged: ((List<Subscription>)->Unit)? = null) : super() {
|
constructor(inflater: LayoutInflater, confirmationMessage: String, sortByDefault: Int, onDatasetChanged: ((List<Subscription>)->Unit)? = null) : super() {
|
||||||
_inflater = inflater;
|
_inflater = inflater;
|
||||||
_confirmationMessage = confirmationMessage;
|
_confirmationMessage = confirmationMessage;
|
||||||
_onDatasetChanged = onDatasetChanged;
|
_onDatasetChanged = onDatasetChanged;
|
||||||
|
sortBy = sortByDefault
|
||||||
|
|
||||||
StateSubscriptions.instance.onSubscriptionsChanged.subscribe { _, _ -> if(Looper.myLooper() != Looper.getMainLooper())
|
StateSubscriptions.instance.onSubscriptionsChanged.subscribe { _, _ -> if(Looper.myLooper() != Looper.getMainLooper())
|
||||||
StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) { updateDataset() }
|
StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) { updateDataset() }
|
||||||
|
|
|
@ -8,12 +8,16 @@ import android.text.Spannable
|
||||||
import android.text.style.URLSpan
|
import android.text.style.URLSpan
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.futo.platformplayer.activities.MainActivity
|
import com.futo.platformplayer.activities.MainActivity
|
||||||
import com.futo.platformplayer.logging.Logger
|
import com.futo.platformplayer.logging.Logger
|
||||||
import com.futo.platformplayer.others.PlatformLinkMovementMethod
|
import com.futo.platformplayer.others.PlatformLinkMovementMethod
|
||||||
import com.futo.platformplayer.receivers.MediaControlReceiver
|
import com.futo.platformplayer.receivers.MediaControlReceiver
|
||||||
|
import com.futo.platformplayer.states.StateApp
|
||||||
import com.futo.platformplayer.timestampRegex
|
import com.futo.platformplayer.timestampRegex
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
class NonScrollingTextView : androidx.appcompat.widget.AppCompatTextView {
|
class NonScrollingTextView : androidx.appcompat.widget.AppCompatTextView {
|
||||||
private var _lastTouchedLinks: Array<URLSpan>? = null
|
private var _lastTouchedLinks: Array<URLSpan>? = null
|
||||||
|
@ -77,12 +81,14 @@ class NonScrollingTextView : androidx.appcompat.widget.AppCompatTextView {
|
||||||
val dx = event.x - downX
|
val dx = event.x - downX
|
||||||
val dy = event.y - downY
|
val dy = event.y - downY
|
||||||
if (Math.abs(dx) <= touchSlop && Math.abs(dy) <= touchSlop && isTouchInside(event)) {
|
if (Math.abs(dx) <= touchSlop && Math.abs(dy) <= touchSlop && isTouchInside(event)) {
|
||||||
runBlocking {
|
for (link in _lastTouchedLinks!!) {
|
||||||
for (link in _lastTouchedLinks!!) {
|
Logger.i(PlatformLinkMovementMethod.TAG) { "Link clicked '${link.url}'." }
|
||||||
Logger.i(PlatformLinkMovementMethod.TAG) { "Link clicked '${link.url}'." }
|
val c = context
|
||||||
val c = context
|
if (c is MainActivity) {
|
||||||
if (c is MainActivity) {
|
c.lifecycleScope.launch(Dispatchers.IO) {
|
||||||
if (c.handleUrl(link.url)) continue
|
if (c.handleUrl(link.url)) {
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
if (timestampRegex.matches(link.url)) {
|
if (timestampRegex.matches(link.url)) {
|
||||||
val tokens = link.url.split(':')
|
val tokens = link.url.split(':')
|
||||||
var time_s = -1L
|
var time_s = -1L
|
||||||
|
@ -92,13 +98,21 @@ class NonScrollingTextView : androidx.appcompat.widget.AppCompatTextView {
|
||||||
tokens[1].toLong() * 60 +
|
tokens[1].toLong() * 60 +
|
||||||
tokens[2].toLong()
|
tokens[2].toLong()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (time_s != -1L) {
|
if (time_s != -1L) {
|
||||||
MediaControlReceiver.onSeekToReceived.emit(time_s * 1000)
|
withContext(Dispatchers.Main) {
|
||||||
continue
|
MediaControlReceiver.onSeekToReceived.emit(time_s * 1000)
|
||||||
|
}
|
||||||
|
return@launch
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(link.url)))
|
|
||||||
} else {
|
withContext(Dispatchers.Main) {
|
||||||
|
c.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(link.url)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) {
|
||||||
c.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(link.url)))
|
c.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(link.url)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,7 +90,6 @@ class ToggleField : TableRow, IField {
|
||||||
|
|
||||||
val advancedFieldAttr = field.getAnnotation(AdvancedField::class.java)
|
val advancedFieldAttr = field.getAnnotation(AdvancedField::class.java)
|
||||||
if(advancedFieldAttr != null || advanced) {
|
if(advancedFieldAttr != null || advanced) {
|
||||||
Logger.w("ToggleField", "Found cccadvanced field: " + field.name);
|
|
||||||
isAdvanced = true;
|
isAdvanced = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -76,6 +76,8 @@
|
||||||
<string name="always_proxy_requests_description">Always proxy requests when casting data through the device.</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">Allow IPV6</string>
|
||||||
<string name="allow_ipv6_description">If casting over IPV6 is allowed, can cause issues on some networks</string>
|
<string name="allow_ipv6_description">If casting over IPV6 is allowed, can cause issues on some networks</string>
|
||||||
|
<string name="allow_ipv4">Allow Link Local IPV4</string>
|
||||||
|
<string name="allow_ipv4_description">If casting over IPV4 link local is allowed, can cause issues on some networks</string>
|
||||||
<string name="discover">Discover</string>
|
<string name="discover">Discover</string>
|
||||||
<string name="find_new_video_sources_to_add">Find new video sources to add</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>
|
<string name="these_sources_have_been_disabled">These sources have been disabled</string>
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<application>
|
<application>
|
||||||
<receiver android:name=".receivers.InstallReceiver" />
|
<receiver android:name=".receivers.InstallReceiver" />
|
||||||
|
|
||||||
<activity android:name=".activities.MainActivity">
|
<activity android:name=".activities.MainActivity" android:launchMode="singleInstance">
|
||||||
<intent-filter android:autoVerify="true">
|
<intent-filter android:autoVerify="true">
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
|
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue