diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7f2db8a8..7f50269c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,8 +7,8 @@ - - + + diff --git a/app/src/main/java/com/futo/platformplayer/Extensions_Network.kt b/app/src/main/java/com/futo/platformplayer/Extensions_Network.kt index b99b33d5..4ea143b6 100644 --- a/app/src/main/java/com/futo/platformplayer/Extensions_Network.kt +++ b/app/src/main/java/com/futo/platformplayer/Extensions_Network.kt @@ -169,7 +169,7 @@ private fun parseHextet(ipString: String, start: Int, end: Int): Short { var hextet = 0 for (i in start until end) { hextet = hextet shl 4 - hextet = hextet or ipString[i].digitToIntOrNull(16)!! ?: -1 + hextet = hextet or ipString[i].digitToIntOrNull(16)!! } return hextet.toShort() } diff --git a/app/src/main/java/com/futo/platformplayer/Extensions_V8.kt b/app/src/main/java/com/futo/platformplayer/Extensions_V8.kt index daaba3f5..e31d3dac 100644 --- a/app/src/main/java/com/futo/platformplayer/Extensions_V8.kt +++ b/app/src/main/java/com/futo/platformplayer/Extensions_V8.kt @@ -27,7 +27,7 @@ fun V8Value?.orDefault(default: R, handler: (V8Value)->R): R { inline fun V8Value.expectOrThrow(config: IV8PluginConfig, contextName: String): T { if(this !is T) throw ScriptImplementationException(config, "Expected ${contextName} to be of type ${T::class.simpleName}, but found ${this::class.simpleName}"); - return this as T; + return this; } //Singles diff --git a/app/src/main/java/com/futo/platformplayer/SettingsDev.kt b/app/src/main/java/com/futo/platformplayer/SettingsDev.kt index ef4f9495..91bba5e4 100644 --- a/app/src/main/java/com/futo/platformplayer/SettingsDev.kt +++ b/app/src/main/java/com/futo/platformplayer/SettingsDev.kt @@ -2,15 +2,9 @@ package com.futo.platformplayer import android.content.Context import android.webkit.CookieManager -import androidx.lifecycle.lifecycleScope -import androidx.work.Constraints import androidx.work.Data -import androidx.work.ExistingPeriodicWorkPolicy -import androidx.work.NetworkType import androidx.work.OneTimeWorkRequestBuilder -import androidx.work.PeriodicWorkRequest import androidx.work.WorkManager -import androidx.work.WorkerParameters import com.caoccao.javet.values.primitive.V8ValueInteger import com.caoccao.javet.values.primitive.V8ValueString import com.futo.platformplayer.activities.DeveloperActivity @@ -36,7 +30,6 @@ import com.futo.platformplayer.states.StatePlatform import com.futo.platformplayer.states.StateSubscriptions import com.futo.platformplayer.stores.FragmentedStorage import com.futo.platformplayer.stores.FragmentedStorageFileJson -import com.futo.platformplayer.stores.db.types.DBHistory import com.futo.platformplayer.views.fields.ButtonField import com.futo.platformplayer.views.fields.FieldForm import com.futo.platformplayer.views.fields.FormField @@ -44,11 +37,12 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import kotlinx.serialization.* -import kotlinx.serialization.json.* +import kotlinx.serialization.Contextual +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json import java.time.OffsetDateTime -import java.util.UUID -import java.util.concurrent.TimeUnit import java.util.stream.IntStream.range import kotlin.system.measureTimeMillis @@ -109,14 +103,14 @@ class SettingsDev : FragmentedStorageFileJson() { StateApp.instance.scope.launch(Dispatchers.IO) { try { val subsCache = - StateSubscriptions.instance.getSubscriptionsFeedWithExceptions(cacheScope = this)?.first; + StateSubscriptions.instance.getSubscriptionsFeedWithExceptions(cacheScope = this).first; var total = 0; var page = 0; var lastToast = System.currentTimeMillis(); - while(subsCache!!.hasMorePages() && total < 5000) { - subsCache!!.nextPage(); - total += subsCache!!.getResults().size; + while(subsCache.hasMorePages() && total < 5000) { + subsCache.nextPage(); + total += subsCache.getResults().size; page++; if(page % 10 == 0) @@ -174,9 +168,9 @@ class SettingsDev : FragmentedStorageFileJson() { var total = 0; var page = 0; var lastToast = System.currentTimeMillis(); - while(subsCache!!.hasMorePages() && total < 5000) { - subsCache!!.nextPage(); - total += subsCache!!.getResults().size; + while(subsCache.hasMorePages() && total < 5000) { + subsCache.nextPage(); + total += subsCache.getResults().size; page++; for(item in subsCache.getResults().filterIsInstance()) { @@ -375,9 +369,9 @@ class SettingsDev : FragmentedStorageFileJson() { @FormField(R.string.getHome, FieldForm.BUTTON, R.string.attempts_to_fetch_2_pages_from_getHome, 2) fun testV8Home() { runTestPlugin(_currentPlugin) { - var home: IPager? = null; - var resultPage1: String = ""; - var resultPage2: String = ""; + var home: IPager?; + val resultPage1: String; + val resultPage2: String; val page1Time = measureTimeMillis { home = it.getHome(); val results = home!!.getResults(); diff --git a/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt b/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt index ccb083a0..f1a0ea44 100644 --- a/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt +++ b/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt @@ -22,20 +22,25 @@ import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.models.Playlist import com.futo.platformplayer.models.Subscription import com.futo.platformplayer.parsers.HLS -import com.futo.platformplayer.states.* +import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.states.StateDownloads +import com.futo.platformplayer.states.StateMeta +import com.futo.platformplayer.states.StatePlatform +import com.futo.platformplayer.states.StatePlayer +import com.futo.platformplayer.states.StatePlaylists import com.futo.platformplayer.views.LoaderView +import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuFilters import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuGroup import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuItem import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuOverlay +import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuTextInput import com.futo.platformplayer.views.pills.RoundButton import com.futo.platformplayer.views.pills.RoundButtonGroup -import com.futo.platformplayer.views.overlays.slideup.* import com.futo.platformplayer.views.video.FutoVideoPlayerBase import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import java.lang.IllegalStateException class UISlideOverlays { companion object { @@ -242,7 +247,7 @@ class UISlideOverlays { val audioSources = if(descriptor is VideoUnMuxedSourceDescriptor) descriptor.audioSources else null; val subtitleSources = video.subtitles; - if(videoSources.size == 0 && (audioSources?.size ?: 0) == 0) { + if(videoSources.isEmpty() && (audioSources?.size ?: 0) == 0) { UIDialogs.toast(container.context.getString(R.string.no_downloads_available), false); return null; } @@ -263,24 +268,30 @@ class UISlideOverlays { videoSources .filter { it.isDownloadable() } .map { - if (it is IVideoUrlSource) { - SlideUpMenuItem(container.context, R.drawable.ic_movie, it.name, "${it.width}x${it.height}", it, { - selectedVideo = it - menu?.selectOption(videoSources, it); - if(selectedAudio != null || !requiresAudio) - menu?.setOk(container.context.getString(R.string.download)); - }, false) - } else if (it is IHLSManifestSource) { - SlideUpMenuItem(container.context, R.drawable.ic_movie, it.name, "HLS", it, { - showHlsPicker(video, it, it.url, container) - }, false) - } else { - throw Exception("Unhandled source type") + when (it) { + is IVideoUrlSource -> { + SlideUpMenuItem(container.context, R.drawable.ic_movie, it.name, "${it.width}x${it.height}", it, { + selectedVideo = it + menu?.selectOption(videoSources, it); + if(selectedAudio != null || !requiresAudio) + menu?.setOk(container.context.getString(R.string.download)); + }, false) + } + + is IHLSManifestSource -> { + SlideUpMenuItem(container.context, R.drawable.ic_movie, it.name, "HLS", it, { + showHlsPicker(video, it, it.url, container) + }, false) + } + + else -> { + throw Exception("Unhandled source type") + } } }).flatten().toList() )); - if(Settings.instance.downloads.getDefaultVideoQualityPixels() > 0 && videoSources.size > 0) { + if(Settings.instance.downloads.getDefaultVideoQualityPixels() > 0 && videoSources.isNotEmpty()) { //TODO: Add HLS support here selectedVideo = VideoHelper.selectBestVideoSource( videoSources.filter { it is IVideoUrlSource && it.isDownloadable() }.asIterable(), @@ -289,30 +300,30 @@ class UISlideOverlays { ) as IVideoUrlSource; } - audioSources?.let { audioSources -> + if (audioSources != null) { items.add(SlideUpMenuGroup(container.context, container.context.getString(R.string.audio), audioSources, audioSources .filter { VideoHelper.isDownloadable(it) } .map { - if (it is IAudioUrlSource) { - SlideUpMenuItem(container.context, R.drawable.ic_music, it.name, "${it.bitrate}", it, { - selectedAudio = it - menu?.selectOption(audioSources, it); - menu?.setOk(container.context.getString(R.string.download)); - }, false); - } else if (it is IHLSManifestAudioSource) { - SlideUpMenuItem(container.context, R.drawable.ic_movie, it.name, "HLS Audio", it, { - showHlsPicker(video, it, it.url, container) - }, false) - } else { - throw Exception("Unhandled source type") + when (it) { + is IAudioUrlSource -> { + SlideUpMenuItem(container.context, R.drawable.ic_music, it.name, "${it.bitrate}", it, { + selectedAudio = it + menu?.selectOption(audioSources, it); + menu?.setOk(container.context.getString(R.string.download)); + }, false); + } + + is IHLSManifestAudioSource -> { + SlideUpMenuItem(container.context, R.drawable.ic_movie, it.name, "HLS Audio", it, { + showHlsPicker(video, it, it.url, container) + }, false) + } + + else -> { + throw Exception("Unhandled source type") + } } })); - val asources = audioSources; - val preferredAudioSource = VideoHelper.selectBestAudioSource(asources.asIterable(), - FutoVideoPlayerBase.PREFERED_AUDIO_CONTAINERS, - Settings.instance.playback.getPrimaryLanguage(container.context), - if(Settings.instance.downloads.isHighBitrateDefault()) 99999999 else 1); - menu?.selectOption(asources, preferredAudioSource); //TODO: Add HLS support here selectedAudio = VideoHelper.selectBestAudioSource(audioSources.filter { it is IAudioUrlSource && it.isDownloadable() }.asIterable(), @@ -321,10 +332,8 @@ class UISlideOverlays { if(Settings.instance.downloads.isHighBitrateDefault()) 9999999 else 1) as IAudioUrlSource?; } - //ContentResolver is required for subtitles.. if(contentResolver != null && subtitleSources.isNotEmpty()) { - items.add(SlideUpMenuGroup(container.context, container.context.getString(R.string.subtitles), subtitleSources, subtitleSources - .map { + items.add(SlideUpMenuGroup(container.context, container.context.getString(R.string.subtitles), subtitleSources, subtitleSources.map { SlideUpMenuItem(container.context, R.drawable.ic_edit, it.name, "", it, { if (selectedSubtitle == it) { selectedSubtitle = null; @@ -334,7 +343,8 @@ class UISlideOverlays { menu?.selectOption(subtitleSources, it); } }, false); - })); + }) + ); } menu = SlideUpMenuOverlay(container.context, container, container.context.getString(R.string.download_video), null, true, items); @@ -645,10 +655,11 @@ class UISlideOverlays { val visible = buttonGroup.getVisibleButtons().filter { !ignoreTags.contains(it.tagRef) }; val hidden = buttonGroup.getInvisibleButtons().filter { !ignoreTags.contains(it.tagRef) }; - val views = arrayOf(hidden - .map { btn -> SlideUpMenuItem(container.context, btn.iconResource, btn.text.text.toString(), "", "", { - btn.handler?.invoke(btn); - }, true) as View }.toTypedArray() ?: arrayOf(), + val views = arrayOf( + hidden + .map { btn -> SlideUpMenuItem(container.context, btn.iconResource, btn.text.text.toString(), "", "", { + btn.handler?.invoke(btn); + }, true) as View }.toTypedArray(), arrayOf(SlideUpMenuItem(container.context, R.drawable.ic_pin, container.context.getString(R.string.change_pins), container.context.getString(R.string.decide_which_buttons_should_be_pinned), "", { showOrderOverlay(container, container.context.getString(R.string.select_your_pins_in_order), (visible + hidden).map { Pair(it.text.text.toString(), it.tagRef!!) }) { val selected = it diff --git a/app/src/main/java/com/futo/platformplayer/Utility.kt b/app/src/main/java/com/futo/platformplayer/Utility.kt index 0e57a220..f58a282f 100644 --- a/app/src/main/java/com/futo/platformplayer/Utility.kt +++ b/app/src/main/java/com/futo/platformplayer/Utility.kt @@ -143,6 +143,7 @@ fun InputStream.copyToOutputStream(inputStreamLength: Long, outputStream: Output } } +@Suppress("DEPRECATION") fun Activity.setNavigationBarColorAndIcons() { window.navigationBarColor = ContextCompat.getColor(this, android.R.color.black); diff --git a/app/src/main/java/com/futo/platformplayer/activities/AddSourceActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/AddSourceActivity.kt index e61ee193..bc22d3dc 100644 --- a/app/src/main/java/com/futo/platformplayer/activities/AddSourceActivity.kt +++ b/app/src/main/java/com/futo/platformplayer/activities/AddSourceActivity.kt @@ -5,13 +5,20 @@ import android.content.Intent import android.graphics.drawable.Animatable import android.os.Bundle import android.view.View -import android.widget.* +import android.widget.ImageButton +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.ScrollView +import android.widget.TextView import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.ContextCompat import androidx.lifecycle.lifecycleScope -import com.futo.platformplayer.* +import com.futo.platformplayer.R +import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.api.http.ManagedHttpClient import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.setNavigationBarColorAndIcons import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.states.StatePlatform import com.futo.platformplayer.states.StatePlugins @@ -194,7 +201,7 @@ class AddSourceActivity : AppCompatActivity() { config.allowUrls, true) ) - val pastelRed = resources.getColor(R.color.pastel_red); + val pastelRed = ContextCompat.getColor(this, R.color.pastel_red); for(warning in config.getWarnings(script)) _sourceWarnings.addView( diff --git a/app/src/main/java/com/futo/platformplayer/activities/AddSourceOptionsActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/AddSourceOptionsActivity.kt index 2db44833..2c270656 100644 --- a/app/src/main/java/com/futo/platformplayer/activities/AddSourceOptionsActivity.kt +++ b/app/src/main/java/com/futo/platformplayer/activities/AddSourceOptionsActivity.kt @@ -11,7 +11,6 @@ import com.futo.platformplayer.* import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.views.buttons.BigButton import com.google.zxing.integration.android.IntentIntegrator -import com.journeyapps.barcodescanner.CaptureActivity class AddSourceOptionsActivity : AppCompatActivity() { lateinit var _buttonBack: ImageButton; diff --git a/app/src/main/java/com/futo/platformplayer/activities/DeveloperActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/DeveloperActivity.kt index f714f880..b8dbb261 100644 --- a/app/src/main/java/com/futo/platformplayer/activities/DeveloperActivity.kt +++ b/app/src/main/java/com/futo/platformplayer/activities/DeveloperActivity.kt @@ -26,7 +26,7 @@ class DeveloperActivity : AppCompatActivity() { _form = findViewById(R.id.settings_form); _form.fromObject(SettingsDev.instance); - _form.onChanged.subscribe { field, value -> + _form.onChanged.subscribe { _, _ -> _form.setObjectValues(); SettingsDev.instance.save(); }; diff --git a/app/src/main/java/com/futo/platformplayer/activities/IWithResultLauncher.kt b/app/src/main/java/com/futo/platformplayer/activities/IWithResultLauncher.kt index 89f48a87..35cbd685 100644 --- a/app/src/main/java/com/futo/platformplayer/activities/IWithResultLauncher.kt +++ b/app/src/main/java/com/futo/platformplayer/activities/IWithResultLauncher.kt @@ -2,7 +2,6 @@ package com.futo.platformplayer.activities import android.content.Intent import androidx.activity.result.ActivityResult -import androidx.activity.result.ActivityResultLauncher interface IWithResultLauncher { fun launchForResult(intent: Intent, code: Int, handler: (ActivityResult)->Unit); diff --git a/app/src/main/java/com/futo/platformplayer/activities/LoginActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/LoginActivity.kt index 5dccdad8..a20a6743 100644 --- a/app/src/main/java/com/futo/platformplayer/activities/LoginActivity.kt +++ b/app/src/main/java/com/futo/platformplayer/activities/LoginActivity.kt @@ -3,24 +3,22 @@ package com.futo.platformplayer.activities import android.content.Context import android.content.Intent import android.os.Bundle -import android.webkit.ConsoleMessage import android.webkit.CookieManager -import android.webkit.WebChromeClient import android.webkit.WebView import android.widget.ImageButton import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.lifecycleScope -import com.futo.platformplayer.* +import com.futo.platformplayer.R import com.futo.platformplayer.api.media.platforms.js.SourceAuth import com.futo.platformplayer.api.media.platforms.js.SourcePluginAuthConfig import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.others.LoginWebViewClient +import com.futo.platformplayer.setNavigationBarColorAndIcons import com.futo.platformplayer.states.StateApp import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json @@ -102,7 +100,7 @@ class LoginActivity : AppCompatActivity() { override fun finish() { lifecycleScope.launch(Dispatchers.Main) { - _webView?.loadUrl("about:blank"); + _webView.loadUrl("about:blank"); } _callback?.let { _callback = null; diff --git a/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt index 9f031ed5..b919c5c3 100644 --- a/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt +++ b/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt @@ -26,7 +26,6 @@ import androidx.lifecycle.lifecycleScope import com.futo.platformplayer.* import com.futo.platformplayer.casting.StateCasting import com.futo.platformplayer.constructs.Event1 -import com.futo.platformplayer.dialogs.ConnectCastingDialog import com.futo.platformplayer.fragment.mainactivity.bottombar.MenuBottomBarFragment import com.futo.platformplayer.fragment.mainactivity.main.* import com.futo.platformplayer.fragment.mainactivity.topbar.AddTopBarFragment @@ -44,7 +43,6 @@ import com.futo.platformplayer.stores.v2.ManagedStore import com.google.gson.JsonParser import com.google.zxing.integration.android.IntentIntegrator import kotlinx.coroutines.* -import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import java.io.File import java.io.PrintWriter @@ -649,7 +647,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher { if(file.lowercase().endsWith(".json") || mime == "application/json") { var recon = String(data); if(!recon.trim().startsWith("[")) - return handleUnknownJson(file, recon); + return handleUnknownJson(recon); val reconLines = Json.decodeFromString>(recon); recon = reconLines.joinToString("\n"); @@ -671,7 +669,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher { if(file.lowercase().endsWith(".json")) { val recon = String(readSharedFile(file)); if(!recon.startsWith("[")) - return handleUnknownJson(file, recon); + return handleUnknownJson(recon); Logger.i(TAG, "Opened shared playlist reconstruction\n${recon}"); handleReconstruction(recon); @@ -723,7 +721,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher { } return false; } - fun handleUnknownJson(name: String?, json: String): Boolean { + fun handleUnknownJson(json: String): Boolean { val context = this; @@ -832,7 +830,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher { val isStop: Boolean = lifecycle.currentState == Lifecycle.State.CREATED; Logger.v(TAG, "onPictureInPictureModeChanged isInPictureInPictureMode=$isInPictureInPictureMode isStop=$isStop") - _fragVideoDetail?.onPictureInPictureModeChanged(isInPictureInPictureMode, isStop, newConfig); + _fragVideoDetail.onPictureInPictureModeChanged(isInPictureInPictureMode, isStop, newConfig); Logger.v(TAG, "onPictureInPictureModeChanged Ready"); } @@ -889,7 +887,6 @@ class MainActivity : AppCompatActivity, IWithResultLauncher { transaction = transaction.replace(R.id.fragment_main, segment); - val extraBottomDP = if(_fragVideoDetail.state == VideoDetailFragment.State.MINIMIZED) HEIGHT_VIDEO_MINIMIZED_DP else 0f if (segment.hasBottomBar) { if (!fragCurrent.hasBottomBar) transaction = transaction.show(_fragBotBarMenu); @@ -899,8 +896,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher { transaction = transaction.hide(_fragBotBarMenu); } transaction.commitNow(); - } - else { + } else { //Special cases if(segment is VideoDetailFragment) { _fragContainerVideoDetail.visibility = View.VISIBLE; diff --git a/app/src/main/java/com/futo/platformplayer/activities/PolycentricImportProfileActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/PolycentricImportProfileActivity.kt index 997b17dd..5ab51be8 100644 --- a/app/src/main/java/com/futo/platformplayer/activities/PolycentricImportProfileActivity.kt +++ b/app/src/main/java/com/futo/platformplayer/activities/PolycentricImportProfileActivity.kt @@ -8,20 +8,19 @@ import android.widget.ImageButton import android.widget.LinearLayout import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity -import androidx.lifecycle.lifecycleScope import com.futo.platformplayer.R import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.setNavigationBarColorAndIcons import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.states.StatePolycentric -import com.futo.polycentric.core.* +import com.futo.polycentric.core.KeyPair +import com.futo.polycentric.core.Process +import com.futo.polycentric.core.ProcessSecret +import com.futo.polycentric.core.SignedEvent +import com.futo.polycentric.core.Store +import com.futo.polycentric.core.base64UrlToByteArray import com.google.zxing.integration.android.IntentIntegrator -import com.journeyapps.barcodescanner.CaptureActivity -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import userpackage.Protocol import userpackage.Protocol.ExportBundle diff --git a/app/src/main/java/com/futo/platformplayer/activities/PolycentricProfileActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/PolycentricProfileActivity.kt index b207da44..b131b1e8 100644 --- a/app/src/main/java/com/futo/platformplayer/activities/PolycentricProfileActivity.kt +++ b/app/src/main/java/com/futo/platformplayer/activities/PolycentricProfileActivity.kt @@ -17,7 +17,6 @@ import androidx.lifecycle.lifecycleScope import com.bumptech.glide.Glide import com.futo.platformplayer.R import com.futo.platformplayer.UIDialogs -import com.futo.platformplayer.dialogs.CommentDialog import com.futo.platformplayer.dp import com.futo.platformplayer.fullyBackfillServersAnnounceExceptions import com.futo.platformplayer.images.GlideHelper.Companion.crossfade @@ -30,7 +29,6 @@ import com.futo.platformplayer.views.buttons.BigButton import com.futo.polycentric.core.Store import com.futo.polycentric.core.Synchronization import com.futo.polycentric.core.SystemState -import com.futo.polycentric.core.toURLInfoDataLink import com.futo.polycentric.core.toURLInfoSystemLinkUrl import com.github.dhaval2404.imagepicker.ImagePicker import kotlinx.coroutines.Dispatchers @@ -250,7 +248,7 @@ class PolycentricProfileActivity : AppCompatActivity() { } private fun getMimeType(contentResolver: ContentResolver, uri: Uri): String? { - var mimeType: String? = null; + var mimeType: String?; // Try to get MIME type from the content URI mimeType = contentResolver.getType(uri); diff --git a/app/src/main/java/com/futo/platformplayer/activities/SettingsActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/SettingsActivity.kt index e10d855a..a2aa5023 100644 --- a/app/src/main/java/com/futo/platformplayer/activities/SettingsActivity.kt +++ b/app/src/main/java/com/futo/platformplayer/activities/SettingsActivity.kt @@ -49,7 +49,7 @@ class SettingsActivity : AppCompatActivity(), IWithResultLauncher { _loaderView = findViewById(R.id.loader); overlay = findViewById(R.id.overlay_container); - _form.onChanged.subscribe { field, value -> + _form.onChanged.subscribe { field, _ -> Logger.i("SettingsActivity", "Setting [${field.field?.name}] changed, saving"); _form.setObjectValues(); Settings.instance.save(); diff --git a/app/src/main/java/com/futo/platformplayer/api/http/ManagedHttpClient.kt b/app/src/main/java/com/futo/platformplayer/api/http/ManagedHttpClient.kt index 18e6859b..9c79b665 100644 --- a/app/src/main/java/com/futo/platformplayer/api/http/ManagedHttpClient.kt +++ b/app/src/main/java/com/futo/platformplayer/api/http/ManagedHttpClient.kt @@ -13,8 +13,6 @@ import okhttp3.Response import okhttp3.ResponseBody import okhttp3.WebSocket import okhttp3.WebSocketListener -import java.util.Dictionary -import java.util.concurrent.TimeUnit import kotlin.system.measureTimeMillis open class ManagedHttpClient { @@ -60,7 +58,7 @@ open class ManagedHttpClient { val requestBuilder: okhttp3.Request.Builder = okhttp3.Request.Builder() .url(url); - if(user_agent != null && !user_agent.isEmpty() && !headers.any { it.key.lowercase() == "user-agent" }) + if(user_agent.isNotEmpty() && !headers.any { it.key.lowercase() == "user-agent" }) requestBuilder.addHeader("User-Agent", user_agent) for (pair in headers.entries) @@ -137,7 +135,7 @@ open class ManagedHttpClient { val requestBuilder: okhttp3.Request.Builder = okhttp3.Request.Builder() .method(request.method, requestBody) .url(request.url); - if(user_agent != null && !user_agent.isEmpty() && !request.headers.any { it.key.lowercase() == "user-agent" }) + if(user_agent.isNotEmpty() && !request.headers.any { it.key.lowercase() == "user-agent" }) requestBuilder.addHeader("User-Agent", user_agent) for (pair in request.headers.entries) @@ -148,7 +146,7 @@ open class ManagedHttpClient { val time = measureTimeMillis { val call = client.newCall(requestBuilder.build()); - request.onCallCreated?.emit(call); + request.onCallCreated.emit(call); response = call.execute() resp = Response( response.code, diff --git a/app/src/main/java/com/futo/platformplayer/api/http/server/ManagedHttpServer.kt b/app/src/main/java/com/futo/platformplayer/api/http/server/ManagedHttpServer.kt index 1625186f..e4f1cb03 100644 --- a/app/src/main/java/com/futo/platformplayer/api/http/server/ManagedHttpServer.kt +++ b/app/src/main/java/com/futo/platformplayer/api/http/server/ManagedHttpServer.kt @@ -1,11 +1,11 @@ package com.futo.platformplayer.api.http.server -import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.api.http.ManagedHttpClient import com.futo.platformplayer.api.http.server.exceptions.EmptyRequestException import com.futo.platformplayer.api.http.server.handlers.HttpFuntionHandler import com.futo.platformplayer.api.http.server.handlers.HttpHandler import com.futo.platformplayer.api.http.server.handlers.HttpOptionsAllowHandler +import com.futo.platformplayer.logging.Logger import java.io.BufferedInputStream import java.io.OutputStream import java.lang.reflect.Field @@ -14,11 +14,10 @@ import java.net.InetAddress import java.net.NetworkInterface import java.net.ServerSocket import java.net.Socket -import java.util.* +import java.util.UUID import java.util.concurrent.ExecutorService import java.util.concurrent.Executors import java.util.stream.IntStream.range -import kotlin.collections.HashMap class ManagedHttpServer(private val _requestedPort: Int = 0) { private val _client : ManagedHttpClient = ManagedHttpClient(); @@ -212,13 +211,13 @@ class ManagedHttpServer(private val _requestedPort: Int = 0) { addHandler(HttpFuntionHandler("GET", getMethod.second.path) { getMethod.first.invoke(obj, it) }).apply { if(!getMethod.second.contentType.isEmpty()) this.withContentType(getMethod.second.contentType); - }.withContentType(getMethod.second.contentType ?: ""); + }.withContentType(getMethod.second.contentType); for(postMethod in postMethods) if(postMethod.first.parameterTypes.firstOrNull() == HttpContext::class.java && postMethod.first.parameterCount == 1) addHandler(HttpFuntionHandler("POST", postMethod.second.path) { postMethod.first.invoke(obj, it) }).apply { if(!postMethod.second.contentType.isEmpty()) this.withContentType(postMethod.second.contentType); - }.withContentType(postMethod.second.contentType ?: ""); + }.withContentType(postMethod.second.contentType); for(getField in getFields) { getField.first.isAccessible = true; @@ -232,13 +231,13 @@ class ManagedHttpServer(private val _requestedPort: Int = 0) { } else it.respondCode(204); - }).withContentType(getField.second.contentType ?: ""); + }).withContentType(getField.second.contentType); } } private fun keepAliveLoop(requestReader: BufferedInputStream, responseStream: OutputStream, requestId: String, handler: (HttpContext)->Unit) { val stopCount = _stopCount; - var keepAlive = false; + var keepAlive: Boolean; var requestsMax = 0; var requestsTotal = 0; do { @@ -288,11 +287,13 @@ class ManagedHttpServer(private val _requestedPort: Int = 0) { for (intf in NetworkInterface.getNetworkInterfaces()) { for (addr in intf.inetAddresses) { if (!addr.isLoopbackAddress) { - val ipString: String = addr.hostAddress; - val isIPv4 = ipString.indexOf(':') < 0; - if (!isIPv4) - continue; - addresses.add(addr); + val ipString: String = addr.hostAddress ?: continue + val isIPv4 = ipString.indexOf(':') < 0 + if (!isIPv4) { + continue + } + + addresses.add(addr) } } } diff --git a/app/src/main/java/com/futo/platformplayer/api/http/server/exceptions/EmptyRequestException.kt b/app/src/main/java/com/futo/platformplayer/api/http/server/exceptions/EmptyRequestException.kt index 91182132..0b7f072c 100644 --- a/app/src/main/java/com/futo/platformplayer/api/http/server/exceptions/EmptyRequestException.kt +++ b/app/src/main/java/com/futo/platformplayer/api/http/server/exceptions/EmptyRequestException.kt @@ -1,6 +1,3 @@ package com.futo.platformplayer.api.http.server.exceptions -import java.net.SocketTimeoutException -import java.util.concurrent.TimeoutException - class EmptyRequestException(msg: String) : Exception(msg) {} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/CachedPlatformClient.kt b/app/src/main/java/com/futo/platformplayer/api/media/CachedPlatformClient.kt index ad567a99..57f53567 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/CachedPlatformClient.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/CachedPlatformClient.kt @@ -10,12 +10,9 @@ import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails import com.futo.platformplayer.api.media.models.live.ILiveChatWindowDescriptor import com.futo.platformplayer.api.media.models.live.IPlatformLiveEvent import com.futo.platformplayer.api.media.models.playback.IPlaybackTracker -import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylist import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylistDetails -import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails import com.futo.platformplayer.api.media.structures.IPager import com.futo.platformplayer.models.ImageVariable -import com.futo.platformplayer.models.Playlist /** * A temporary class that caches video results @@ -44,8 +41,7 @@ class CachedPlatformClient : IPlatformClient { var result = _cache.get(url); if(result == null) { result = _client.getContentDetails(url); - if (result != null) - _cache.put(url, result); + _cache.put(url, result); } return result; } diff --git a/app/src/main/java/com/futo/platformplayer/api/media/IPlatformClient.kt b/app/src/main/java/com/futo/platformplayer/api/media/IPlatformClient.kt index 009e0c14..b527d6ff 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/IPlatformClient.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/IPlatformClient.kt @@ -10,11 +10,9 @@ import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails import com.futo.platformplayer.api.media.models.live.ILiveChatWindowDescriptor import com.futo.platformplayer.api.media.models.live.IPlatformLiveEvent import com.futo.platformplayer.api.media.models.playback.IPlaybackTracker -import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylist import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylistDetails import com.futo.platformplayer.api.media.structures.IPager import com.futo.platformplayer.models.ImageVariable -import com.futo.platformplayer.models.Playlist /** * A client for a specific platform diff --git a/app/src/main/java/com/futo/platformplayer/api/media/LiveChatManager.kt b/app/src/main/java/com/futo/platformplayer/api/media/LiveChatManager.kt index 3e265c10..ab903057 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/LiveChatManager.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/LiveChatManager.kt @@ -9,7 +9,6 @@ import com.caverock.androidsvg.SVG import com.futo.platformplayer.api.http.ManagedHttpClient import com.futo.platformplayer.api.media.models.live.IPlatformLiveEvent import com.futo.platformplayer.api.media.models.live.LiveEventComment -import com.futo.platformplayer.api.media.models.live.LiveEventDonation import com.futo.platformplayer.api.media.models.live.LiveEventEmojis import com.futo.platformplayer.api.media.platforms.js.models.JSLiveEventPager import com.futo.platformplayer.api.media.structures.IPager @@ -195,7 +194,7 @@ class LiveChatManager { fun getEmojiDrawable(emoji: String, cb: (drawable: Drawable?)->Unit) { var drawable: Drawable? = null; - var url: String? = null; + var url: String?; synchronized(_cache_lock) { url = _cache_urls[emoji]; if(url != null) diff --git a/app/src/main/java/com/futo/platformplayer/api/media/PlatformMultiClientPool.kt b/app/src/main/java/com/futo/platformplayer/api/media/PlatformMultiClientPool.kt index 235a28b0..b77a4f35 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/PlatformMultiClientPool.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/PlatformMultiClientPool.kt @@ -20,7 +20,7 @@ class PlatformMultiClientPool { val pool = synchronized(_clientPools) { if(!_clientPools.containsKey(parentClient)) _clientPools[parentClient] = PlatformClientPool(parentClient, _name).apply { - this.onDead.subscribe { client, pool -> + this.onDead.subscribe { _, pool -> synchronized(_clientPools) { if(_clientPools[parentClient] == pool) _clientPools.remove(parentClient); diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/ResultCapabilities.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/ResultCapabilities.kt index 9b328bf1..de495f6a 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/models/ResultCapabilities.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/ResultCapabilities.kt @@ -64,7 +64,6 @@ class FilterGroup( val isMultiSelect: Boolean, val id: String? = null ) { - @kotlinx.serialization.Transient val idOrName: String get() = id ?: name; companion object { diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/live/IPlatformLiveEvent.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/live/IPlatformLiveEvent.kt index 6ffaea35..89826b01 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/models/live/IPlatformLiveEvent.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/live/IPlatformLiveEvent.kt @@ -1,31 +1,23 @@ package com.futo.platformplayer.api.media.models.live -import com.caoccao.javet.values.V8Value import com.caoccao.javet.values.reference.V8ValueObject -import com.futo.platformplayer.api.media.models.ratings.IRating -import com.futo.platformplayer.api.media.models.ratings.RatingLikeDislikes -import com.futo.platformplayer.api.media.models.ratings.RatingLikes -import com.futo.platformplayer.api.media.models.ratings.RatingScaler -import com.futo.platformplayer.api.media.models.ratings.RatingType import com.futo.platformplayer.engine.IV8PluginConfig import com.futo.platformplayer.getOrThrow -import com.futo.platformplayer.orDefault interface IPlatformLiveEvent { val type : LiveEventType; companion object { - fun fromV8(config: IV8PluginConfig, obj: V8ValueObject, contextName: String = "Unknown") : IPlatformLiveEvent { - val contextName = "LiveEvent"; - val type = LiveEventType.fromInt(obj.getOrThrow(config, "type", contextName)); - return when(type) { + fun fromV8(config: IV8PluginConfig, obj: V8ValueObject, contextName: String = "LiveEvent") : IPlatformLiveEvent { + val t = LiveEventType.fromInt(obj.getOrThrow(config, "type", contextName)); + return when(t) { LiveEventType.COMMENT -> LiveEventComment.fromV8(config, obj); LiveEventType.EMOJIS -> LiveEventEmojis.fromV8(config, obj); LiveEventType.DONATION -> LiveEventDonation.fromV8(config, obj); LiveEventType.VIEWCOUNT -> LiveEventViewCount.fromV8(config, obj); LiveEventType.RAID -> LiveEventRaid.fromV8(config, obj); - else -> throw NotImplementedError("Unknown type ${type}"); + else -> throw NotImplementedError("Unknown type $t"); } } } diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/playlists/IPlatformPlaylist.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/playlists/IPlatformPlaylist.kt index d0470cfe..52fc46e0 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/models/playlists/IPlatformPlaylist.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/playlists/IPlatformPlaylist.kt @@ -1,9 +1,6 @@ package com.futo.platformplayer.api.media.models.playlists -import com.futo.platformplayer.api.media.models.Thumbnails import com.futo.platformplayer.api.media.models.contents.IPlatformContent -import com.futo.platformplayer.api.media.models.video.IPlatformVideo -import com.futo.platformplayer.api.media.structures.IPager interface IPlatformPlaylist : IPlatformContent { val thumbnail: String?; diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/post/IPlatformPostDetails.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/post/IPlatformPostDetails.kt index 985e8697..6f591407 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/models/post/IPlatformPostDetails.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/post/IPlatformPostDetails.kt @@ -2,10 +2,6 @@ package com.futo.platformplayer.api.media.models.post import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails import com.futo.platformplayer.api.media.models.ratings.IRating -import com.futo.platformplayer.api.media.models.streams.IVideoSourceDescriptor -import com.futo.platformplayer.api.media.models.streams.sources.* -import com.futo.platformplayer.api.media.models.subtitles.ISubtitleSource -import com.futo.platformplayer.api.media.models.video.IPlatformVideo /** * A detailed video model with data including video/audio sources diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/ratings/IRating.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/ratings/IRating.kt index 4d560b9f..75286b44 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/models/ratings/IRating.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/ratings/IRating.kt @@ -14,14 +14,13 @@ interface IRating { companion object { fun fromV8OrDefault(config: IV8PluginConfig, obj: V8Value?, default: IRating) = obj.orDefault(default) { fromV8(config, it as V8ValueObject) }; - fun fromV8(config: IV8PluginConfig, obj: V8ValueObject, contextName: String = "Unknown") : IRating { - val contextName = "Rating"; - val type = RatingType.fromInt(obj.getOrThrow(config, "type", contextName)); - return when(type) { + fun fromV8(config: IV8PluginConfig, obj: V8ValueObject, contextName: String = "Rating") : IRating { + val t = RatingType.fromInt(obj.getOrThrow(config, "type", contextName)); + return when(t) { RatingType.LIKES -> RatingLikes.fromV8(config, obj); RatingType.LIKEDISLIKES -> RatingLikeDislikes.fromV8(config, obj); RatingType.SCALE -> RatingScaler.fromV8(config, obj); - else -> throw NotImplementedError("Unknown type ${type}"); + else -> throw NotImplementedError("Unknown type $t"); } } } diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/subtitles/ISubtitleSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/subtitles/ISubtitleSource.kt index e210774d..fd6e423f 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/models/subtitles/ISubtitleSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/subtitles/ISubtitleSource.kt @@ -1,8 +1,6 @@ package com.futo.platformplayer.api.media.models.subtitles import android.net.Uri -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Deferred interface ISubtitleSource { val name: String; diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/video/IPlatformVideoDetails.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/video/IPlatformVideoDetails.kt index 433b44c4..37baf41f 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/models/video/IPlatformVideoDetails.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/video/IPlatformVideoDetails.kt @@ -1,13 +1,12 @@ package com.futo.platformplayer.api.media.models.video -import com.futo.platformplayer.api.media.IPlatformClient -import com.futo.platformplayer.api.media.models.comments.IPlatformComment import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails import com.futo.platformplayer.api.media.models.ratings.IRating import com.futo.platformplayer.api.media.models.streams.IVideoSourceDescriptor -import com.futo.platformplayer.api.media.models.streams.sources.* +import com.futo.platformplayer.api.media.models.streams.sources.IDashManifestSource +import com.futo.platformplayer.api.media.models.streams.sources.IHLSManifestSource +import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource import com.futo.platformplayer.api.media.models.subtitles.ISubtitleSource -import com.futo.platformplayer.api.media.structures.IPager /** * A detailed video model with data including video/audio sources diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/video/SerializedVideoMuxedSourceDescriptor.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/video/SerializedVideoMuxedSourceDescriptor.kt index ad2d14ec..65b7cdc9 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/models/video/SerializedVideoMuxedSourceDescriptor.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/video/SerializedVideoMuxedSourceDescriptor.kt @@ -8,6 +8,5 @@ import com.futo.platformplayer.api.media.models.streams.sources.VideoUrlSource class SerializedVideoMuxedSourceDescriptor( val _videoSources: Array ): VideoMuxedSourceDescriptor(), ISerializedVideoSourceDescriptor { - @kotlinx.serialization.Transient override val videoSources: Array get() = _videoSources.map { it }.toTypedArray(); }; \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/video/SerializedVideoUnmuxedSourceDescriptor.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/video/SerializedVideoUnmuxedSourceDescriptor.kt index f7a84c05..62924647 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/models/video/SerializedVideoUnmuxedSourceDescriptor.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/video/SerializedVideoUnmuxedSourceDescriptor.kt @@ -1,15 +1,16 @@ package com.futo.platformplayer.api.media.models.video import com.futo.platformplayer.api.media.models.streams.VideoUnMuxedSourceDescriptor -import com.futo.platformplayer.api.media.models.streams.sources.* +import com.futo.platformplayer.api.media.models.streams.sources.AudioUrlSource +import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource +import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource +import com.futo.platformplayer.api.media.models.streams.sources.VideoUrlSource @kotlinx.serialization.Serializable class SerializedVideoNonMuxedSourceDescriptor( val _videoSources: Array, val _audioSources: Array ): VideoUnMuxedSourceDescriptor(), ISerializedVideoSourceDescriptor { - @kotlinx.serialization.Transient override val videoSources: Array get() = _videoSources.map { it }.toTypedArray(); - @kotlinx.serialization.Transient override val audioSources: Array get() = _audioSources.map { it }.toTypedArray(); }; \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/DevJSClient.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/DevJSClient.kt index 9e5ffa06..1b929293 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/DevJSClient.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/DevJSClient.kt @@ -1,14 +1,14 @@ package com.futo.platformplayer.api.media.platforms.js import android.content.Context -import com.futo.platformplayer.states.StateDeveloper import com.futo.platformplayer.api.media.models.channels.IPlatformChannel import com.futo.platformplayer.api.media.models.comments.IPlatformComment import com.futo.platformplayer.api.media.models.contents.IPlatformContent import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails import com.futo.platformplayer.api.media.structures.IPager import com.futo.platformplayer.states.StateApp -import java.util.* +import com.futo.platformplayer.states.StateDeveloper +import java.util.UUID class DevJSClient : JSClient { override val id: String @@ -26,8 +26,8 @@ class DevJSClient : JSClient { _captcha = captcha; this.devID = devID ?: UUID.randomUUID().toString().substring(0, 5); - onCaptchaException.subscribe { client, captcha -> - StateApp.instance.handleCaptchaException(client, captcha); + onCaptchaException.subscribe { client, c -> + StateApp.instance.handleCaptchaException(client, c); } } //TODO: Misisng auth/captcha pass on purpose? @@ -37,8 +37,8 @@ class DevJSClient : JSClient { _captcha = captcha; this.devID = devID ?: UUID.randomUUID().toString().substring(0, 5); - onCaptchaException.subscribe { client, captcha -> - StateApp.instance.handleCaptchaException(client, captcha); + onCaptchaException.subscribe { client, c -> + StateApp.instance.handleCaptchaException(client, c); } } diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt index b3450002..f5f72686 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt @@ -4,12 +4,9 @@ import android.content.Context import com.caoccao.javet.values.V8Value import com.caoccao.javet.values.primitive.V8ValueBoolean import com.caoccao.javet.values.primitive.V8ValueInteger -import com.caoccao.javet.values.primitive.V8ValueNull import com.caoccao.javet.values.primitive.V8ValueString import com.caoccao.javet.values.reference.V8ValueArray import com.caoccao.javet.values.reference.V8ValueObject -import com.futo.platformplayer.states.StatePlatform -import com.futo.platformplayer.states.StatePlugins import com.futo.platformplayer.api.media.IPlatformClient import com.futo.platformplayer.api.media.PlatformClientCapabilities import com.futo.platformplayer.api.media.models.PlatformAuthorLink @@ -23,23 +20,38 @@ import com.futo.platformplayer.api.media.models.live.ILiveChatWindowDescriptor import com.futo.platformplayer.api.media.models.live.IPlatformLiveEvent import com.futo.platformplayer.api.media.models.playback.IPlaybackTracker import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylistDetails -import com.futo.platformplayer.api.media.platforms.js.internal.* -import com.futo.platformplayer.api.media.platforms.js.models.* +import com.futo.platformplayer.api.media.platforms.js.internal.JSCallDocs +import com.futo.platformplayer.api.media.platforms.js.internal.JSDocs +import com.futo.platformplayer.api.media.platforms.js.internal.JSDocsParameter +import com.futo.platformplayer.api.media.platforms.js.internal.JSHttpClient +import com.futo.platformplayer.api.media.platforms.js.internal.JSOptional +import com.futo.platformplayer.api.media.platforms.js.internal.JSParameterDocs +import com.futo.platformplayer.api.media.platforms.js.models.IJSContentDetails +import com.futo.platformplayer.api.media.platforms.js.models.JSChannel +import com.futo.platformplayer.api.media.platforms.js.models.JSChannelPager +import com.futo.platformplayer.api.media.platforms.js.models.JSChapter +import com.futo.platformplayer.api.media.platforms.js.models.JSComment +import com.futo.platformplayer.api.media.platforms.js.models.JSCommentPager +import com.futo.platformplayer.api.media.platforms.js.models.JSContentPager +import com.futo.platformplayer.api.media.platforms.js.models.JSLiveChatWindowDescriptor +import com.futo.platformplayer.api.media.platforms.js.models.JSLiveEventPager +import com.futo.platformplayer.api.media.platforms.js.models.JSPlaybackTracker +import com.futo.platformplayer.api.media.platforms.js.models.JSPlaylistDetails import com.futo.platformplayer.api.media.structures.EmptyPager import com.futo.platformplayer.api.media.structures.IPager import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.constructs.Event2 import com.futo.platformplayer.engine.V8Plugin import com.futo.platformplayer.engine.exceptions.PluginEngineException -import com.futo.platformplayer.engine.exceptions.PluginEngineStoppedException import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException import com.futo.platformplayer.engine.exceptions.ScriptImplementationException -import com.futo.platformplayer.engine.exceptions.ScriptLoginRequiredException import com.futo.platformplayer.engine.exceptions.ScriptValidationException import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.models.ImageVariable import com.futo.platformplayer.states.AnnouncementType import com.futo.platformplayer.states.StateAnnouncement +import com.futo.platformplayer.states.StatePlatform +import com.futo.platformplayer.states.StatePlugins import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import java.time.OffsetDateTime @@ -50,7 +62,7 @@ open class JSClient : IPlatformClient { val config: SourcePluginConfig; protected val _context: Context; private val _plugin: V8Plugin; - private val plugin: V8Plugin get() = _plugin ?: throw IllegalStateException("Client not enabled"); + private val plugin: V8Plugin get() = _plugin var descriptor: SourcePluginDescriptor private set; diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginConfig.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginConfig.kt index 2d39a2a5..b35f9ade 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginConfig.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginConfig.kt @@ -5,9 +5,8 @@ import com.futo.platformplayer.SignatureProvider import com.futo.platformplayer.api.media.Serializer import com.futo.platformplayer.engine.IV8PluginConfig import com.futo.platformplayer.states.StatePlugins -import kotlinx.serialization.decodeFromString import java.net.URL -import java.util.* +import java.util.UUID @kotlinx.serialization.Serializable class SourcePluginConfig( @@ -149,7 +148,6 @@ class SourcePluginConfig( val warningDialog: String? = null, val options: List? = null ) { - @kotlinx.serialization.Transient val variableOrName: String get() = variable ?: name; } } \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginDescriptor.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginDescriptor.kt index 8bcb51b3..5833295e 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginDescriptor.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginDescriptor.kt @@ -2,7 +2,6 @@ package com.futo.platformplayer.api.media.platforms.js import com.futo.platformplayer.R import com.futo.platformplayer.constructs.Event0 -import com.futo.platformplayer.serializers.FlexibleBooleanSerializer import com.futo.platformplayer.views.fields.DropdownFieldOptions import com.futo.platformplayer.views.fields.FieldForm import com.futo.platformplayer.views.fields.FormField @@ -107,9 +106,9 @@ class SourcePluginDescriptor { fun loadDefaults(config: SourcePluginConfig) { if(tabEnabled.enableHome == null) - tabEnabled.enableHome = config.enableInHome ?: true; + tabEnabled.enableHome = config.enableInHome if(tabEnabled.enableSearch == null) - tabEnabled.enableSearch = config.enableInSearch ?: true; + tabEnabled.enableSearch = config.enableInSearch } } diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/internal/JSHttpClient.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/internal/JSHttpClient.kt index af7291ae..05df143f 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/internal/JSHttpClient.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/internal/JSHttpClient.kt @@ -1,7 +1,5 @@ package com.futo.platformplayer.api.media.platforms.js.internal -import android.net.Uri -import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.api.http.ManagedHttpClient import com.futo.platformplayer.api.media.platforms.js.JSClient import com.futo.platformplayer.api.media.platforms.js.SourceAuth @@ -47,10 +45,7 @@ class JSHttpClient : ManagedHttpClient { override fun clone(): ManagedHttpClient { val newClient = JSHttpClient(_jsClient, _auth); - newClient._currentCookieMap = if(_currentCookieMap != null) - HashMap(_currentCookieMap.toList().associate { Pair(it.first, HashMap(it.second)) }) - else - hashMapOf(); + newClient._currentCookieMap = HashMap(_currentCookieMap.toList().associate { Pair(it.first, HashMap(it.second)) }) return newClient; } @@ -69,10 +64,10 @@ class JSHttpClient : ManagedHttpClient { } if(doApplyCookies) { - if (!_currentCookieMap.isNullOrEmpty()) { + if (_currentCookieMap.isNotEmpty()) { val cookiesToApply = hashMapOf(); - synchronized(_currentCookieMap!!) { - for(cookie in _currentCookieMap!! + synchronized(_currentCookieMap) { + for(cookie in _currentCookieMap .filter { domain.matchesDomain(it.key) } .flatMap { it.value.toList() }) cookiesToApply[cookie.first] = cookie.second; @@ -92,11 +87,11 @@ class JSHttpClient : ManagedHttpClient { } if(_jsClient != null) - _jsClient?.validateUrlOrThrow(request.url.toString()); + _jsClient.validateUrlOrThrow(request.url.toString()); else if (_jsConfig != null && !_jsConfig.isUrlAllowed(request.url.toString())) throw ScriptImplementationException(_jsConfig, "Attempted to access non-whitelisted url: ${request.url.toString()}\nAdd it to your config"); - return newBuilder?.let { it.build() } ?: request; + return newBuilder?.build() ?: request; } override fun afterRequest(resp: okhttp3.Response): okhttp3.Response { @@ -107,84 +102,48 @@ class JSHttpClient : ManagedHttpClient { "." + domainParts.drop(domainParts.size - 2).joinToString("."); for (header in resp.headers) { if ((_auth != null || _currentCookieMap.isNotEmpty()) && header.first.lowercase() == "set-cookie") { - //val newCookies = cookieStringToMap(header.second.split("; ")); val cookie = cookieStringToPair(header.second); - //for (cookie in newCookies) { - var cookieValue = cookie.second; - var domainToUse = domain; + var cookieValue = cookie.second; + var domainToUse = domain; - if (!cookie.first.isNullOrEmpty() && !cookie.second.isNullOrEmpty()) { - val cookieParts = cookie.second.split(";"); - if (cookieParts.size == 0) - continue; - cookieValue = cookieParts[0].trim(); + if (cookie.first.isNotEmpty() && cookie.second.isNotEmpty()) { + val cookieParts = cookie.second.split(";"); + if (cookieParts.size == 0) + continue; + cookieValue = cookieParts[0].trim(); - val cookieVariables = cookieParts.drop(1).map { - val splitIndex = it.indexOf("="); - if (splitIndex < 0) - return@map Pair(it.trim().lowercase(), ""); - return@map Pair( - it.substring(0, splitIndex).lowercase().trim(), - it.substring(splitIndex + 1).trim() - ); - }.toMap(); - domainToUse = if (cookieVariables.containsKey("domain")) - cookieVariables["domain"]!!.lowercase(); - else defaultCookieDomain; - } + val cookieVariables = cookieParts.drop(1).map { + val splitIndex = it.indexOf("="); + if (splitIndex < 0) + return@map Pair(it.trim().lowercase(), ""); + return@map Pair( + it.substring(0, splitIndex).lowercase().trim(), + it.substring(splitIndex + 1).trim() + ); + }.toMap(); + domainToUse = if (cookieVariables.containsKey("domain")) + cookieVariables["domain"]!!.lowercase(); + else defaultCookieDomain; + } - val cookieMap = if (_currentCookieMap!!.containsKey(domainToUse)) - _currentCookieMap!![domainToUse]!!; - else { - val newMap = hashMapOf(); - _currentCookieMap!!.put(domainToUse, newMap) - newMap; - } - if(cookieMap.containsKey(cookie.first) || doAllowNewCookies) - cookieMap.put(cookie.first, cookieValue); - //} + val cookieMap = if (_currentCookieMap.containsKey(domainToUse)) + _currentCookieMap[domainToUse]!!; + else { + val newMap = hashMapOf(); + _currentCookieMap[domainToUse] = newMap + newMap; + } + if(cookieMap.containsKey(cookie.first) || doAllowNewCookies) + cookieMap[cookie.first] = cookieValue; } } } return resp; } - - private fun cookieStringToMap(parts: List): Map { - val map = hashMapOf(); - for(cookie in parts) { - val pair = cookieStringToPair(cookie) - map.put(pair.first, pair.second); - } - return map; - } private fun cookieStringToPair(cookie: String): Pair { val cookieKey = cookie.substring(0, cookie.indexOf("=")); val cookieVal = cookie.substring(cookie.indexOf("=") + 1); return Pair(cookieKey.trim(), cookieVal.trim()); } - - //Prints out code for test reproduction.. - fun printTestCode(url: String, body: ByteArray?, headers: Map, cookieString: String, allHeaders: Map? = null) { - var code = "Code: \n"; - code += "\nurl = \"${url}\";"; - if(body != null) - code += "\nbody = \"${String(body).replace("\"", "\\\"")}\";"; - if(headers != null) - for(header in headers) { - code += "\nclient.Headers.Add(\"${header.key}\", \"${header.value}\");"; - } - if(cookieString != null) - code += "\nclient.Headers.Add(\"Cookie\", \"${cookieString}\");"; - - if(allHeaders != null) { - code += "\n//OTHER HEADERS:" - for (header in allHeaders) { - code += "\nclient.Headers.Add(\"${header.key}\", \"${header.value}\");"; - } - } - - Logger.i("Testing", code); - } - } \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSSource.kt index c4cae894..fb7f05dd 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSSource.kt @@ -1,3 +1,5 @@ +@file:Suppress("DEPRECATION") + package com.futo.platformplayer.api.media.platforms.js.models.sources import com.caoccao.javet.values.V8Value diff --git a/app/src/main/java/com/futo/platformplayer/api/media/structures/IAsyncPager.kt b/app/src/main/java/com/futo/platformplayer/api/media/structures/IAsyncPager.kt index 9a9ebd65..34900c86 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/structures/IAsyncPager.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/structures/IAsyncPager.kt @@ -1,7 +1,5 @@ package com.futo.platformplayer.api.media.structures -import kotlinx.coroutines.CoroutineScope - /** * A Pager interface that implements a suspended manner of nextPage */ diff --git a/app/src/main/java/com/futo/platformplayer/api/media/structures/IRefreshPager.kt b/app/src/main/java/com/futo/platformplayer/api/media/structures/IRefreshPager.kt index 390895bd..34a9e41a 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/structures/IRefreshPager.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/structures/IRefreshPager.kt @@ -1,6 +1,5 @@ package com.futo.platformplayer.api.media.structures -import com.futo.platformplayer.api.media.models.contents.IPlatformContent import com.futo.platformplayer.constructs.Event1 /** diff --git a/app/src/main/java/com/futo/platformplayer/api/media/structures/MultiRefreshPager.kt b/app/src/main/java/com/futo/platformplayer/api/media/structures/MultiRefreshPager.kt index 10f76d6e..ba98a8d1 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/structures/MultiRefreshPager.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/structures/MultiRefreshPager.kt @@ -5,6 +5,7 @@ import com.futo.platformplayer.api.media.structures.ReusablePager.Companion.asRe import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.logging.Logger import kotlinx.coroutines.Deferred +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking /** @@ -25,6 +26,7 @@ abstract class MultiRefreshPager: IRefreshPager, IPager { private val _pending: MutableList?>>; + @OptIn(ExperimentalCoroutinesApi::class) constructor(pagers: List>, pendingPagers: List?>>, placeholderPagers: List>? = null) { _pagersReusable = pagers.map { ReusablePager(it) }.toMutableList(); _totalPagers = pagers.size + pendingPagers.size; @@ -100,7 +102,7 @@ abstract class MultiRefreshPager: IRefreshPager, IPager { } private fun getCurrentSubPagers(): List> { - val reusableWindows = _pagersReusable.map { it.getWindow() as IPager }; + val reusableWindows = _pagersReusable.map { it.getWindow() }; val placeholderWindows = synchronized(_pending) { _placeHolderPagersPaired.filter { _pending.contains(it.key) }.values } diff --git a/app/src/main/java/com/futo/platformplayer/api/media/structures/SingleAsyncItemPager.kt b/app/src/main/java/com/futo/platformplayer/api/media/structures/SingleAsyncItemPager.kt index beb841e8..592ce2e5 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/structures/SingleAsyncItemPager.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/structures/SingleAsyncItemPager.kt @@ -100,7 +100,7 @@ class SingleAsyncItemPager { private fun fillDeferredUntil(i: Int): Int { val startPos = _requestedPageItems.size; - for(i in _requestedPageItems.size..i) { + for(v in _requestedPageItems.size..i) { _requestedPageItems.add(CompletableDeferred()); } return startPos; diff --git a/app/src/main/java/com/futo/platformplayer/casting/ChomecastCastingDevice.kt b/app/src/main/java/com/futo/platformplayer/casting/ChomecastCastingDevice.kt index b0335d6d..09f4fbab 100644 --- a/app/src/main/java/com/futo/platformplayer/casting/ChomecastCastingDevice.kt +++ b/app/src/main/java/com/futo/platformplayer/casting/ChomecastCastingDevice.kt @@ -2,13 +2,17 @@ package com.futo.platformplayer.casting import android.os.Looper import android.util.Log -import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.getConnectedSocket +import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.models.CastingDeviceInfo import com.futo.platformplayer.protos.ChromeCast import com.futo.platformplayer.toHexString import com.futo.platformplayer.toInetAddress -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.cancel +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch import org.json.JSONObject import java.io.DataInputStream import java.io.DataOutputStream @@ -502,10 +506,10 @@ class ChromecastCastingDevice : CastingDevice { } val volume = status.getJSONObject("volume"); - val volumeControlType = volume.getString("controlType"); + //val volumeControlType = volume.getString("controlType"); val volumeLevel = volume.getString("level").toDouble(); 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; Logger.i(TAG, "Status update received volume (level: $volumeLevel, muted: $volumeMuted)"); diff --git a/app/src/main/java/com/futo/platformplayer/casting/StateCasting.kt b/app/src/main/java/com/futo/platformplayer/casting/StateCasting.kt index 39c27308..9ecb7b9d 100644 --- a/app/src/main/java/com/futo/platformplayer/casting/StateCasting.kt +++ b/app/src/main/java/com/futo/platformplayer/casting/StateCasting.kt @@ -5,12 +5,23 @@ import android.content.Context import android.net.Uri import android.os.Looper import android.util.Base64 -import com.futo.platformplayer.* -import com.futo.platformplayer.activities.MainActivity +import com.futo.platformplayer.BuildConfig +import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.api.http.ManagedHttpClient import com.futo.platformplayer.api.http.server.ManagedHttpServer -import com.futo.platformplayer.api.http.server.handlers.* -import com.futo.platformplayer.api.media.models.streams.sources.* +import com.futo.platformplayer.api.http.server.handlers.HttpConstantHandler +import com.futo.platformplayer.api.http.server.handlers.HttpFileHandler +import com.futo.platformplayer.api.http.server.handlers.HttpFuntionHandler +import com.futo.platformplayer.api.http.server.handlers.HttpProxyHandler +import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource +import com.futo.platformplayer.api.media.models.streams.sources.IAudioUrlSource +import com.futo.platformplayer.api.media.models.streams.sources.IHLSManifestAudioSource +import com.futo.platformplayer.api.media.models.streams.sources.IHLSManifestSource +import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource +import com.futo.platformplayer.api.media.models.streams.sources.IVideoUrlSource +import com.futo.platformplayer.api.media.models.streams.sources.LocalAudioSource +import com.futo.platformplayer.api.media.models.streams.sources.LocalSubtitleSource +import com.futo.platformplayer.api.media.models.streams.sources.LocalVideoSource import com.futo.platformplayer.api.media.models.subtitles.ISubtitleSource import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails import com.futo.platformplayer.builders.DashBuilder @@ -21,18 +32,20 @@ import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.models.CastingDeviceInfo import com.futo.platformplayer.parsers.HLS import com.futo.platformplayer.states.StateApp -import kotlinx.coroutines.* +import com.futo.platformplayer.stores.CastingDeviceInfoStorage +import com.futo.platformplayer.stores.FragmentedStorage +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json import java.net.InetAddress -import java.util.* +import java.util.UUID import javax.jmdns.JmDNS import javax.jmdns.ServiceEvent import javax.jmdns.ServiceListener -import kotlin.collections.HashMap -import com.futo.platformplayer.stores.CastingDeviceInfoStorage -import com.futo.platformplayer.stores.FragmentedStorage -import kotlinx.serialization.Serializable -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.json.Json import javax.jmdns.ServiceTypeListener class StateCasting { @@ -213,7 +226,7 @@ class StateCasting { } } _castServer.start(); - enableDeveloper(context.contentResolver, true); + enableDeveloper(true); Logger.i(TAG, "CastingService started."); } @@ -1077,7 +1090,6 @@ class StateCasting { CastProtocolType.FCAST -> { FCastCastingDevice(deviceInfo); } - else -> throw Exception("${deviceInfo.type} is not a valid casting protocol") } } @@ -1172,7 +1184,7 @@ class StateCasting { invokeEvents?.let { _scopeMain.launch { it(); }; }; } - fun enableDeveloper(contentResolver: ContentResolver, enableDev: Boolean){ + fun enableDeveloper(enableDev: Boolean){ _castServer.removeAllHandlers("dev"); if(enableDev) { _castServer.addHandler(HttpFuntionHandler("GET", "/dashPlayer") { context -> diff --git a/app/src/main/java/com/futo/platformplayer/constructs/BatchedTaskHandler.kt b/app/src/main/java/com/futo/platformplayer/constructs/BatchedTaskHandler.kt index f656fc40..e7714e02 100644 --- a/app/src/main/java/com/futo/platformplayer/constructs/BatchedTaskHandler.kt +++ b/app/src/main/java/com/futo/platformplayer/constructs/BatchedTaskHandler.kt @@ -1,8 +1,10 @@ package com.futo.platformplayer.constructs -import android.provider.Settings.Global -import com.futo.platformplayer.states.StateApp -import kotlinx.coroutines.* +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.async class BatchedTaskHandler { @@ -25,14 +27,14 @@ class BatchedTaskHandler { } fun execute(para : TParameter) : Deferred { - var result: TResult? = null; + var result: TResult?; var taskResult: Deferred? = null; synchronized(_batchLock) { result = _taskGetCache?.invoke(para); if(result == null) { taskResult = _batchRequest[para]; - if(taskResult?.isCancelled ?: false) { + if(taskResult?.isCancelled == true) { _batchRequest.remove(para); taskResult = null; } diff --git a/app/src/main/java/com/futo/platformplayer/constructs/TaskHandler.kt b/app/src/main/java/com/futo/platformplayer/constructs/TaskHandler.kt index 3036f667..b590fe6e 100644 --- a/app/src/main/java/com/futo/platformplayer/constructs/TaskHandler.kt +++ b/app/src/main/java/com/futo/platformplayer/constructs/TaskHandler.kt @@ -2,7 +2,11 @@ package com.futo.platformplayer.constructs import android.util.Log import com.futo.platformplayer.logging.Logger -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext class TaskHandler { private val TAG = "TaskHandler" @@ -16,7 +20,7 @@ class TaskHandler { private val _task: suspend ((parameter: TParameter) -> TResult); constructor(claz : Class, scope: ()->CoroutineScope) { - _task = { claz.newInstance() }; + _task = { claz.getDeclaredConstructor().newInstance() }; _scope = scope; _dispatcher = Dispatchers.IO; } @@ -32,7 +36,7 @@ class TaskHandler { } inline fun exception(noinline cb : (T)->Unit) : TaskHandler { - onError.subscribeConditional { ex, para -> + onError.subscribeConditional { ex, _ -> if(ex is T) { cb(ex); return@subscribeConditional true; @@ -76,8 +80,7 @@ class TaskHandler { try { onSuccess.emit(result); handled = true; - } - catch (e: Throwable) { + } catch (e: Throwable) { Logger.w(TAG, "Handled exception in TaskHandler onSuccess.", e); onError.emit(e, parameter); handled = true; diff --git a/app/src/main/java/com/futo/platformplayer/debug/Stopwatch.kt b/app/src/main/java/com/futo/platformplayer/debug/Stopwatch.kt index 0924d19b..fa0257a5 100644 --- a/app/src/main/java/com/futo/platformplayer/debug/Stopwatch.kt +++ b/app/src/main/java/com/futo/platformplayer/debug/Stopwatch.kt @@ -1,9 +1,10 @@ package com.futo.platformplayer.debug -import com.google.android.exoplayer2.util.Log +import com.futo.platformplayer.logging.Logger + class Stopwatch { - var startTime = System.nanoTime() + private var startTime = System.nanoTime() val elapsedMs: Double get() { val now = System.nanoTime() @@ -19,7 +20,7 @@ class Stopwatch { val now = System.nanoTime() val diff = now - startTime val diffMs = diff / 1000000.0 - Log.d(tag, "STOPWATCH $message ${diffMs}ms") + Logger.i(tag, "STOPWATCH $message ${diffMs}ms") startTime = now return diff } diff --git a/app/src/main/java/com/futo/platformplayer/developer/DeveloperEndpoints.kt b/app/src/main/java/com/futo/platformplayer/developer/DeveloperEndpoints.kt index 92a48ebf..0245d9bc 100644 --- a/app/src/main/java/com/futo/platformplayer/developer/DeveloperEndpoints.kt +++ b/app/src/main/java/com/futo/platformplayer/developer/DeveloperEndpoints.kt @@ -80,11 +80,11 @@ class DeveloperEndpoints(private val context: Context) { //Files @HttpGET("/dev", "text/html") - val devTestHtml = StateAssets.readAsset(context, "devportal/index.html", true); + val devTestHtml = StateAssets.readAsset(context, "devportal/index.html"); @HttpGET("/source.js", "application/javascript") - val devSourceJS = StateAssets.readAsset(context, "scripts/source.js", true); + val devSourceJS = StateAssets.readAsset(context, "scripts/source.js"); @HttpGET("/dev_bridge.js", "application/javascript") - val devBridgeJS = StateAssets.readAsset(context, "devportal/dev_bridge.js", true); + val devBridgeJS = StateAssets.readAsset(context, "devportal/dev_bridge.js"); @HttpGET("/source_docs.json", "application/json") val devSourceDocsJson = Json.encodeToString(JSClient.getJSDocs()); @HttpGET("/source_docs.js", "application/javascript") @@ -98,7 +98,7 @@ class DeveloperEndpoints(private val context: Context) { //@HttpGET("/dependencies/vuetify.min.css", "text/css") //val depVuetifyCss = StateAssets.readAsset(context, "devportal/dependencies/vuetify.min.css", true); @HttpGET("/dependencies/FutoMainLogo.svg", "image/svg+xml") - val depFutoLogo = StateAssets.readAsset(context, "devportal/dependencies/FutoMainLogo.svg", true); + val depFutoLogo = StateAssets.readAsset(context, "devportal/dependencies/FutoMainLogo.svg"); @HttpGET("/reference_plugin.d.ts", "text/plain") fun devSourceTSWithRefs(httpContext: HttpContext) { @@ -107,7 +107,7 @@ class DeveloperEndpoints(private val context: Context) { builder.appendLine("//Reference Scriptfile"); builder.appendLine("//Intended exclusively for auto-complete in your IDE, not for execution"); - builder.appendLine(StateAssets.readAsset(context, "devportal/plugin.d.ts", true)); + builder.appendLine(StateAssets.readAsset(context, "devportal/plugin.d.ts")); httpContext.respondCode(200, builder.toString(), "text/plain"); } @@ -119,7 +119,7 @@ class DeveloperEndpoints(private val context: Context) { builder.appendLine("//Reference Scriptfile"); builder.appendLine("//Intended exclusively for auto-complete in your IDE, not for execution"); - builder.appendLine(StateAssets.readAsset(context, "scripts/source.js", true)); + builder.appendLine(StateAssets.readAsset(context, "scripts/source.js")); for(pack in testPluginOrThrow.getPackages()) { builder.appendLine(); @@ -194,7 +194,7 @@ class DeveloperEndpoints(private val context: Context) { context.respondJson(200, testPluginOrThrow.getPackageVariables()); } catch(ex: Throwable) { - context.respondCode(500, (ex::class.simpleName + ":" + ex.message) ?: "", "text/plain") + context.respondCode(500, (ex::class.simpleName + ":" + ex.message), "text/plain") } } @HttpPOST("/plugin/cleanTestPlugin") @@ -204,7 +204,7 @@ class DeveloperEndpoints(private val context: Context) { context.respondCode(200); } catch(ex: Throwable) { - context.respondCode(500, (ex::class.simpleName + ":" + ex.message) ?: "", "text/plain") + context.respondCode(500, (ex::class.simpleName + ":" + ex.message), "text/plain") } } @HttpPOST("/plugin/captchaTestPlugin") @@ -226,7 +226,7 @@ class DeveloperEndpoints(private val context: Context) { context.respondCode(200, "Captcha started"); } catch(ex: Throwable) { - context.respondCode(500, (ex::class.simpleName + ":" + ex.message) ?: "", "text/plain") + context.respondCode(500, (ex::class.simpleName + ":" + ex.message), "text/plain") } } @HttpGET("/plugin/loginTestPlugin") @@ -246,7 +246,7 @@ class DeveloperEndpoints(private val context: Context) { context.respondCode(200, "Login started"); } catch(ex: Throwable) { - context.respondCode(500, (ex::class.simpleName + ":" + ex.message) ?: "", "text/plain") + context.respondCode(500, (ex::class.simpleName + ":" + ex.message), "text/plain") } } @HttpGET("/plugin/logoutTestPlugin") @@ -258,7 +258,7 @@ class DeveloperEndpoints(private val context: Context) { context.respondCode(200, "Logged out"); } catch(ex: Throwable) { - context.respondCode(500, (ex::class.simpleName + ":" + ex.message) ?: "", "text/plain") + context.respondCode(500, (ex::class.simpleName + ":" + ex.message), "text/plain") } } @@ -269,7 +269,7 @@ class DeveloperEndpoints(private val context: Context) { context.respondCode(200, if(isLoggedIn) "true" else "false", "application/json"); } catch(ex: Throwable) { - context.respondCode(500, (ex::class.simpleName + ":" + ex.message) ?: "", "text/plain") + context.respondCode(500, (ex::class.simpleName + ":" + ex.message), "text/plain") } } @@ -319,7 +319,7 @@ class DeveloperEndpoints(private val context: Context) { catch(invocation: InvocationTargetException) { val innerException = invocation.targetException; Logger.e("DeveloperEndpoints", innerException.message, innerException); - context.respondCode(500, innerException::class.simpleName + ":" + innerException.message ?: "", "text/plain") + context.respondCode(500, innerException::class.simpleName + ":" + innerException.message, "text/plain") } catch(ilEx: IllegalArgumentException) { if(ilEx.message?.contains("does not exist") ?: false) { @@ -327,12 +327,12 @@ class DeveloperEndpoints(private val context: Context) { } else { Logger.e("DeveloperEndpoints", ilEx.message, ilEx); - context.respondCode(500, ilEx::class.simpleName + ":" + ilEx.message ?: "", "text/plain") + context.respondCode(500, ilEx::class.simpleName + ":" + ilEx.message, "text/plain") } } catch(ex: Throwable) { Logger.e("DeveloperEndpoints", ex.message, ex); - context.respondCode(500, ex::class.simpleName + ":" + ex.message ?: "", "text/plain") + context.respondCode(500, ex::class.simpleName + ":" + ex.message, "text/plain") } } @HttpGET("/plugin/remoteProp") @@ -362,12 +362,12 @@ class DeveloperEndpoints(private val context: Context) { } else { Logger.e("DeveloperEndpoints", ilEx.message, ilEx); - context.respondCode(500, ilEx::class.simpleName + ":" + ilEx.message ?: "", "text/plain") + context.respondCode(500, ilEx::class.simpleName + ":" + ilEx.message, "text/plain") } } catch(ex: Throwable) { Logger.e("DeveloperEndpoints", ex.message, ex); - context.respondCode(500, ex::class.simpleName + ":" + ex.message ?: "", "text/plain") + context.respondCode(500, ex::class.simpleName + ":" + ex.message, "text/plain") } } @@ -398,7 +398,7 @@ class DeveloperEndpoints(private val context: Context) { fun pluginLoadDevPlugin(context: HttpContext) { val config = context.readContentJson() try { - val script = _client.get(config.absoluteScriptUrl!!); + val script = _client.get(config.absoluteScriptUrl); if(!script.isOk) throw IllegalStateException("URL ${config.scriptUrl} return code ${script.code}"); if(script.body == null) @@ -409,7 +409,7 @@ class DeveloperEndpoints(private val context: Context) { } catch(ex: Exception) { Logger.e("DeveloperEndpoints", ex.message, ex); - context.respondCode(500, ex::class.simpleName + ":" + ex.message ?: "", "text/plain") + context.respondCode(500, ex::class.simpleName + ":" + ex.message, "text/plain") } } diff --git a/app/src/main/java/com/futo/platformplayer/dialogs/AutoUpdateDialog.kt b/app/src/main/java/com/futo/platformplayer/dialogs/AutoUpdateDialog.kt index b8e15806..3f7fda29 100644 --- a/app/src/main/java/com/futo/platformplayer/dialogs/AutoUpdateDialog.kt +++ b/app/src/main/java/com/futo/platformplayer/dialogs/AutoUpdateDialog.kt @@ -1,7 +1,9 @@ package com.futo.platformplayer.dialogs import android.app.AlertDialog -import android.app.PendingIntent.* +import android.app.PendingIntent.FLAG_MUTABLE +import android.app.PendingIntent.FLAG_UPDATE_CURRENT +import android.app.PendingIntent.getBroadcast import android.content.Context import android.content.Intent import android.content.pm.PackageInstaller @@ -14,12 +16,18 @@ import android.widget.Button import android.widget.ImageView import android.widget.LinearLayout import android.widget.TextView -import com.futo.platformplayer.* -import com.futo.platformplayer.receivers.InstallReceiver +import com.futo.platformplayer.R +import com.futo.platformplayer.Settings +import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.api.http.ManagedHttpClient +import com.futo.platformplayer.copyToOutputStream import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.receivers.InstallReceiver import com.futo.platformplayer.states.StateUpdate -import kotlinx.coroutines.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import java.io.File import java.io.InputStream @@ -100,7 +108,7 @@ class AutoUpdateDialog(context: Context?) : AlertDialog(context) { window?.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); _text.text = context.resources.getText(R.string.downloading_update); - (_updateSpinner?.drawable as Animatable?)?.start(); + (_updateSpinner.drawable as Animatable?)?.start(); GlobalScope.launch(Dispatchers.IO) { var inputStream: InputStream? = null; @@ -193,7 +201,7 @@ class AutoUpdateDialog(context: Context?) : AlertDialog(context) { setCancelable(true); setCanceledOnTouchOutside(true); _buttonClose.visibility = View.VISIBLE; - (_updateSpinner?.drawable as Animatable?)?.stop(); + (_updateSpinner.drawable as Animatable?)?.stop(); if (result == null || result.isBlank()) { _updateSpinner.setImageResource(R.drawable.ic_update_success_251dp); diff --git a/app/src/main/java/com/futo/platformplayer/dialogs/ImportDialog.kt b/app/src/main/java/com/futo/platformplayer/dialogs/ImportDialog.kt index 73995003..76c15ae9 100644 --- a/app/src/main/java/com/futo/platformplayer/dialogs/ImportDialog.kt +++ b/app/src/main/java/com/futo/platformplayer/dialogs/ImportDialog.kt @@ -7,8 +7,8 @@ import android.graphics.drawable.Animatable import android.os.Bundle import android.text.Spannable import android.text.SpannableString -import android.text.style.ForegroundColorSpan import android.text.method.ScrollingMovementMethod +import android.text.style.ForegroundColorSpan import android.view.LayoutInflater import android.view.View import android.view.WindowManager @@ -17,12 +17,16 @@ import android.widget.FrameLayout import android.widget.ImageView import android.widget.LinearLayout import android.widget.TextView -import com.futo.platformplayer.* +import com.futo.platformplayer.R +import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.api.media.exceptions.NoPlatformClientException +import com.futo.platformplayer.assume import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.stores.v2.ManagedStore -import kotlinx.coroutines.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext class ImportDialog : AlertDialog { companion object { @@ -99,7 +103,6 @@ class ImportDialog : AlertDialog { _textProgress = findViewById(R.id.text_progress); _updateSpinner = findViewById(R.id.update_spinner); - val toMigrateCount = _store.getMissingReconstructionCount(); _import_type_text.text = _store.name; _import_name_text.text = _name; diff --git a/app/src/main/java/com/futo/platformplayer/dialogs/ProgressDialog.kt b/app/src/main/java/com/futo/platformplayer/dialogs/ProgressDialog.kt index fea26588..c9a579b6 100644 --- a/app/src/main/java/com/futo/platformplayer/dialogs/ProgressDialog.kt +++ b/app/src/main/java/com/futo/platformplayer/dialogs/ProgressDialog.kt @@ -1,29 +1,13 @@ package com.futo.platformplayer.dialogs import android.app.AlertDialog -import android.app.PendingIntent.* import android.content.Context -import android.content.Intent -import android.content.pm.PackageInstaller import android.graphics.drawable.Animatable import android.os.Bundle -import android.util.Log import android.view.LayoutInflater -import android.view.View -import android.widget.Button import android.widget.ImageView -import android.widget.LinearLayout import android.widget.TextView -import com.futo.platformplayer.receivers.InstallReceiver import com.futo.platformplayer.R -import com.futo.platformplayer.Settings -import com.futo.platformplayer.activities.MainActivity -import com.futo.platformplayer.api.http.ManagedHttpClient -import com.futo.platformplayer.states.StateApp -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext class ProgressDialog : AlertDialog { companion object { @@ -50,7 +34,7 @@ class ProgressDialog : AlertDialog { setCancelable(false); setCanceledOnTouchOutside(false); _text.text = ""; - (_updateSpinner?.drawable as Animatable?)?.start(); + (_updateSpinner.drawable as Animatable?)?.start(); _handler(this); } diff --git a/app/src/main/java/com/futo/platformplayer/downloads/VideoDownload.kt b/app/src/main/java/com/futo/platformplayer/downloads/VideoDownload.kt index 048e36d3..7f2b20ee 100644 --- a/app/src/main/java/com/futo/platformplayer/downloads/VideoDownload.kt +++ b/app/src/main/java/com/futo/platformplayer/downloads/VideoDownload.kt @@ -6,13 +6,20 @@ import com.arthenica.ffmpegkit.FFmpegKit import com.arthenica.ffmpegkit.ReturnCode import com.arthenica.ffmpegkit.StatisticsCallback import com.futo.platformplayer.Settings -import com.futo.platformplayer.logging.Logger -import com.futo.platformplayer.states.StateDownloads -import com.futo.platformplayer.states.StatePlatform import com.futo.platformplayer.api.http.ManagedHttpClient import com.futo.platformplayer.api.media.PlatformID import com.futo.platformplayer.api.media.models.streams.VideoUnMuxedSourceDescriptor -import com.futo.platformplayer.api.media.models.streams.sources.* +import com.futo.platformplayer.api.media.models.streams.sources.AudioUrlSource +import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource +import com.futo.platformplayer.api.media.models.streams.sources.IAudioUrlSource +import com.futo.platformplayer.api.media.models.streams.sources.IHLSManifestSource +import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource +import com.futo.platformplayer.api.media.models.streams.sources.IVideoUrlSource +import com.futo.platformplayer.api.media.models.streams.sources.LocalAudioSource +import com.futo.platformplayer.api.media.models.streams.sources.LocalSubtitleSource +import com.futo.platformplayer.api.media.models.streams.sources.LocalVideoSource +import com.futo.platformplayer.api.media.models.streams.sources.SubtitleRawSource +import com.futo.platformplayer.api.media.models.streams.sources.VideoUrlSource import com.futo.platformplayer.api.media.models.streams.sources.other.IStreamMetaDataSource import com.futo.platformplayer.api.media.models.video.IPlatformVideo import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails @@ -24,8 +31,11 @@ import com.futo.platformplayer.hasAnySource import com.futo.platformplayer.helpers.FileHelper.Companion.sanitizeFileName import com.futo.platformplayer.helpers.VideoHelper import com.futo.platformplayer.isDownloadable +import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.parsers.HLS import com.futo.platformplayer.serializers.OffsetDateTimeNullableSerializer +import com.futo.platformplayer.states.StateDownloads +import com.futo.platformplayer.states.StatePlatform import com.futo.platformplayer.toHumanBitrate import com.futo.platformplayer.toHumanBytesSpeed import kotlinx.coroutines.CancellationException @@ -54,14 +64,9 @@ class VideoDownload { var video: SerializedPlatformVideo? = null; var videoDetails: SerializedPlatformVideoDetails? = null; - @kotlinx.serialization.Transient val videoEither: IPlatformVideo get() = videoDetails ?: video ?: throw IllegalStateException("Missing video?"); - - @kotlinx.serialization.Transient val id: PlatformID get() = videoEither.id - @kotlinx.serialization.Transient val name: String get() = videoEither.name; - @kotlinx.serialization.Transient val thumbnail: String? get() = videoDetails?.thumbnails?.getHQThumbnail() ?: video?.thumbnails?.getHQThumbnail(); var targetPixelCount: Long? = null; @@ -385,7 +390,7 @@ class VideoDownload { Logger.i(TAG, "${name} downloadSource Finished"); } catch(ioex: IOException) { - if(targetFile.exists() ?: false) + if(targetFile.exists()) targetFile.delete(); if(ioex.message?.contains("ENOSPC") ?: false) throw Exception("Not enough space on device", ioex); @@ -393,7 +398,7 @@ class VideoDownload { throw ioex; } catch(ex: Throwable) { - if(targetFile.exists() ?: false) + if(targetFile.exists()) targetFile.delete(); throw ex; } @@ -412,7 +417,7 @@ class VideoDownload { val cmd = "-f concat -safe 0 -i \"${fileList.absolutePath}\" -c copy \"${targetFile.absolutePath}\"" - val statisticsCallback = StatisticsCallback { statistics -> + val statisticsCallback = StatisticsCallback { _ -> //TODO: Show progress? } @@ -449,8 +454,7 @@ class VideoDownload { targetFile.createNewFile(); - var sourceLength: Long? = null; - + val sourceLength: Long?; val fileStream = FileOutputStream(targetFile); try{ @@ -458,17 +462,17 @@ class VideoDownload { if(Settings.instance.downloads.byteRangeDownload && head?.containsKey("accept-ranges") == true && head.containsKey("content-length")) { val concurrency = Settings.instance.downloads.getByteRangeThreadCount(); - Logger.i(TAG, "Download ${name} ByteRange Parallel (${concurrency})"); + Logger.i(TAG, "Download $name ByteRange Parallel (${concurrency})"); sourceLength = head["content-length"]!!.toLong(); onProgress(sourceLength, 0, 0); downloadSource_Ranges(name, client, fileStream, videoUrl, sourceLength, 1024*512, concurrency, onProgress); } else { - Logger.i(TAG, "Download ${name} Sequential"); + Logger.i(TAG, "Download $name Sequential"); sourceLength = downloadSource_Sequential(client, fileStream, videoUrl, onProgress); } - Logger.i(TAG, "${name} downloadSource Finished"); + Logger.i(TAG, "$name downloadSource Finished"); } catch(ioex: IOException) { if(targetFile.exists() ?: false) @@ -484,7 +488,7 @@ class VideoDownload { throw ex; } finally { - fileStream?.close(); + fileStream.close(); } return sourceLength!!; } @@ -507,7 +511,7 @@ class VideoDownload { val sourceStream = result.body.byteStream(); var totalRead: Long = 0; - var read = 0; + var read: Int; val buffer = ByteArray(4096); diff --git a/app/src/main/java/com/futo/platformplayer/downloads/VideoLocal.kt b/app/src/main/java/com/futo/platformplayer/downloads/VideoLocal.kt index bf8cb601..3b985796 100644 --- a/app/src/main/java/com/futo/platformplayer/downloads/VideoLocal.kt +++ b/app/src/main/java/com/futo/platformplayer/downloads/VideoLocal.kt @@ -11,7 +11,13 @@ import com.futo.platformplayer.api.media.models.ratings.IRating import com.futo.platformplayer.api.media.models.streams.IVideoSourceDescriptor import com.futo.platformplayer.api.media.models.streams.LocalVideoMuxedSourceDescriptor import com.futo.platformplayer.api.media.models.streams.LocalVideoUnMuxedSourceDescriptor -import com.futo.platformplayer.api.media.models.streams.sources.* +import com.futo.platformplayer.api.media.models.streams.sources.IDashManifestSource +import com.futo.platformplayer.api.media.models.streams.sources.IHLSManifestSource +import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource +import com.futo.platformplayer.api.media.models.streams.sources.LocalAudioSource +import com.futo.platformplayer.api.media.models.streams.sources.LocalSubtitleSource +import com.futo.platformplayer.api.media.models.streams.sources.LocalVideoSource +import com.futo.platformplayer.api.media.models.streams.sources.SubtitleRawSource import com.futo.platformplayer.api.media.models.subtitles.ISubtitleSource import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideoDetails @@ -46,7 +52,7 @@ class VideoLocal: IPlatformVideoDetails, IStoreItem { override val shareUrl: String get() = videoSerialized.shareUrl; @kotlinx.serialization.Transient - override val video: IVideoSourceDescriptor get() = if(!audioSource.isEmpty()) + override val video: IVideoSourceDescriptor get() = if(audioSource.isNotEmpty()) LocalVideoUnMuxedSourceDescriptor(this) else LocalVideoMuxedSourceDescriptor(this); diff --git a/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt b/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt index 65155ac3..7517bddd 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt @@ -10,14 +10,30 @@ import com.caoccao.javet.values.primitive.V8ValueBoolean import com.caoccao.javet.values.primitive.V8ValueInteger import com.caoccao.javet.values.primitive.V8ValueString import com.caoccao.javet.values.reference.V8ValueObject -import com.futo.platformplayer.* import com.futo.platformplayer.api.http.ManagedHttpClient import com.futo.platformplayer.constructs.Event1 -import com.futo.platformplayer.engine.exceptions.* +import com.futo.platformplayer.engine.exceptions.NoInternetException +import com.futo.platformplayer.engine.exceptions.PluginEngineStoppedException +import com.futo.platformplayer.engine.exceptions.ScriptAgeException +import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException +import com.futo.platformplayer.engine.exceptions.ScriptCompilationException +import com.futo.platformplayer.engine.exceptions.ScriptCriticalException +import com.futo.platformplayer.engine.exceptions.ScriptException +import com.futo.platformplayer.engine.exceptions.ScriptExecutionException +import com.futo.platformplayer.engine.exceptions.ScriptImplementationException +import com.futo.platformplayer.engine.exceptions.ScriptLoginRequiredException +import com.futo.platformplayer.engine.exceptions.ScriptTimeoutException +import com.futo.platformplayer.engine.exceptions.ScriptUnavailableException import com.futo.platformplayer.engine.internal.V8Converter -import com.futo.platformplayer.engine.packages.* +import com.futo.platformplayer.engine.packages.PackageBridge +import com.futo.platformplayer.engine.packages.PackageDOMParser +import com.futo.platformplayer.engine.packages.PackageHttp +import com.futo.platformplayer.engine.packages.PackageUtilities +import com.futo.platformplayer.engine.packages.V8Package +import com.futo.platformplayer.getOrThrow import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.states.StateAssets +import com.futo.platformplayer.warnIfMainThread class V8Plugin { val config: IV8PluginConfig; @@ -61,7 +77,7 @@ class V8Plugin { withDependency(PackageBridge(this, config)); for(pack in config.packages) - withDependency(getPackage(context, pack)); + withDependency(getPackage(pack)); } fun withDependency(context: Context, assetPath: String) : V8Plugin { @@ -208,10 +224,10 @@ class V8Plugin { } } - private fun getPackage(context: Context, packageName: String): V8Package { + private fun getPackage(packageName: String): V8Package { //TODO: Auto get all package types? return when(packageName) { - "DOMParser" -> PackageDOMParser(context, this) + "DOMParser" -> PackageDOMParser(this) "Http" -> PackageHttp(this, config) "Utilities" -> PackageUtilities(this, config) else -> throw ScriptCompilationException(config, "Unknown package [${packageName}] required for plugin ${config.name}"); diff --git a/app/src/main/java/com/futo/platformplayer/engine/exceptions/PluginEngineException.kt b/app/src/main/java/com/futo/platformplayer/engine/exceptions/PluginEngineException.kt index 3ca2ec13..6569b001 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/exceptions/PluginEngineException.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/exceptions/PluginEngineException.kt @@ -1,9 +1,6 @@ package com.futo.platformplayer.engine.exceptions -import com.caoccao.javet.values.reference.V8ValueObject import com.futo.platformplayer.engine.IV8PluginConfig -import com.futo.platformplayer.getOrThrow -import java.lang.Exception open class PluginEngineException(config: IV8PluginConfig, error: String, code: String? = null) : PluginException(config, error, null, code) { diff --git a/app/src/main/java/com/futo/platformplayer/engine/exceptions/PluginEngineStoppedException.kt b/app/src/main/java/com/futo/platformplayer/engine/exceptions/PluginEngineStoppedException.kt index b3f6044e..410bbd95 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/exceptions/PluginEngineStoppedException.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/exceptions/PluginEngineStoppedException.kt @@ -1,9 +1,6 @@ package com.futo.platformplayer.engine.exceptions -import com.caoccao.javet.values.reference.V8ValueObject import com.futo.platformplayer.engine.IV8PluginConfig -import com.futo.platformplayer.getOrThrow -import java.lang.Exception class PluginEngineStoppedException(config: IV8PluginConfig, error: String, code: String? = null) : PluginEngineException(config, error, code) { diff --git a/app/src/main/java/com/futo/platformplayer/engine/internal/V8Converter.kt b/app/src/main/java/com/futo/platformplayer/engine/internal/V8Converter.kt index 62b6c149..5c0d8a67 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/internal/V8Converter.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/internal/V8Converter.kt @@ -1,12 +1,8 @@ package com.futo.platformplayer.engine.internal -import com.caoccao.javet.annotations.V8Convert import com.caoccao.javet.interop.V8Runtime import com.caoccao.javet.interop.converters.JavetObjectConverter -import com.caoccao.javet.interop.converters.JavetProxyConverter import com.caoccao.javet.values.V8Value -import com.caoccao.javet.values.reference.V8ValueObject -import com.futo.platformplayer.engine.V8Plugin class V8Converter : JavetObjectConverter() { @@ -17,11 +13,11 @@ class V8Converter : JavetObjectConverter() { return null; val value: V8Value? = super.toV8Value(v8Runtime, obj, depth) - if (value != null && !value.isUndefined) + if (value != null && !value.isUndefined) { return value as T; - if (obj != null) { - if (obj is IV8Convertable) - return obj.toV8(v8Runtime) as T; + } + if (obj is IV8Convertable) { + return obj.toV8(v8Runtime) as T } return null; } diff --git a/app/src/main/java/com/futo/platformplayer/engine/packages/PackageDOMParser.kt b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageDOMParser.kt index 9c5cacbd..c261487d 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/packages/PackageDOMParser.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageDOMParser.kt @@ -1,16 +1,11 @@ package com.futo.platformplayer.engine.packages -import android.content.Context -import android.util.Log -import com.caoccao.javet.annotations.V8Allow import com.caoccao.javet.annotations.V8Convert import com.caoccao.javet.annotations.V8Function import com.caoccao.javet.annotations.V8Property import com.caoccao.javet.enums.V8ConversionMode import com.caoccao.javet.enums.V8ProxyMode -import com.caoccao.javet.values.reference.V8ValueObject import com.futo.platformplayer.engine.V8Plugin -import com.futo.platformplayer.engine.dev.V8RemoteObject import com.futo.platformplayer.engine.internal.V8BindObject import org.jsoup.Jsoup import org.jsoup.nodes.Element @@ -20,8 +15,8 @@ class PackageDOMParser : V8Package { override val name: String get() = "DOMParser"; override val variableName: String = "domParser"; - constructor(context: Context, v8Plugin: V8Plugin): super(v8Plugin) { - //v8Plugin.withDependency(context, "/scripts/some/package/path"); + constructor(v8Plugin: V8Plugin): super(v8Plugin) { + } @V8Function @@ -45,8 +40,7 @@ class PackageDOMParser : V8Package { @V8Property fun childNodes(): List { val results = _element.children().map { DOMNode(_package, it) }.toList(); - if(results != null) - _children.addAll(results); + _children.addAll(results); return results; } @V8Property diff --git a/app/src/main/java/com/futo/platformplayer/engine/packages/PackageHttp.kt b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageHttp.kt index eadf68c6..68a74202 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/packages/PackageHttp.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageHttp.kt @@ -8,18 +8,15 @@ import com.caoccao.javet.enums.V8ProxyMode import com.caoccao.javet.interop.V8Runtime import com.caoccao.javet.values.V8Value import com.caoccao.javet.values.reference.V8ValueObject -import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.api.http.ManagedHttpClient import com.futo.platformplayer.api.media.platforms.js.internal.JSHttpClient import com.futo.platformplayer.engine.IV8PluginConfig import com.futo.platformplayer.engine.V8Plugin import com.futo.platformplayer.engine.internal.IV8Convertable import com.futo.platformplayer.engine.internal.V8BindObject -import com.futo.platformplayer.getOrThrow -import kotlinx.coroutines.CoroutineScope +import com.futo.platformplayer.logging.Logger import java.net.SocketTimeoutException import kotlin.streams.asSequence -import kotlin.streams.toList class PackageHttp: V8Package { @Transient @@ -227,10 +224,10 @@ class PackageHttp: V8Package { return logExceptions { return@logExceptions catchHttp { val client = _client; - logRequest(method, url, headers, null); + //logRequest(method, url, headers, null); val resp = client.requestMethod(method, url, headers); val responseBody = resp.body?.string(); - logResponse(method, url, resp.code, resp.headers, responseBody); + //logResponse(method, url, resp.code, resp.headers, responseBody); return@catchHttp BridgeHttpResponse(resp.url, resp.code, responseBody, sanitizeResponseHeaders(resp.headers)); } }; @@ -241,10 +238,10 @@ class PackageHttp: V8Package { return logExceptions { catchHttp { val client = _client; - logRequest(method, url, headers, body); + //logRequest(method, url, headers, body); val resp = client.requestMethod(method, url, body, headers); val responseBody = resp.body?.string(); - logResponse(method, url, resp.code, resp.headers, responseBody); + //logResponse(method, url, resp.code, resp.headers, responseBody); return@catchHttp BridgeHttpResponse(resp.url, resp.code, responseBody, sanitizeResponseHeaders(resp.headers)); } }; @@ -256,10 +253,10 @@ class PackageHttp: V8Package { return logExceptions { catchHttp { val client = _client; - logRequest("GET", url, headers, null); + //logRequest("GET", url, headers, null); val resp = client.get(url, headers); val responseBody = resp.body?.string(); - logResponse("GET", url, resp.code, resp.headers, responseBody); + //logResponse("GET", url, resp.code, resp.headers, responseBody); return@catchHttp BridgeHttpResponse(resp.url, resp.code, responseBody, sanitizeResponseHeaders(resp.headers)); } }; @@ -270,10 +267,10 @@ class PackageHttp: V8Package { return logExceptions { catchHttp { val client = _client; - logRequest("POST", url, headers, body); + //logRequest("POST", url, headers, body); val resp = client.post(url, body, headers); val responseBody = resp.body?.string(); - logResponse("POST", url, resp.code, resp.headers, responseBody); + //logResponse("POST", url, resp.code, resp.headers, responseBody); return@catchHttp BridgeHttpResponse(resp.url, resp.code, responseBody, sanitizeResponseHeaders(resp.headers)); } }; @@ -283,7 +280,7 @@ class PackageHttp: V8Package { fun socket(url: String, headers: Map? = null): SocketResult { val socketHeaders = headers?.toMutableMap() ?: HashMap(); applyDefaultHeaders(socketHeaders); - return SocketResult(this, _client, url, socketHeaders ?: HashMap()); + return SocketResult(this, _client, url, socketHeaders); } private fun applyDefaultHeaders(headerMap: MutableMap) { @@ -305,9 +302,7 @@ class PackageHttp: V8Package { return result } - private fun logRequest(method: String, url: String, headers: Map = HashMap(), body: String?) { - return; - + /*private fun logRequest(method: String, url: String, headers: Map = HashMap(), body: String?) { Logger.v(TAG) { val stringBuilder = StringBuilder(); stringBuilder.appendLine("HTTP request (useAuth = )"); @@ -324,11 +319,9 @@ class PackageHttp: V8Package { return@v stringBuilder.toString(); }; - } - - private fun logResponse(method: String, url: String, responseCode: Int? = null, responseHeaders: Map> = HashMap(), responseBody: String? = null) { - return; + }*/ + /*private fun logResponse(method: String, url: String, responseCode: Int? = null, responseHeaders: Map> = HashMap(), responseBody: String? = null) { Logger.v(TAG) { val stringBuilder = StringBuilder(); if (responseCode != null) { @@ -353,7 +346,7 @@ class PackageHttp: V8Package { return@v stringBuilder.toString(); }; - } + }*/ fun logExceptions(handle: ()->T): T { try { diff --git a/app/src/main/java/com/futo/platformplayer/exceptions/RateLimitException.kt b/app/src/main/java/com/futo/platformplayer/exceptions/RateLimitException.kt index c59910de..30720a75 100644 --- a/app/src/main/java/com/futo/platformplayer/exceptions/RateLimitException.kt +++ b/app/src/main/java/com/futo/platformplayer/exceptions/RateLimitException.kt @@ -4,6 +4,6 @@ class RateLimitException : Throwable { val pluginIds: List; constructor(pluginIds: List): super() { - this.pluginIds = pluginIds ?: listOf(); + this.pluginIds = pluginIds; } } \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelAboutFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelAboutFragment.kt index 1cf84c32..f7d9d9bf 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelAboutFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelAboutFragment.kt @@ -9,11 +9,17 @@ import android.widget.LinearLayout import android.widget.TextView import androidx.fragment.app.Fragment import com.bumptech.glide.Glide -import com.futo.platformplayer.* +import com.futo.platformplayer.R import com.futo.platformplayer.api.media.models.channels.IPlatformChannel +import com.futo.platformplayer.dp +import com.futo.platformplayer.fixHtmlLinks import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.resolveChannelUrl +import com.futo.platformplayer.selectBestImage +import com.futo.platformplayer.setPlatformPlayerLinkMovementMethod import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.toHumanNumber import com.futo.platformplayer.views.platform.PlatformLinkView import com.futo.polycentric.core.toName import com.futo.polycentric.core.toURLInfoSystemLinkUrl @@ -48,7 +54,7 @@ class ChannelAboutFragment : Fragment, IChannelTabFragment { setChannel(it); }; _lastPolycentricProfile?.also { - setPolycentricProfile(it, animate = false); + setPolycentricProfile(it); } return view; @@ -108,7 +114,7 @@ class ChannelAboutFragment : Fragment, IChannelTabFragment { } - fun setPolycentricProfile(polycentricProfile: PolycentricProfile?, animate: Boolean) { + fun setPolycentricProfile(polycentricProfile: PolycentricProfile?) { _lastPolycentricProfile = polycentricProfile; if (polycentricProfile == null) { diff --git a/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelContentsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelContentsFragment.kt index 0bf75149..1aec934b 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelContentsFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelContentsFragment.kt @@ -8,8 +8,6 @@ import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import com.futo.platformplayer.logging.Logger -import com.futo.platformplayer.states.StatePlatform import com.futo.platformplayer.R import com.futo.platformplayer.Settings import com.futo.platformplayer.UIDialogs @@ -31,13 +29,15 @@ import com.futo.platformplayer.engine.exceptions.PluginException import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException import com.futo.platformplayer.fragment.mainactivity.main.FeedView import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile +import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.states.StateCache +import com.futo.platformplayer.states.StatePlatform import com.futo.platformplayer.states.StatePolycentric import com.futo.platformplayer.states.StateSubscriptions import com.futo.platformplayer.views.FeedStyle -import com.futo.platformplayer.views.adapters.feedtypes.PreviewContentListAdapter import com.futo.platformplayer.views.adapters.ContentPreviewViewHolder import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader +import com.futo.platformplayer.views.adapters.feedtypes.PreviewContentListAdapter import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -215,14 +215,14 @@ class ChannelContentsFragment : Fragment(), IChannelTabFragment { fun setPager(pager: IPager, cache: FeedView.ItemCache? = null) { if (_pager_parent != null && _pager_parent is IRefreshPager<*>) { - (_pager_parent as IRefreshPager<*>).onPagerError?.remove(this); - (_pager_parent as IRefreshPager<*>).onPagerChanged?.remove(this); + (_pager_parent as IRefreshPager<*>).onPagerError.remove(this); + (_pager_parent as IRefreshPager<*>).onPagerChanged.remove(this); _pager_parent = null; } if(_pager is IReplacerPager<*>) (_pager as IReplacerPager<*>).onReplaced.remove(this); - var pagerToSet: IPager? = null; + var pagerToSet: IPager?; if(pager is IRefreshPager<*>) { _pager_parent = pager; pagerToSet = pager.getCurrentPager() as IPager; @@ -305,7 +305,7 @@ class ChannelContentsFragment : Fragment(), IChannelTabFragment { _adapterResults?.setLoading(loading); } - fun setPolycentricProfile(polycentricProfile: PolycentricProfile?, animate: Boolean) { + fun setPolycentricProfile(polycentricProfile: PolycentricProfile?) { val p = _lastPolycentricProfile; if (p != null && polycentricProfile != null && p.system == polycentricProfile.system) { Logger.i(TAG, "setPolycentricProfile skipped because previous was same"); diff --git a/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelListFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelListFragment.kt index 4ce5337d..00ec5982 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelListFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelListFragment.kt @@ -1,7 +1,6 @@ package com.futo.platformplayer.fragment.channel.tab import android.os.Bundle -import android.util.TypedValue import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -9,7 +8,8 @@ import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView -import com.futo.platformplayer.* +import com.futo.platformplayer.R +import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.api.media.models.PlatformAuthorLink import com.futo.platformplayer.api.media.models.channels.IPlatformChannel import com.futo.platformplayer.constructs.Event1 @@ -18,11 +18,10 @@ import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException import com.futo.platformplayer.fragment.mainactivity.main.ChannelFragment import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.resolveChannelUrl import com.futo.platformplayer.states.StatePlatform import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader import com.futo.platformplayer.views.adapters.viewholders.CreatorViewHolder -import com.futo.polycentric.core.toUrl -import kotlinx.coroutines.runBlocking class ChannelListFragment : Fragment, IChannelTabFragment { private var _channels: ArrayList = arrayListOf(); @@ -84,7 +83,7 @@ class ChannelListFragment : Fragment, IChannelTabFragment { recyclerCreator.layoutManager = _lm; _recyclerCreator = recyclerCreator; _lastChannel?.also { setChannel(it); }; - _lastPolycentricProfile?.also { setPolycentricProfile(it, animate = false); } + _lastPolycentricProfile?.also { setPolycentricProfile(it); } return view; } @@ -125,7 +124,7 @@ class ChannelListFragment : Fragment, IChannelTabFragment { } } - fun setPolycentricProfile(polycentricProfile: PolycentricProfile?, animate: Boolean) { + fun setPolycentricProfile(polycentricProfile: PolycentricProfile?) { _taskLoadChannel.cancel(); _lastPolycentricProfile = polycentricProfile; diff --git a/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelMonetizationFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelMonetizationFragment.kt index 20b6f5b4..e2e39ee7 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelMonetizationFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelMonetizationFragment.kt @@ -32,7 +32,7 @@ class ChannelMonetizationFragment : Fragment, IChannelTabFragment { _supportView?.visibility = View.GONE; _textMonetization?.visibility = View.GONE; - setPolycentricProfile(_lastPolycentricProfile, animate = false); + setPolycentricProfile(_lastPolycentricProfile); return view; } @@ -46,14 +46,14 @@ class ChannelMonetizationFragment : Fragment, IChannelTabFragment { _lastChannel = channel; } - fun setPolycentricProfile(polycentricProfile: PolycentricProfile?, animate: Boolean) { + fun setPolycentricProfile(polycentricProfile: PolycentricProfile?) { _lastPolycentricProfile = polycentricProfile if (polycentricProfile != null) { - _supportView?.setPolycentricProfile(polycentricProfile, animate) + _supportView?.setPolycentricProfile(polycentricProfile) _supportView?.visibility = View.VISIBLE _textMonetization?.visibility = View.GONE } else { - _supportView?.setPolycentricProfile(null, animate) + _supportView?.setPolycentricProfile(null) _supportView?.visibility = View.GONE _textMonetization?.visibility = View.VISIBLE } diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/bottombar/MenuBottomBarFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/bottombar/MenuBottomBarFragment.kt index e96754ce..48f68590 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/bottombar/MenuBottomBarFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/bottombar/MenuBottomBarFragment.kt @@ -7,11 +7,8 @@ import android.annotation.SuppressLint import android.app.Activity import android.content.Context import android.content.Intent -import android.graphics.Color import android.os.Bundle -import android.view.Gravity import android.view.LayoutInflater -import android.view.MotionEvent import android.view.View import android.view.ViewGroup import android.widget.* @@ -19,7 +16,6 @@ import androidx.core.animation.doOnEnd import androidx.lifecycle.lifecycleScope import com.futo.platformplayer.R import com.futo.platformplayer.Settings -import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.activities.MainActivity import com.futo.platformplayer.activities.SettingsActivity import com.futo.platformplayer.dp @@ -31,7 +27,6 @@ import com.futo.platformplayer.states.StatePayment import com.futo.platformplayer.states.StateSubscriptions import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import java.util.Collections import kotlin.math.floor import kotlin.math.roundToInt diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/BuyFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/BuyFragment.kt index ca970cfb..6fca4178 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/BuyFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/BuyFragment.kt @@ -63,7 +63,7 @@ class BuyFragment : MainFragment() { _overlayLoading = findViewById(R.id.overlay_loading); _overlayPaying = findViewById(R.id.overlay_paying); - _paymentManager = PaymentManager(StatePayment.instance, fragment, _overlayPaying) { success, purchaseId, exception -> + _paymentManager = PaymentManager(StatePayment.instance, fragment, _overlayPaying) { success, _, exception -> if(success) { UIDialogs.showDialog(context, R.drawable.ic_check, context.getString(R.string.payment_succeeded), context.getString(R.string.thanks_for_your_purchase_a_key_will_be_sent_to_your_email_after_your_payment_has_been_received), null, 0, UIDialogs.Action("Ok", {}, UIDialogs.ActionStyle.PRIMARY)); @@ -90,7 +90,7 @@ class BuyFragment : MainFragment() { val currencies = StatePayment.instance.getAvailableCurrencies("grayjay"); val prices = StatePayment.instance.getAvailableCurrencyPrices("grayjay"); val country = StatePayment.instance.getPaymentCountryFromIP()?.let { c -> PaymentConfigurations.COUNTRIES.find { it.id.equals(c, ignoreCase = true) } }; - val currency = country?.let { c -> PaymentConfigurations.CURRENCIES.find { it.id == c.defaultCurrencyId && (currencies.contains(it.id) ?: true) } }; + val currency = country?.let { c -> PaymentConfigurations.CURRENCIES.find { it.id == c.defaultCurrencyId && (currencies.contains(it.id)) } }; if(currency != null && prices.containsKey(currency.id)) { val price = prices[currency.id]!!; diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ChannelFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ChannelFragment.kt index 056cece7..ded1948b 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ChannelFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ChannelFragment.kt @@ -3,8 +3,6 @@ package com.futo.platformplayer.fragment.mainactivity.main import android.annotation.SuppressLint import android.graphics.drawable.Animatable import android.os.Bundle -import android.text.Html -import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -30,9 +28,9 @@ import com.futo.platformplayer.api.media.models.post.IPlatformPost import com.futo.platformplayer.api.media.models.video.IPlatformVideo import com.futo.platformplayer.constructs.TaskHandler import com.futo.platformplayer.fragment.channel.tab.ChannelAboutFragment +import com.futo.platformplayer.fragment.channel.tab.ChannelContentsFragment import com.futo.platformplayer.fragment.channel.tab.ChannelListFragment import com.futo.platformplayer.fragment.channel.tab.ChannelMonetizationFragment -import com.futo.platformplayer.fragment.channel.tab.ChannelContentsFragment import com.futo.platformplayer.fragment.mainactivity.topbar.NavigationTopBarFragment import com.futo.platformplayer.images.GlideHelper.Companion.crossfade import com.futo.platformplayer.logging.Logger @@ -43,10 +41,10 @@ import com.futo.platformplayer.states.StatePlatform import com.futo.platformplayer.states.StatePlayer import com.futo.platformplayer.states.StatePlaylists import com.futo.platformplayer.states.StateSubscriptions -import com.futo.platformplayer.views.others.CreatorThumbnail -import com.futo.platformplayer.views.subscriptions.SubscribeButton import com.futo.platformplayer.views.adapters.ChannelViewPagerAdapter +import com.futo.platformplayer.views.others.CreatorThumbnail import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuOverlay +import com.futo.platformplayer.views.subscriptions.SubscribeButton import com.futo.polycentric.core.* import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayoutMediator @@ -54,7 +52,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlinx.serialization.Serializable -import okhttp3.internal.platform.Platform @Serializable data class PolycentricProfile(val system: PublicKey, val systemState: SystemState, val ownedClaims: List); @@ -450,7 +447,7 @@ class ChannelFragment : MainFragment() { private fun setPolycentricProfileOr(url: String, or: () -> Unit) { setPolycentricProfile(null, animate = false); - val cachedProfile = channel?.let { PolycentricCache.instance.getCachedProfile(it.url) }; + val cachedProfile = channel?.let { PolycentricCache.instance.getCachedProfile(url) }; if (cachedProfile != null) { setPolycentricProfile(cachedProfile, animate = false); } else { @@ -492,10 +489,10 @@ class ChannelFragment : MainFragment() { } (_viewPager.adapter as ChannelViewPagerAdapter?)?.let { - it.getFragment().setPolycentricProfile(profile, animate); - it.getFragment().setPolycentricProfile(profile, animate); - it.getFragment().setPolycentricProfile(profile, animate); - it.getFragment().setPolycentricProfile(profile, animate); + it.getFragment().setPolycentricProfile(profile); + it.getFragment().setPolycentricProfile(profile); + it.getFragment().setPolycentricProfile(profile); + it.getFragment().setPolycentricProfile(profile); //TODO: Call on other tabs as needed } } diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentSearchResultsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentSearchResultsFragment.kt index d5a2c987..e6b9be9b 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentSearchResultsFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentSearchResultsFragment.kt @@ -6,10 +6,8 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.lifecycle.lifecycleScope -import com.futo.platformplayer.logging.Logger -import com.futo.platformplayer.UIDialogs -import com.futo.platformplayer.states.StatePlatform import com.futo.platformplayer.Settings +import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.UISlideOverlays import com.futo.platformplayer.api.media.models.ResultCapabilities import com.futo.platformplayer.api.media.models.contents.IPlatformContent @@ -18,6 +16,8 @@ import com.futo.platformplayer.constructs.TaskHandler import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException import com.futo.platformplayer.fragment.mainactivity.topbar.SearchTopBarFragment import com.futo.platformplayer.isHttpUrl +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.StatePlatform import com.futo.platformplayer.views.FeedStyle import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -32,7 +32,7 @@ class ContentSearchResultsFragment : MainFragment() { override fun onShownWithView(parameter: Any?, isBack: Boolean) { super.onShownWithView(parameter, isBack); - _view?.onShown(parameter, isBack); + _view?.onShown(parameter); } override fun onHide() { @@ -110,7 +110,7 @@ class ContentSearchResultsFragment : MainFragment() { _taskSearch.cancel(); } - fun onShown(parameter: Any?, isBack: Boolean) { + fun onShown(parameter: Any?) { if(parameter is SuggestionsFragmentData) { setQuery(parameter.query, false); setChannelUrl(parameter.channelUrl, false); @@ -127,7 +127,7 @@ class ContentSearchResultsFragment : MainFragment() { setFilterButtonVisible(true); onFilterClick.subscribe(this) { - _overlayContainer?.let { + _overlayContainer.let { val filterValuesCopy = HashMap(_filterValues); val filtersOverlay = UISlideOverlays.showFiltersOverlay(lifecycleScope, it, _enabledClientIds!!, filterValuesCopy); filtersOverlay.onOK.subscribe { enabledClientIds, changed -> diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CreatorSearchResultsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CreatorSearchResultsFragment.kt index a685e73e..eac18ba9 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CreatorSearchResultsFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CreatorSearchResultsFragment.kt @@ -6,15 +6,15 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.lifecycle.lifecycleScope -import com.futo.platformplayer.logging.Logger -import com.futo.platformplayer.UIDialogs -import com.futo.platformplayer.states.StatePlatform import com.futo.platformplayer.Settings +import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.api.media.models.PlatformAuthorLink import com.futo.platformplayer.api.media.structures.IPager import com.futo.platformplayer.constructs.TaskHandler import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException import com.futo.platformplayer.fragment.mainactivity.topbar.SearchTopBarFragment +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.StatePlatform import com.futo.platformplayer.views.FeedStyle class CreatorSearchResultsFragment : MainFragment() { @@ -26,7 +26,7 @@ class CreatorSearchResultsFragment : MainFragment() { override fun onShownWithView(parameter: Any?, isBack: Boolean) { super.onShownWithView(parameter, isBack); - _view?.onShown(parameter, isBack); + _view?.onShown(parameter); } override fun onResume() { @@ -69,7 +69,7 @@ class CreatorSearchResultsFragment : MainFragment() { _taskSearch.cancel(); } - fun onShown(parameter: Any?, isBack: Boolean) { + fun onShown(parameter: Any?) { if(parameter is String) { setQuery(parameter); diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/HomeFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/HomeFragment.kt index dd5f33c3..90574133 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/HomeFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/HomeFragment.kt @@ -8,32 +8,24 @@ import android.view.ViewGroup import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import com.futo.platformplayer.* -import com.futo.platformplayer.activities.CaptchaActivity -import com.futo.platformplayer.api.media.IPlatformClient import com.futo.platformplayer.api.media.models.contents.IPlatformContent -import com.futo.platformplayer.api.media.models.video.IPlatformVideo import com.futo.platformplayer.api.media.platforms.js.JSClient -import com.futo.platformplayer.api.media.platforms.js.internal.JSHttpClient import com.futo.platformplayer.api.media.structures.EmptyPager import com.futo.platformplayer.api.media.structures.IPager import com.futo.platformplayer.constructs.TaskHandler import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException import com.futo.platformplayer.engine.exceptions.ScriptExecutionException import com.futo.platformplayer.engine.exceptions.ScriptImplementationException -import com.futo.platformplayer.fragment.mainactivity.topbar.ImportTopBarFragment import com.futo.platformplayer.logging.Logger -import com.futo.platformplayer.others.CaptchaWebViewClient import com.futo.platformplayer.states.AnnouncementType import com.futo.platformplayer.states.StateAnnouncement import com.futo.platformplayer.states.StateMeta import com.futo.platformplayer.states.StatePlatform -import com.futo.platformplayer.states.StatePlugins -import com.futo.platformplayer.states.StateSubscriptions -import com.futo.platformplayer.views.announcements.AnnouncementView import com.futo.platformplayer.views.FeedStyle import com.futo.platformplayer.views.adapters.ContentPreviewViewHolder import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader import com.futo.platformplayer.views.adapters.InsertedViewHolder +import com.futo.platformplayer.views.announcements.AnnouncementView import java.time.OffsetDateTime import java.util.UUID @@ -159,8 +151,8 @@ class HomeFragment : MainFragment() { loadResults(); } - override fun filterResults(contents: List): List { - return contents.filter { !StateMeta.instance.isVideoHidden(it.url) && !StateMeta.instance.isCreatorHidden(it.author.url) }; + override fun filterResults(results: List): List { + return results.filter { !StateMeta.instance.isVideoHidden(it.url) && !StateMeta.instance.isCreatorHidden(it.author.url) }; } private fun loadResults() { diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ImportPlaylistsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ImportPlaylistsFragment.kt index 588d2bb8..e90a535d 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ImportPlaylistsFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ImportPlaylistsFragment.kt @@ -14,12 +14,12 @@ import androidx.recyclerview.widget.RecyclerView import com.futo.platformplayer.* import com.futo.platformplayer.constructs.TaskHandler import com.futo.platformplayer.fragment.mainactivity.topbar.ImportTopBarFragment -import com.futo.platformplayer.views.AnyAdapterView -import com.futo.platformplayer.views.AnyAdapterView.Companion.asAny import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.models.Playlist import com.futo.platformplayer.states.StatePlatform import com.futo.platformplayer.states.StatePlaylists +import com.futo.platformplayer.views.AnyAdapterView +import com.futo.platformplayer.views.AnyAdapterView.Companion.asAny import com.futo.platformplayer.views.adapters.viewholders.ImportPlaylistsViewHolder import com.futo.platformplayer.views.adapters.viewholders.SelectablePlaylist @@ -32,7 +32,7 @@ class ImportPlaylistsFragment : MainFragment() { override fun onShownWithView(parameter: Any?, isBack: Boolean) { super.onShownWithView(parameter, isBack); - _view?.onShown(parameter, isBack); + _view?.onShown(parameter); } override fun onHide() { @@ -79,7 +79,7 @@ class ImportPlaylistsFragment : MainFragment() { _spinner = findViewById(R.id.channel_loader); _adapterView = findViewById(R.id.recycler_import).asAny( _items) { - it.onSelectedChange.subscribe { c -> + it.onSelectedChange.subscribe { updateSelected(); }; }; @@ -123,7 +123,7 @@ class ImportPlaylistsFragment : MainFragment() { _taskLoadPlaylist.cancel(); } - fun onShown(parameter: Any ?, isBack: Boolean) { + fun onShown(parameter: Any?) { updateSelected(); val itemsRemoved = _items.size; diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ImportSubscriptionsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ImportSubscriptionsFragment.kt index e245ec74..a95d96df 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ImportSubscriptionsFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ImportSubscriptionsFragment.kt @@ -15,13 +15,13 @@ import com.futo.platformplayer.* import com.futo.platformplayer.api.media.models.channels.IPlatformChannel import com.futo.platformplayer.constructs.TaskHandler import com.futo.platformplayer.fragment.mainactivity.topbar.ImportTopBarFragment +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.StatePlatform +import com.futo.platformplayer.states.StateSubscriptions import com.futo.platformplayer.views.AnyAdapterView import com.futo.platformplayer.views.AnyAdapterView.Companion.asAny import com.futo.platformplayer.views.adapters.viewholders.ImportSubscriptionViewHolder import com.futo.platformplayer.views.adapters.viewholders.SelectableIPlatformChannel -import com.futo.platformplayer.states.StateSubscriptions -import com.futo.platformplayer.logging.Logger -import com.futo.platformplayer.states.StatePlatform import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -37,7 +37,7 @@ class ImportSubscriptionsFragment : MainFragment() { override fun onShownWithView(parameter: Any?, isBack: Boolean) { super.onShownWithView(parameter, isBack); - _view?.onShown(parameter, isBack); + _view?.onShown(parameter); } override fun onHide() { @@ -93,7 +93,7 @@ class ImportSubscriptionsFragment : MainFragment() { _loadProgress = findViewById(R.id.text_load_progress); _adapterView = findViewById(R.id.recycler_import).asAny( _items) { - it.onSelectedChange.subscribe { c -> + it.onSelectedChange.subscribe { updateSelected(); }; }; @@ -152,14 +152,14 @@ class ImportSubscriptionsFragment : MainFragment() { _taskLoadChannel.cancel(); } - fun onShown(parameter: Any ?, isBack: Boolean) { + fun onShown(parameter: Any?) { _counter = 0; _limitToastShown = false; updateSelected(); val itemsRemoved = _items.size; _items.clear(); - _adapterView?.adapter?.notifyItemRangeRemoved(0, itemsRemoved); + _adapterView.adapter?.notifyItemRangeRemoved(0, itemsRemoved); _links = (parameter as List).filter { i -> !StateSubscriptions.instance.isSubscribed(i) }.toList(); _currentLoadIndex = 0; diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PlaylistFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PlaylistFragment.kt index a15e0377..38c920ef 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PlaylistFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PlaylistFragment.kt @@ -41,7 +41,7 @@ class PlaylistFragment : MainFragment() { override fun onShownWithView(parameter: Any?, isBack: Boolean) { super.onShownWithView(parameter, isBack); - _view?.onShown(parameter, isBack); + _view?.onShown(parameter); } override fun onCreateMainView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { @@ -150,7 +150,7 @@ class PlaylistFragment : MainFragment() { }; } - fun onShown(parameter: Any ?, isBack: Boolean) { + fun onShown(parameter: Any?) { _taskLoadPlaylist.cancel(); if (parameter is Playlist?) { diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PlaylistSearchResultsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PlaylistSearchResultsFragment.kt index bf42b2d4..ec01bb80 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PlaylistSearchResultsFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PlaylistSearchResultsFragment.kt @@ -6,14 +6,14 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.lifecycle.lifecycleScope -import com.futo.platformplayer.logging.Logger -import com.futo.platformplayer.UIDialogs -import com.futo.platformplayer.states.StatePlatform import com.futo.platformplayer.Settings +import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.api.media.models.contents.IPlatformContent import com.futo.platformplayer.api.media.structures.IPager import com.futo.platformplayer.constructs.TaskHandler import com.futo.platformplayer.fragment.mainactivity.topbar.SearchTopBarFragment +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.StatePlatform import com.futo.platformplayer.views.FeedStyle class PlaylistSearchResultsFragment : MainFragment() { @@ -25,7 +25,7 @@ class PlaylistSearchResultsFragment : MainFragment() { override fun onShownWithView(parameter: Any?, isBack: Boolean) { super.onShownWithView(parameter, isBack); - _view?.onShown(parameter, isBack); + _view?.onShown(parameter); } override fun onResume() { @@ -78,7 +78,7 @@ class PlaylistSearchResultsFragment : MainFragment() { _taskSearch.cancel(); } - fun onShown(parameter: Any?, isBack: Boolean) { + fun onShown(parameter: Any?) { if(parameter is String) { setQuery(parameter); diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PlaylistsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PlaylistsFragment.kt index b1179940..085d7cb0 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PlaylistsFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PlaylistsFragment.kt @@ -14,21 +14,15 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import com.futo.platformplayer.states.StatePlayer -import com.futo.platformplayer.states.StatePlaylists import com.futo.platformplayer.R import com.futo.platformplayer.UISlideOverlays import com.futo.platformplayer.api.media.models.video.IPlatformVideo -import com.futo.platformplayer.assume -import com.futo.platformplayer.fragment.mainactivity.topbar.NavigationTopBarFragment import com.futo.platformplayer.models.Playlist -import com.futo.platformplayer.models.SearchType -import com.futo.platformplayer.states.StatePlatform +import com.futo.platformplayer.states.StatePlayer +import com.futo.platformplayer.states.StatePlaylists import com.futo.platformplayer.views.adapters.* import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuOverlay -import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuTextInput import com.google.android.material.appbar.AppBarLayout -import kotlin.collections.ArrayList class PlaylistsFragment : MainFragment() { @@ -52,7 +46,7 @@ class PlaylistsFragment : MainFragment() { override fun onShownWithView(parameter: Any?, isBack: Boolean) { super.onShownWithView(parameter, isBack); - _view?.onShown(parameter, isBack); + _view?.onShown(); } override fun onBackPressed(): Boolean { @@ -133,11 +127,12 @@ class PlaylistsFragment : MainFragment() { StatePlaylists.instance.onWatchLaterChanged.remove(this); } - fun onShown(parameter: Any?, isBack: Boolean) { + @SuppressLint("NotifyDataSetChanged") + fun onShown() { playlists.clear() playlists.addAll( - StatePlaylists.instance.getPlaylists() - .sortedByDescending { maxOf(it.datePlayed, it.dateUpdate, it.dateCreation) }); + StatePlaylists.instance.getPlaylists().sortedByDescending { maxOf(it.datePlayed, it.dateUpdate, it.dateCreation) } + ); _adapterPlaylist.notifyDataSetChanged(); updateWatchLater(); diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PostDetailFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PostDetailFragment.kt index 87185ba1..3cadb47c 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PostDetailFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PostDetailFragment.kt @@ -40,15 +40,15 @@ import com.futo.platformplayer.states.StatePlatform import com.futo.platformplayer.states.StatePolycentric import com.futo.platformplayer.toHumanNowDiffString import com.futo.platformplayer.toHumanNumber -import com.futo.platformplayer.views.comments.AddCommentView -import com.futo.platformplayer.views.segments.CommentsList -import com.futo.platformplayer.views.others.CreatorThumbnail -import com.futo.platformplayer.views.platform.PlatformIndicator -import com.futo.platformplayer.views.subscriptions.SubscribeButton -import com.futo.platformplayer.views.others.Toggle import com.futo.platformplayer.views.adapters.feedtypes.PreviewPostView +import com.futo.platformplayer.views.comments.AddCommentView +import com.futo.platformplayer.views.others.CreatorThumbnail +import com.futo.platformplayer.views.others.Toggle import com.futo.platformplayer.views.overlays.RepliesOverlay import com.futo.platformplayer.views.pills.PillRatingLikesDislikes +import com.futo.platformplayer.views.platform.PlatformIndicator +import com.futo.platformplayer.views.segments.CommentsList +import com.futo.platformplayer.views.subscriptions.SubscribeButton import com.futo.polycentric.core.ApiMethods import com.futo.polycentric.core.ContentType import com.futo.polycentric.core.Models @@ -220,7 +220,7 @@ class PostDetailFragment : MainFragment { root.removeView(layoutTop); _commentsList.setPrependedView(layoutTop); - _commentsList.onCommentsLoaded.subscribe { count -> + _commentsList.onCommentsLoaded.subscribe { updateCommentType(false); }; diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SourceDetailFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SourceDetailFragment.kt index 9f8a4454..a28e8016 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SourceDetailFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SourceDetailFragment.kt @@ -13,7 +13,9 @@ import android.widget.FrameLayout import android.widget.ImageView import android.widget.LinearLayout import androidx.lifecycle.lifecycleScope -import com.futo.platformplayer.* +import com.futo.platformplayer.R +import com.futo.platformplayer.Settings +import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.activities.AddSourceActivity import com.futo.platformplayer.activities.LoginActivity import com.futo.platformplayer.api.http.ManagedHttpClient @@ -25,8 +27,8 @@ import com.futo.platformplayer.states.StatePlatform import com.futo.platformplayer.states.StatePlugins import com.futo.platformplayer.views.buttons.BigButton import com.futo.platformplayer.views.buttons.BigButtonGroup -import com.futo.platformplayer.views.sources.SourceHeaderView import com.futo.platformplayer.views.fields.FieldForm +import com.futo.platformplayer.views.sources.SourceHeaderView import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -40,7 +42,7 @@ class SourceDetailFragment : MainFragment() { override fun onShownWithView(parameter: Any?, isBack: Boolean) { super.onShownWithView(parameter, isBack); - _view?.onShown(parameter, isBack); + _view?.onShown(parameter); } override fun onHide() { @@ -92,7 +94,7 @@ class SourceDetailFragment : MainFragment() { updateSourceViews(); } - fun onShown(parameter: Any?, isBack: Boolean) { + fun onShown(parameter: Any?) { if (parameter is SourcePluginConfig) { loadConfig(parameter); updateSourceViews(); @@ -135,7 +137,7 @@ class SourceDetailFragment : MainFragment() { try { _settingsAppForm.fromObject(source.descriptor.appSettings); _settingsAppForm.onChanged.clear(); - _settingsAppForm.onChanged.subscribe { field, value -> + _settingsAppForm.onChanged.subscribe { _, _ -> _settingsAppChanged = true; } } catch (e: Throwable) { @@ -150,7 +152,7 @@ class SourceDetailFragment : MainFragment() { context.getString(R.string.these_settings_are_defined_by_the_plugin) ); _settingsForm.onChanged.clear(); - _settingsForm.onChanged.subscribe { field, value -> + _settingsForm.onChanged.subscribe { _, _ -> _settingsChanged = true; } } catch (e: Throwable) { @@ -478,8 +480,8 @@ class SourceDetailFragment : MainFragment() { Logger.i(TAG, "Update is available (config.version=${config.version}, source.config.version=${c.version})."); - val c = context ?: return@launch; - val intent = Intent(c, AddSourceActivity::class.java).apply { + val ctx = context ?: return@launch; + val intent = Intent(ctx, AddSourceActivity::class.java).apply { data = Uri.parse(sourceUrl) }; diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SourcesFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SourcesFragment.kt index 83e94208..bfe49af6 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SourcesFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SourcesFragment.kt @@ -1,5 +1,6 @@ package com.futo.platformplayer.fragment.mainactivity.main +import android.annotation.SuppressLint import android.content.Context import android.content.Intent import android.os.Bundle @@ -10,23 +11,23 @@ import android.widget.LinearLayout import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import com.futo.platformplayer.UIDialogs -import com.futo.platformplayer.states.StatePlatform import com.futo.platformplayer.R +import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.activities.AddSourceOptionsActivity import com.futo.platformplayer.api.media.IPlatformClient import com.futo.platformplayer.api.media.platforms.js.JSClient import com.futo.platformplayer.fragment.mainactivity.topbar.AddTopBarFragment +import com.futo.platformplayer.states.StatePlatform import com.futo.platformplayer.states.StatePlugins import com.futo.platformplayer.stores.FragmentedStorage import com.futo.platformplayer.stores.SubscriptionStorage -import com.futo.platformplayer.views.sources.SourceUnderConstructionView import com.futo.platformplayer.views.adapters.DisabledSourceView import com.futo.platformplayer.views.adapters.EnabledSourceAdapter import com.futo.platformplayer.views.adapters.EnabledSourceViewHolder import com.futo.platformplayer.views.adapters.ItemMoveCallback +import com.futo.platformplayer.views.sources.SourceUnderConstructionView import kotlinx.coroutines.runBlocking -import java.util.* +import java.util.Collections class SourcesFragment : MainFragment() { override val isMainView : Boolean = true; @@ -159,15 +160,15 @@ class SourcesFragment : MainFragment() { _didCreateView = true; } + @SuppressLint("NotifyDataSetChanged") fun reloadSources() { enabledSources.clear(); disabledSources.clear(); enabledSources.addAll(StatePlatform.instance.getSortedEnabledClient()); disabledSources.addAll(StatePlatform.instance.getAvailableClients().filter { !enabledSources.contains(it) }); - _adapterSourcesEnabled?.notifyDataSetChanged(); + _adapterSourcesEnabled.notifyDataSetChanged(); setCanRemove(enabledSources.size > 1); - //_adapterSourcesDisabled?.notifyDataSetChanged(); updateDisabledSources(); if(_didCreateView) { @@ -207,18 +208,15 @@ class SourcesFragment : MainFragment() { } private fun setCanRemove(canRemove: Boolean) { - val recyclerSourcesEnabled = _recyclerSourcesEnabled ?: return; - var adapterSourcesEnabled = _adapterSourcesEnabled ?: return; - - for (i in 0 until recyclerSourcesEnabled.childCount) { - val view: View = recyclerSourcesEnabled.getChildAt(i) - val viewHolder = recyclerSourcesEnabled.getChildViewHolder(view) + for (i in 0 until _recyclerSourcesEnabled.childCount) { + val view: View = _recyclerSourcesEnabled.getChildAt(i) + val viewHolder = _recyclerSourcesEnabled.getChildViewHolder(view) if (viewHolder is EnabledSourceViewHolder) { viewHolder.setCanRemove(canRemove); } } - adapterSourcesEnabled.canRemove = canRemove; + _adapterSourcesEnabled.canRemove = canRemove; } private fun onPrimaryChanged(client: IPlatformClient) { diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SubscriptionsFeedFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SubscriptionsFeedFragment.kt index fd821356..cd5dca38 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SubscriptionsFeedFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SubscriptionsFeedFragment.kt @@ -27,12 +27,12 @@ import com.futo.platformplayer.states.StatePlatform import com.futo.platformplayer.states.StateSubscriptions import com.futo.platformplayer.stores.FragmentedStorage import com.futo.platformplayer.stores.FragmentedStorageFileJson -import com.futo.platformplayer.views.announcements.AnnouncementView import com.futo.platformplayer.views.FeedStyle import com.futo.platformplayer.views.NoResultsView import com.futo.platformplayer.views.adapters.ContentPreviewViewHolder import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader import com.futo.platformplayer.views.adapters.InsertedViewHolder +import com.futo.platformplayer.views.announcements.AnnouncementView import com.futo.platformplayer.views.buttons.BigButton import com.futo.platformplayer.views.subscriptions.SubscriptionBar import kotlinx.coroutines.CancellationException @@ -111,7 +111,7 @@ class SubscriptionsFeedFragment : MainFragment() { } }; - StateSubscriptions.instance.onSubscriptionsChanged.subscribe(this) { subs, added -> + StateSubscriptions.instance.onSubscriptionsChanged.subscribe(this) { _, added -> if(!added) StateSubscriptions.instance.clearSubscriptionFeed(); StateApp.instance.scopeOrNull?.let { @@ -146,7 +146,7 @@ class SubscriptionsFeedFragment : MainFragment() { val homeTab = Settings.instance.tabs.find { it.id == 0 }; val isHomeEnabled = homeTab?.enabled == true; if (announcementsView != null && isHomeEnabled) { - headerView?.removeView(announcementsView); + headerView.removeView(announcementsView); _announcementsView = null; } @@ -154,7 +154,7 @@ class SubscriptionsFeedFragment : MainFragment() { val c = context; if (c != null) { _announcementsView = AnnouncementView(c, null).apply { - headerView?.addView(this) + headerView.addView(this) }; } } @@ -272,17 +272,18 @@ class SubscriptionsFeedFragment : MainFragment() { } private fun toggleFilterContentType(contentType: ContentType, isTrue: Boolean) { synchronized(_filterLock) { - if(!isTrue) + if(!isTrue) { _filterSettings.allowContentTypes.remove(contentType); - else if(!_filterSettings.allowContentTypes.contains(contentType)) + } else if(!_filterSettings.allowContentTypes.contains(contentType)) { _filterSettings.allowContentTypes.add(contentType) - else null; + } _filterSettings.save(); }; - if(Settings.instance.subscriptions.fetchOnTabOpen) //TODO: Do this different, temporary workaround + if(Settings.instance.subscriptions.fetchOnTabOpen) { //TODO: Do this different, temporary workaround loadResults(false); - else + } else { loadCache(); + } } override fun filterResults(results: List): List { @@ -381,7 +382,7 @@ class SubscriptionsFeedFragment : MainFragment() { context?.let { fragment.lifecycleScope.launch(Dispatchers.Main) { try { - if (exs!!.size <= 8) { + if (exs.size <= 8) { for (ex in exs) { var toShow = ex; var channel: String? = null; diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailFragment.kt index 476acfbb..4f1b087e 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailFragment.kt @@ -3,16 +3,17 @@ package com.futo.platformplayer.fragment.mainactivity.main import android.content.pm.ActivityInfo import android.content.res.Configuration import android.os.Bundle -import android.view.* +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup import androidx.constraintlayout.motion.widget.MotionLayout -import androidx.core.view.* -import com.futo.platformplayer.logging.Logger -import com.futo.platformplayer.states.StatePlayer +import androidx.core.view.WindowCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.WindowInsetsControllerCompat import com.futo.platformplayer.R import com.futo.platformplayer.Settings import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.activities.MainActivity -import com.futo.platformplayer.api.media.models.contents.IPlatformContent import com.futo.platformplayer.api.media.models.video.IPlatformVideo import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails import com.futo.platformplayer.casting.CastConnectionState @@ -20,8 +21,10 @@ import com.futo.platformplayer.casting.StateCasting import com.futo.platformplayer.constructs.Event0 import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.listeners.OrientationManager +import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.models.PlatformVideoWithTime import com.futo.platformplayer.models.UrlVideoWithTime +import com.futo.platformplayer.states.StatePlayer import com.futo.platformplayer.states.StateSaved import com.futo.platformplayer.states.VideoToOpen import com.futo.platformplayer.views.containers.SingleViewTouchableMotionLayout @@ -269,7 +272,7 @@ class VideoDetailFragment : MainFragment { val viewDetail = _viewDetail; Logger.i(TAG, "onUserLeaveHint preventPictureInPicture=${viewDetail?.preventPictureInPicture} isCasting=${StateCasting.instance.isCasting} isBackgroundPictureInPicture=${Settings.instance.playback.isBackgroundPictureInPicture()} allowBackground=${viewDetail?.allowBackground}"); - if(viewDetail?.preventPictureInPicture == false && !StateCasting.instance.isCasting && Settings.instance.playback.isBackgroundPictureInPicture() && viewDetail?.allowBackground != true) { + if(viewDetail?.preventPictureInPicture == false && !StateCasting.instance.isCasting && Settings.instance.playback.isBackgroundPictureInPicture() && !viewDetail.allowBackground) { _leavingPiP = false; val params = _viewDetail?.getPictureInPictureParams(); diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt index f1ef3ec4..e7bc360f 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt @@ -1,3 +1,5 @@ +@file:Suppress("DEPRECATION") + package com.futo.platformplayer.fragment.mainactivity.main import android.app.PictureInPictureParams @@ -23,16 +25,21 @@ import android.view.View import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import android.view.WindowManager -import android.widget.* +import android.widget.FrameLayout +import android.widget.ImageButton +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView import androidx.constraintlayout.widget.ConstraintLayout import androidx.lifecycle.lifecycleScope import com.bumptech.glide.Glide import com.bumptech.glide.request.target.CustomTarget import com.bumptech.glide.request.transition.Transition -import com.futo.platformplayer.* - -import com.futo.platformplayer.api.media.IPluginSourced import com.futo.platformplayer.R +import com.futo.platformplayer.Settings +import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.UISlideOverlays +import com.futo.platformplayer.api.media.IPluginSourced import com.futo.platformplayer.api.media.LiveChatManager import com.futo.platformplayer.api.media.PlatformID import com.futo.platformplayer.api.media.exceptions.ContentNotAvailableYetException @@ -46,12 +53,17 @@ import com.futo.platformplayer.api.media.models.playback.IPlaybackTracker import com.futo.platformplayer.api.media.models.ratings.RatingLikeDislikes import com.futo.platformplayer.api.media.models.ratings.RatingLikes import com.futo.platformplayer.api.media.models.streams.VideoUnMuxedSourceDescriptor -import com.futo.platformplayer.api.media.models.streams.sources.* +import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource +import com.futo.platformplayer.api.media.models.streams.sources.IDashManifestSource +import com.futo.platformplayer.api.media.models.streams.sources.IHLSManifestSource +import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource +import com.futo.platformplayer.api.media.models.streams.sources.LocalAudioSource +import com.futo.platformplayer.api.media.models.streams.sources.LocalSubtitleSource +import com.futo.platformplayer.api.media.models.streams.sources.LocalVideoSource import com.futo.platformplayer.api.media.models.subtitles.ISubtitleSource import com.futo.platformplayer.api.media.models.video.IPlatformVideo import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo -import com.futo.platformplayer.api.media.platforms.js.JSClient import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig import com.futo.platformplayer.api.media.platforms.js.models.JSVideoDetails import com.futo.platformplayer.api.media.structures.IPager @@ -61,21 +73,41 @@ import com.futo.platformplayer.constructs.Event0 import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.constructs.TaskHandler import com.futo.platformplayer.downloads.VideoLocal +import com.futo.platformplayer.dp import com.futo.platformplayer.engine.exceptions.ScriptAgeException import com.futo.platformplayer.engine.exceptions.ScriptException import com.futo.platformplayer.engine.exceptions.ScriptImplementationException import com.futo.platformplayer.engine.exceptions.ScriptLoginRequiredException import com.futo.platformplayer.engine.exceptions.ScriptUnavailableException import com.futo.platformplayer.exceptions.UnsupportedCastException +import com.futo.platformplayer.fixHtmlLinks +import com.futo.platformplayer.fixHtmlWhitespace +import com.futo.platformplayer.fullyBackfillServersAnnounceExceptions +import com.futo.platformplayer.getNowDiffSeconds import com.futo.platformplayer.helpers.VideoHelper import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.models.Subscription import com.futo.platformplayer.polycentric.PolycentricCache import com.futo.platformplayer.receivers.MediaControlReceiver -import com.futo.platformplayer.states.* +import com.futo.platformplayer.selectBestImage +import com.futo.platformplayer.states.AnnouncementType +import com.futo.platformplayer.states.StateAnnouncement +import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.states.StateDownloads +import com.futo.platformplayer.states.StateHistory +import com.futo.platformplayer.states.StatePlatform +import com.futo.platformplayer.states.StatePlayer +import com.futo.platformplayer.states.StatePlaylists +import com.futo.platformplayer.states.StatePlugins +import com.futo.platformplayer.states.StatePolycentric +import com.futo.platformplayer.states.StateSubscriptions import com.futo.platformplayer.stores.FragmentedStorage import com.futo.platformplayer.stores.StringArrayStorage import com.futo.platformplayer.stores.db.types.DBHistory +import com.futo.platformplayer.toHumanBitrate +import com.futo.platformplayer.toHumanNowDiffString +import com.futo.platformplayer.toHumanNumber +import com.futo.platformplayer.toHumanTime import com.futo.platformplayer.views.MonetizationView import com.futo.platformplayer.views.behavior.TouchInterceptFrameLayout import com.futo.platformplayer.views.casting.CastView @@ -87,7 +119,11 @@ import com.futo.platformplayer.views.overlays.LiveChatOverlay import com.futo.platformplayer.views.overlays.QueueEditorOverlay import com.futo.platformplayer.views.overlays.RepliesOverlay import com.futo.platformplayer.views.overlays.SupportOverlay -import com.futo.platformplayer.views.overlays.slideup.* +import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuButtonList +import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuGroup +import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuItem +import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuOverlay +import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuTitle import com.futo.platformplayer.views.pills.PillRatingLikesDislikes import com.futo.platformplayer.views.pills.RoundButton import com.futo.platformplayer.views.pills.RoundButtonGroup @@ -97,17 +133,25 @@ import com.futo.platformplayer.views.subscriptions.SubscribeButton import com.futo.platformplayer.views.video.FutoVideoPlayer import com.futo.platformplayer.views.video.FutoVideoPlayerBase import com.futo.platformplayer.views.videometa.UpNextView -import com.futo.polycentric.core.* +import com.futo.polycentric.core.ApiMethods +import com.futo.polycentric.core.ContentType +import com.futo.polycentric.core.Models +import com.futo.polycentric.core.Opinion +import com.futo.polycentric.core.toURLInfoSystemLinkUrl import com.google.android.exoplayer2.C import com.google.android.exoplayer2.Format import com.google.android.exoplayer2.ui.PlayerControlView import com.google.android.exoplayer2.ui.TimeBar import com.google.android.exoplayer2.upstream.HttpDataSource.InvalidResponseCodeException import com.google.protobuf.ByteString -import kotlinx.coroutines.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import userpackage.Protocol import java.time.OffsetDateTime -import kotlin.collections.ArrayList import kotlin.math.abs import kotlin.math.roundToLong @@ -336,7 +380,7 @@ class VideoDetailView : ConstraintLayout { }; _monetization.onSupportTap.subscribe { - _container_content_support.setPolycentricProfile(_polycentricProfile?.profile, false); + _container_content_support.setPolycentricProfile(_polycentricProfile?.profile); switchContentView(_container_content_support); }; @@ -484,7 +528,7 @@ class VideoDetailView : ConstraintLayout { }; if (!isInEditMode) { - StateCasting.instance.onActiveDeviceConnectionStateChanged.subscribe(this) { d, connectionState -> + StateCasting.instance.onActiveDeviceConnectionStateChanged.subscribe(this) { _, connectionState -> if (_onPauseCalled) { return@subscribe; } @@ -530,7 +574,7 @@ class VideoDetailView : ConstraintLayout { } _playerProgress.player = _player.exoPlayer?.player; - _playerProgress.setProgressUpdateListener { position, bufferedPosition -> + _playerProgress.setProgressUpdateListener { position, _ -> StatePlayer.instance.updateMediaSessionPlaybackState(_player.exoPlayer?.getPlaybackStateCompat() ?: PlaybackStateCompat.STATE_NONE, position); } @@ -658,7 +702,7 @@ class VideoDetailView : ConstraintLayout { _trackingLastVideoSubscription?.let { Logger.i(TAG, "Subscription [${it.channel.name}] watch time delta [${delta}]" + "(${"%.2f".format((_trackingTotalWatched / 1000) / currentVideo.duration.toDouble().coerceAtLeast(1.0))})"); - it.updatePlayback(currentVideo, (delta / 1000).toInt()); + it.updatePlayback((delta / 1000).toInt()); _trackingTotalWatched += delta; if(!_trackingDidCountView && currentVideo.duration > 0) { val percentage = (_trackingTotalWatched / 1000) / currentVideo.duration.toDouble(); @@ -1048,6 +1092,7 @@ class VideoDetailView : ConstraintLayout { switchContentView(_container_content_main); } + @OptIn(ExperimentalCoroutinesApi::class) fun setVideoDetails(videoDetail: IPlatformVideoDetails, newVideo: Boolean = false) { Logger.i(TAG, "setVideoDetails (${videoDetail.name})") @@ -1064,8 +1109,8 @@ class VideoDetailView : ConstraintLayout { _player.setPlaybackRate(Settings.instance.playback.getDefaultPlaybackSpeed()); } - var videoLocal: VideoLocal? = null; - var video: IPlatformVideoDetails? = null; + val videoLocal: VideoLocal?; + val video: IPlatformVideoDetails?; if(videoDetail is VideoLocal) { videoLocal = videoDetail; @@ -1077,7 +1122,7 @@ class VideoDetailView : ConstraintLayout { return@invokeOnCompletion; } val result = videoTask.getCompleted(); - if(this.video == videoDetail && result != null && result is IPlatformVideoDetails) { + if(this.video == videoDetail && result is IPlatformVideoDetails) { this.video = result; fragment.lifecycleScope.launch(Dispatchers.Main) { updateQualitySourcesOverlay(result, videoLocal); @@ -1246,37 +1291,33 @@ class VideoDetailView : ConstraintLayout { _rating.visibility = View.GONE; } - if (video.rating != null) { - when (video.rating) { - is RatingLikeDislikes -> { - val r = video.rating as RatingLikeDislikes; - _layoutRating.visibility = View.VISIBLE; + when (video.rating) { + is RatingLikeDislikes -> { + val r = video.rating as RatingLikeDislikes; + _layoutRating.visibility = View.VISIBLE; - _textLikes.visibility = View.VISIBLE; - _imageLikeIcon.visibility = View.VISIBLE; - _textLikes.text = r.likes.toHumanNumber(); + _textLikes.visibility = View.VISIBLE; + _imageLikeIcon.visibility = View.VISIBLE; + _textLikes.text = r.likes.toHumanNumber(); - _imageDislikeIcon.visibility = View.VISIBLE; - _textDislikes.visibility = View.VISIBLE; - _textDislikes.text = r.dislikes.toHumanNumber(); - } - is RatingLikes -> { - val r = video.rating as RatingLikes; - _layoutRating.visibility = View.VISIBLE; - - _textLikes.visibility = View.VISIBLE; - _imageLikeIcon.visibility = View.VISIBLE; - _textLikes.text = r.likes.toHumanNumber(); - - _imageDislikeIcon.visibility = View.GONE; - _textDislikes.visibility = View.GONE; - } - else -> { - _layoutRating.visibility = View.GONE; - } + _imageDislikeIcon.visibility = View.VISIBLE; + _textDislikes.visibility = View.VISIBLE; + _textDislikes.text = r.dislikes.toHumanNumber(); + } + is RatingLikes -> { + val r = video.rating as RatingLikes; + _layoutRating.visibility = View.VISIBLE; + + _textLikes.visibility = View.VISIBLE; + _imageLikeIcon.visibility = View.VISIBLE; + _textLikes.text = r.likes.toHumanNumber(); + + _imageDislikeIcon.visibility = View.GONE; + _textDislikes.visibility = View.GONE; + } + else -> { + _layoutRating.visibility = View.GONE; } - } else { - _layoutRating.visibility = View.GONE; } @@ -1636,7 +1677,7 @@ class VideoDetailView : ConstraintLayout { SlideUpMenuGroup(this.context, context.getString(R.string.offline_video), "video", *localVideoSources .map { - SlideUpMenuItem(this.context, R.drawable.ic_movie, it!!.name, "${it.width}x${it.height}", it, + SlideUpMenuItem(this.context, R.drawable.ic_movie, it.name, "${it.width}x${it.height}", it, { handleSelectVideoTrack(it) }); }.toList().toTypedArray()) else null, @@ -1660,7 +1701,7 @@ class VideoDetailView : ConstraintLayout { SlideUpMenuGroup(this.context, context.getString(R.string.stream_video), "video", *liveStreamVideoFormats .map { - SlideUpMenuItem(this.context, R.drawable.ic_movie, it?.label ?: it.containerMimeType ?: it.bitrate.toString(), "${it.width}x${it.height}", it, + SlideUpMenuItem(this.context, R.drawable.ic_movie, it.label ?: it.containerMimeType ?: it.bitrate.toString(), "${it.width}x${it.height}", it, { _player.selectVideoTrack(it.height) }); }.toList().toTypedArray()) else null, @@ -1668,7 +1709,7 @@ class VideoDetailView : ConstraintLayout { SlideUpMenuGroup(this.context, context.getString(R.string.stream_audio), "audio", *liveStreamAudioFormats .map { - SlideUpMenuItem(this.context, R.drawable.ic_music, "${it?.label ?: it.containerMimeType} ${it.bitrate}", "", it, + SlideUpMenuItem(this.context, R.drawable.ic_music, "${it.label ?: it.containerMimeType} ${it.bitrate}", "", it, { _player.selectAudioTrack(it.bitrate) }); }.toList().toTypedArray()) else null, @@ -2216,7 +2257,7 @@ class VideoDetailView : ConstraintLayout { _channelName.text = username } - _monetization.setPolycentricProfile(cachedPolycentricProfile, animate); + _monetization.setPolycentricProfile(cachedPolycentricProfile); } fun setProgressBarOverlayed(isOverlayed: Boolean?) { @@ -2292,13 +2333,13 @@ class VideoDetailView : ConstraintLayout { StateAnnouncement.instance.registerAnnouncement(video?.id?.value + "_Q_NOSOURCES", context.getString(R.string.video_without_source), context.getString(R.string.there_was_a_in_your_queue_videoname_by_authorname_without_the_required_source_being_enabled_playback_was_skipped).replace("{videoName}", video?.name ?: "").replace("{authorName}", video?.author?.name ?: ""), AnnouncementType.SESSION) } } - .exception { - Logger.w(TAG, "exception", it); + .exception { e -> + Logger.w(TAG, "exception", e); - UIDialogs.showDialog(context, R.drawable.ic_security, "Authentication", it.message, null, 0, + UIDialogs.showDialog(context, R.drawable.ic_security, "Authentication", e.message, null, 0, UIDialogs.Action("Cancel", {}), UIDialogs.Action("Login", { - val id = it.config?.let { if(it is SourcePluginConfig) it.id else null }; + val id = e.config.let { if(it is SourcePluginConfig) it.id else null }; val didLogin = if(id == null) false else StatePlugins.instance.loginPlugin(context, id) { diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoListEditorView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoListEditorView.kt index c43d926c..c44c6cab 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoListEditorView.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoListEditorView.kt @@ -52,7 +52,7 @@ abstract class VideoListEditorView : LinearLayout { _buttonShare.visibility = View.VISIBLE; } else - _buttonShare?.visibility = View.GONE; + _buttonShare.visibility = View.GONE; buttonPlayAll.setOnClickListener { onPlayAllClick(); }; buttonShuffle.setOnClickListener { onShuffleClick(); }; @@ -106,11 +106,9 @@ abstract class VideoListEditorView : LinearLayout { }; } else { _textMetadata.text = "0 " + context.getString(R.string.videos); - if(_imagePlaylistThumbnail != null) { - Glide.with(_imagePlaylistThumbnail) - .load(R.drawable.placeholder_video_thumbnail) - .into(_imagePlaylistThumbnail); - } + Glide.with(_imagePlaylistThumbnail) + .load(R.drawable.placeholder_video_thumbnail) + .into(_imagePlaylistThumbnail) } _videoListEditorView.setVideos(videos, canEdit); diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/WatchLaterFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/WatchLaterFragment.kt index 9b1e847a..807efcbb 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/WatchLaterFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/WatchLaterFragment.kt @@ -5,10 +5,10 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import com.futo.platformplayer.states.StatePlayer -import com.futo.platformplayer.states.StatePlaylists import com.futo.platformplayer.api.media.models.video.IPlatformVideo import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo +import com.futo.platformplayer.states.StatePlayer +import com.futo.platformplayer.states.StatePlaylists class WatchLaterFragment : MainFragment() { override val isMainView : Boolean = true; @@ -19,7 +19,7 @@ class WatchLaterFragment : MainFragment() { override fun onShownWithView(parameter: Any?, isBack: Boolean) { super.onShownWithView(parameter, isBack); - _view?.onShown(parameter, isBack); + _view?.onShown(); } override fun onCreateMainView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { @@ -42,7 +42,7 @@ class WatchLaterFragment : MainFragment() { } - fun onShown(parameter: Any ?, isBack: Boolean) { + fun onShown() { setName("Watch Later"); setVideos(StatePlaylists.instance.getWatchLater(), true); } diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/ImportTopBarFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/ImportTopBarFragment.kt index 1d157ce5..076e2c6f 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/ImportTopBarFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/ImportTopBarFragment.kt @@ -6,6 +6,7 @@ import android.view.View import android.view.ViewGroup import android.widget.ImageButton import android.widget.TextView +import androidx.core.content.ContextCompat import com.futo.platformplayer.R import com.futo.platformplayer.api.media.IPlatformClient import com.futo.platformplayer.constructs.Event0 @@ -73,9 +74,9 @@ class ImportTopBarFragment : TopFragment() { fun setImportEnabled(enabled: Boolean) { if (enabled) { - _textImport?.setTextColor(resources.getColor(R.color.colorPrimary)); + _textImport?.setTextColor(ContextCompat.getColor(requireContext(), R.color.colorPrimary)); } else { - _textImport?.setTextColor(resources.getColor(R.color.gray_67)); + _textImport?.setTextColor(ContextCompat.getColor(requireContext(), R.color.gray_67)); } _importEnabled = enabled; diff --git a/app/src/main/java/com/futo/platformplayer/helpers/VideoHelper.kt b/app/src/main/java/com/futo/platformplayer/helpers/VideoHelper.kt index e40f83cb..3c03472c 100644 --- a/app/src/main/java/com/futo/platformplayer/helpers/VideoHelper.kt +++ b/app/src/main/java/com/futo/platformplayer/helpers/VideoHelper.kt @@ -1,9 +1,10 @@ +@file:Suppress("DEPRECATION") + package com.futo.platformplayer.helpers import android.net.Uri import com.futo.platformplayer.api.media.models.streams.IVideoSourceDescriptor import com.futo.platformplayer.api.media.models.streams.VideoUnMuxedSourceDescriptor -import com.futo.platformplayer.api.media.models.streams.sources.HLSManifestSource import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource import com.futo.platformplayer.api.media.models.streams.sources.IAudioUrlSource import com.futo.platformplayer.api.media.models.streams.sources.IHLSManifestAudioSource @@ -19,6 +20,7 @@ import com.google.android.exoplayer2.source.MediaSource import com.google.android.exoplayer2.source.dash.DashMediaSource import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser import com.google.android.exoplayer2.upstream.ResolvingDataSource +import kotlin.math.abs class VideoHelper { companion object { @@ -43,19 +45,17 @@ class VideoHelper { fun selectBestVideoSource(desc: IVideoSourceDescriptor, desiredPixelCount : Int, prefContainers : Array) : IVideoSource? = selectBestVideoSource(desc.videoSources.toList(), desiredPixelCount, prefContainers); fun selectBestVideoSource(sources: Iterable, desiredPixelCount : Int, prefContainers : Array) : IVideoSource? { - val targetVideo = if(desiredPixelCount > 0) - sources.toList() - .sortedBy { x -> Math.abs(x.height * x.width - desiredPixelCount) } - .firstOrNull(); - else - sources.toList() - .lastOrNull(); + val targetVideo = if(desiredPixelCount > 0) { + sources.toList().minByOrNull { x -> abs(x.height * x.width - desiredPixelCount) }; + } else { + sources.toList().lastOrNull(); + } val hasPriority = sources.any { it.priority }; val targetPixelCount = if(targetVideo != null) targetVideo.width * targetVideo.height else desiredPixelCount; val altSources = if(hasPriority) { - sources.filter { it.priority }.sortedBy { x -> Math.abs(x.height * x.width - targetPixelCount) }; + sources.filter { it.priority }.sortedBy { x -> abs(x.height * x.width - targetPixelCount) }; } else { sources.filter { it.height == (targetVideo?.height ?: 0) }; } @@ -76,33 +76,42 @@ class VideoHelper { fun selectBestAudioSource(desc: IVideoSourceDescriptor, prefContainers : Array, prefLanguage: String? = null, targetBitrate: Long? = null) : IAudioSource? { if(!desc.isUnMuxed) return null; - return selectBestAudioSource((desc as VideoUnMuxedSourceDescriptor).audioSources.toList(), prefContainers, prefLanguage); + + return selectBestAudioSource((desc as VideoUnMuxedSourceDescriptor).audioSources.toList(), prefContainers, prefLanguage, targetBitrate); } fun selectBestAudioSource(altSources : Iterable, prefContainers : Array, preferredLanguage: String? = null, targetBitrate: Long? = null) : IAudioSource? { - val languageToFilter = if(preferredLanguage != null && altSources.any { it.language == preferredLanguage }) + val languageToFilter = if(preferredLanguage != null && altSources.any { it.language == preferredLanguage }) { preferredLanguage - else if(preferredLanguage == null) null - else "Unknown"; + } else if(preferredLanguage == null) { + null + } else { + "Unknown" + } - var usableSources = if(languageToFilter != null && altSources.any { it.language == languageToFilter }) + var usableSources = if(languageToFilter != null && altSources.any { it.language == languageToFilter }) { altSources.filter { it.language == languageToFilter }.sortedBy { it.bitrate }.toList(); - else altSources.sortedBy { it.bitrate }; + } else { + altSources.sortedBy { it.bitrate } + } - if(usableSources.any { it.priority }) + if(usableSources.any { it.priority }) { usableSources = usableSources.filter { it.priority }; + } - var bestSource = if(targetBitrate != null) - usableSources.minByOrNull { Math.abs(it.bitrate - targetBitrate) }; - else + var bestSource = if(targetBitrate != null) { + usableSources.minByOrNull { abs(it.bitrate - targetBitrate) }; + } else { usableSources.lastOrNull(); + } for (prefContainer in prefContainers) { val betterSources = usableSources.filter { it.container == prefContainer }; - val betterSource = if(targetBitrate != null) - betterSources.minByOrNull { Math.abs(it.bitrate - targetBitrate) }; - else + val betterSource = if(targetBitrate != null) { + betterSources.minByOrNull { abs(it.bitrate - targetBitrate) }; + } else { betterSources.lastOrNull(); + } if(betterSource != null) { bestSource = betterSource; @@ -112,17 +121,9 @@ class VideoHelper { return bestSource; } - var breakOnce = hashSetOf() + @Suppress("DEPRECATION") fun convertItagSourceToChunkedDashSource(videoSource: JSVideoUrlRangeSource) : MediaSource { - var urlToUse = videoSource.getVideoUrl(); - /* - //TODO: REMOVE THIS, PURPOSELY 403s - if(urlToUse.contains("sig=") && !breakOnce.contains(urlToUse)) { - breakOnce.add(urlToUse); - val sigIndex = urlToUse.indexOf("sig="); - urlToUse = urlToUse.substring(0, sigIndex) + "sig=0" + urlToUse.substring(sigIndex + 4); - }*/ - + val urlToUse = videoSource.getVideoUrl(); val manifestConfig = ProgressiveDashManifestCreator.fromVideoProgressiveStreamingUrl(urlToUse, videoSource.duration * 1000, videoSource.container, @@ -143,13 +144,10 @@ class VideoHelper { return DashMediaSource.Factory(ResolvingDataSource.Factory(videoSource.getHttpDataSourceFactory(), ResolvingDataSource.Resolver { dataSpec -> Logger.v("PLAYBACK", "Video REQ Range [" + dataSpec.position + "-" + (dataSpec.position + dataSpec.length) + "](" + dataSpec.length + ")", null); return@Resolver dataSpec; - })) - .createMediaSource(manifest, - MediaItem.Builder() - .setUri(Uri.parse(videoSource.getVideoUrl())) - .build()) + })).createMediaSource(manifest, MediaItem.Builder().setUri(Uri.parse(videoSource.getVideoUrl())).build()) } + @Suppress("DEPRECATION") fun convertItagSourceToChunkedDashSource(audioSource: JSAudioUrlRangeSource) : MediaSource { val manifestConfig = ProgressiveDashManifestCreator.fromAudioProgressiveStreamingUrl(audioSource.getAudioUrl(), audioSource.duration?.times(1000) ?: 0, @@ -170,11 +168,7 @@ class VideoHelper { return DashMediaSource.Factory(ResolvingDataSource.Factory(audioSource.getHttpDataSourceFactory(), ResolvingDataSource.Resolver { dataSpec -> Logger.v("PLAYBACK", "Audio REQ Range [" + dataSpec.position + "-" + (dataSpec.position + dataSpec.length) + "](" + dataSpec.length + ")", null); return@Resolver dataSpec; - })) - .createMediaSource(manifest, - MediaItem.Builder() - .setUri(Uri.parse(audioSource.getAudioUrl())) - .build()) + })).createMediaSource(manifest, MediaItem.Builder().setUri(Uri.parse(audioSource.getAudioUrl())).build()) } } } diff --git a/app/src/main/java/com/futo/platformplayer/models/Subscription.kt b/app/src/main/java/com/futo/platformplayer/models/Subscription.kt index b2d41b6e..0d2c5382 100644 --- a/app/src/main/java/com/futo/platformplayer/models/Subscription.kt +++ b/app/src/main/java/com/futo/platformplayer/models/Subscription.kt @@ -4,7 +4,6 @@ import com.futo.platformplayer.api.media.models.ResultCapabilities import com.futo.platformplayer.api.media.models.channels.IPlatformChannel import com.futo.platformplayer.api.media.models.channels.SerializedChannel import com.futo.platformplayer.api.media.models.contents.IPlatformContent -import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails import com.futo.platformplayer.getNowDiffDays import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.serializers.OffsetDateTimeSerializer @@ -74,7 +73,7 @@ class Subscription { this.channel = SerializedChannel.fromChannel(channel); } - fun updatePlayback(content: IPlatformContentDetails, seconds: Int) { + fun updatePlayback(seconds: Int) { playbackSeconds += seconds; } fun addPlaybackView() { diff --git a/app/src/main/java/com/futo/platformplayer/others/CaptchaWebViewClient.kt b/app/src/main/java/com/futo/platformplayer/others/CaptchaWebViewClient.kt index bf5abc50..90ae414a 100644 --- a/app/src/main/java/com/futo/platformplayer/others/CaptchaWebViewClient.kt +++ b/app/src/main/java/com/futo/platformplayer/others/CaptchaWebViewClient.kt @@ -3,7 +3,6 @@ package com.futo.platformplayer.others import android.webkit.* import com.futo.platformplayer.api.media.Serializer import com.futo.platformplayer.api.media.platforms.js.SourceCaptchaData -import com.futo.platformplayer.api.media.platforms.js.SourcePluginAuthConfig import com.futo.platformplayer.api.media.platforms.js.SourcePluginCaptchaConfig import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig import com.futo.platformplayer.constructs.Event1 @@ -58,7 +57,7 @@ class CaptchaWebViewClient : WebViewClient { if(request == null) return super.shouldInterceptRequest(view, request as WebResourceRequest?); - val extracted = _extractor.handleRequest(view, request); + val extracted = _extractor.handleRequest(request); if(extracted != null && !_didNotify) { _didNotify = true; onCaptchaFinished.emit(SourceCaptchaData( diff --git a/app/src/main/java/com/futo/platformplayer/others/LoginWebViewClient.kt b/app/src/main/java/com/futo/platformplayer/others/LoginWebViewClient.kt index b442508a..0d6e5eea 100644 --- a/app/src/main/java/com/futo/platformplayer/others/LoginWebViewClient.kt +++ b/app/src/main/java/com/futo/platformplayer/others/LoginWebViewClient.kt @@ -1,19 +1,22 @@ package com.futo.platformplayer.others import android.net.Uri -import android.webkit.* +import android.webkit.CookieManager +import android.webkit.WebResourceRequest +import android.webkit.WebResourceResponse +import android.webkit.WebView +import android.webkit.WebViewClient import com.futo.platformplayer.BuildConfig import com.futo.platformplayer.api.http.ManagedHttpClient import com.futo.platformplayer.api.media.Serializer -import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.api.media.platforms.js.SourceAuth import com.futo.platformplayer.api.media.platforms.js.SourcePluginAuthConfig import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig +import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.constructs.Event2 import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.matchesDomain import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json class LoginWebViewClient : WebViewClient { private val LOG_VERBOSE = false; @@ -30,9 +33,9 @@ class LoginWebViewClient : WebViewClient { _pluginConfig = config; _authConfig = config.authentication!!; Logger.i(TAG, "Login [${config.name}]" + - "\nRequired Headers: ${config.authentication?.headersToFind?.joinToString(", ")}" + - "\nRequired Domain Headers: ${Serializer.json.encodeToString(config.authentication?.domainHeadersToFind)}" + - "\nRequired Cookies: ${Serializer.json.encodeToString(config.authentication?.cookiesToFind)}",); + "\nRequired Headers: ${config.authentication.headersToFind?.joinToString(", ")}" + + "\nRequired Domain Headers: ${Serializer.json.encodeToString(config.authentication.domainHeadersToFind)}" + + "\nRequired Cookies: ${Serializer.json.encodeToString(config.authentication.cookiesToFind)}",); } constructor(auth: SourcePluginAuthConfig) : super() { _pluginConfig = null; diff --git a/app/src/main/java/com/futo/platformplayer/others/WebViewRequirementExtractor.kt b/app/src/main/java/com/futo/platformplayer/others/WebViewRequirementExtractor.kt index b62138a8..1722d274 100644 --- a/app/src/main/java/com/futo/platformplayer/others/WebViewRequirementExtractor.kt +++ b/app/src/main/java/com/futo/platformplayer/others/WebViewRequirementExtractor.kt @@ -3,8 +3,6 @@ package com.futo.platformplayer.others import android.net.Uri import android.webkit.CookieManager import android.webkit.WebResourceRequest -import android.webkit.WebView -import com.futo.platformplayer.api.media.platforms.js.SourceAuth import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.matchesDomain @@ -33,13 +31,15 @@ class WebViewRequirementExtractor { } - fun handleRequest(view: WebView?, request: WebResourceRequest, logVerbose: Boolean = false): ExtractedData? { + fun handleRequest(request: WebResourceRequest, logVerbose: Boolean = false): ExtractedData? { val domain = request.url.host; val domainLower = request.url.host?.lowercase(); - if(completionUrl == null) + if (completionUrl == null) { urlFound = true; - else urlFound = urlFound || request.url == Uri.parse(completionUrl); + } else { + urlFound = urlFound || request.url == Uri.parse(completionUrl) + } //HEADERS if(domainLower != null) { diff --git a/app/src/main/java/com/futo/platformplayer/parsers/HLS.kt b/app/src/main/java/com/futo/platformplayer/parsers/HLS.kt index 57f42576..734248b2 100644 --- a/app/src/main/java/com/futo/platformplayer/parsers/HLS.kt +++ b/app/src/main/java/com/futo/platformplayer/parsers/HLS.kt @@ -1,22 +1,12 @@ package com.futo.platformplayer.parsers -import android.view.View -import com.futo.platformplayer.R -import com.futo.platformplayer.UIDialogs -import com.futo.platformplayer.api.http.ManagedHttpClient import com.futo.platformplayer.api.media.models.streams.sources.HLSVariantAudioUrlSource import com.futo.platformplayer.api.media.models.streams.sources.HLSVariantSubtitleUrlSource import com.futo.platformplayer.api.media.models.streams.sources.HLSVariantVideoUrlSource import com.futo.platformplayer.api.media.models.streams.sources.IHLSManifestAudioSource import com.futo.platformplayer.api.media.models.streams.sources.IHLSManifestSource -import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource -import com.futo.platformplayer.states.StateDownloads import com.futo.platformplayer.toYesNo -import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuGroup -import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuItem import com.futo.platformplayer.yesNoToBoolean -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext import java.net.URI import java.time.ZonedDateTime import java.time.format.DateTimeFormatter @@ -73,7 +63,7 @@ class HLS { val segments = mutableListOf() var currentSegment: MediaSegment? = null - lines.forEachIndexed { index, line -> + lines.forEach { line -> when { line.startsWith("#EXTINF:") -> { val duration = line.substringAfter(":").substringBefore(",").toDoubleOrNull() diff --git a/app/src/main/java/com/futo/platformplayer/polycentric/PolycentricCache.kt b/app/src/main/java/com/futo/platformplayer/polycentric/PolycentricCache.kt index ada84f6a..c80bc8f4 100644 --- a/app/src/main/java/com/futo/platformplayer/polycentric/PolycentricCache.kt +++ b/app/src/main/java/com/futo/platformplayer/polycentric/PolycentricCache.kt @@ -1,7 +1,5 @@ package com.futo.platformplayer.polycentric -import com.futo.polycentric.core.* -import userpackage.Protocol import com.futo.platformplayer.api.media.PlatformID import com.futo.platformplayer.constructs.BatchedTaskHandler import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile @@ -12,9 +10,25 @@ import com.futo.platformplayer.serializers.OffsetDateTimeSerializer import com.futo.platformplayer.states.StatePolycentric import com.futo.platformplayer.stores.CachedPolycentricProfileStorage import com.futo.platformplayer.stores.FragmentedStorage +import com.futo.polycentric.core.ApiMethods +import com.futo.polycentric.core.ContentType +import com.futo.polycentric.core.OwnedClaim +import com.futo.polycentric.core.PublicKey +import com.futo.polycentric.core.SignedEvent +import com.futo.polycentric.core.StorageTypeSystemState +import com.futo.polycentric.core.SystemState +import com.futo.polycentric.core.base64ToByteArray +import com.futo.polycentric.core.base64UrlToByteArray +import com.futo.polycentric.core.getClaimIfValid +import com.futo.polycentric.core.getValidClaims import com.google.protobuf.ByteString -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.cancel import kotlinx.serialization.Serializable +import userpackage.Protocol import java.nio.ByteBuffer import java.time.OffsetDateTime import kotlin.system.measureTimeMillis @@ -251,8 +265,11 @@ class PolycentricCache { Logger.v(TAG, "getProfileAsync (id: $id) != null (with retrieved valid claims)") return getProfileAsync(claims.ownedClaims.first().system).await() } else { - if(urlNullCache != null) - _profileUrlCache.setAndSave(urlNullCache, PolycentricCache.CachedPolycentricProfile(null)); + synchronized (_cache) { + if (urlNullCache != null) { + _profileUrlCache.setAndSave(urlNullCache, CachedPolycentricProfile(null)) + } + } return null; } } diff --git a/app/src/main/java/com/futo/platformplayer/receivers/InstallReceiver.kt b/app/src/main/java/com/futo/platformplayer/receivers/InstallReceiver.kt index abac844a..09e41150 100644 --- a/app/src/main/java/com/futo/platformplayer/receivers/InstallReceiver.kt +++ b/app/src/main/java/com/futo/platformplayer/receivers/InstallReceiver.kt @@ -4,9 +4,10 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.pm.PackageInstaller -import com.futo.platformplayer.logging.Logger +import android.os.Build import com.futo.platformplayer.R import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.logging.Logger class InstallReceiver : BroadcastReceiver() { @@ -16,13 +17,19 @@ class InstallReceiver : BroadcastReceiver() { val onReceiveResult = Event1(); } + @Suppress("DEPRECATION") override fun onReceive(context: Context, intent: Intent) { val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -1); Logger.i(TAG, "Received status $status."); when (status) { PackageInstaller.STATUS_PENDING_USER_ACTION -> { - val activityIntent = intent.getParcelableExtra(Intent.EXTRA_INTENT) + val activityIntent: Intent? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java) + } else { + intent.getParcelableExtra(Intent.EXTRA_INTENT) + } + if (activityIntent == null) { Logger.w(TAG, "Received STATUS_PENDING_USER_ACTION and activity intent is null.") return; diff --git a/app/src/main/java/com/futo/platformplayer/serializers/IRatingSerializer.kt b/app/src/main/java/com/futo/platformplayer/serializers/IRatingSerializer.kt index 66b607cb..d11714a1 100644 --- a/app/src/main/java/com/futo/platformplayer/serializers/IRatingSerializer.kt +++ b/app/src/main/java/com/futo/platformplayer/serializers/IRatingSerializer.kt @@ -1,39 +1,37 @@ package com.futo.platformplayer.serializers -import com.futo.platformplayer.api.media.models.ratings.* +import com.futo.platformplayer.api.media.models.ratings.IRating +import com.futo.platformplayer.api.media.models.ratings.RatingLikeDislikes +import com.futo.platformplayer.api.media.models.ratings.RatingLikes +import com.futo.platformplayer.api.media.models.ratings.RatingScaler +import com.futo.platformplayer.api.media.models.ratings.RatingType import kotlinx.serialization.DeserializationStrategy -import kotlinx.serialization.KSerializer -import kotlinx.serialization.PolymorphicSerializer -import kotlinx.serialization.descriptors.PrimitiveKind -import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -import kotlinx.serialization.encoding.decodeStructure -import kotlinx.serialization.json.* -import java.time.LocalDateTime -import java.time.OffsetDateTime -import java.time.ZoneOffset -import kotlin.reflect.KClass +import kotlinx.serialization.json.JsonContentPolymorphicSerializer +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.contentOrNull +import kotlinx.serialization.json.int +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive class IRatingSerializer() : JsonContentPolymorphicSerializer(IRating::class) { - override fun selectDeserializer(element: JsonElement): DeserializationStrategy { + override fun selectDeserializer(element: JsonElement): DeserializationStrategy { val obj = element.jsonObject["type"]; - if(obj?.jsonPrimitive?.isString ?: true) - return when(obj?.jsonPrimitive?.contentOrNull) { + return if(obj?.jsonPrimitive?.isString != false) { + when (obj?.jsonPrimitive?.contentOrNull) { "LIKES" -> RatingLikes.serializer(); "LIKEDISLIKES" -> RatingLikeDislikes.serializer(); "SCALE" -> RatingScaler.serializer(); else -> throw NotImplementedError("Rating Value: ${obj?.jsonPrimitive?.contentOrNull}") }; - else - return when(element.jsonObject["type"]?.jsonPrimitive?.int) { + } else { + when (element.jsonObject["type"]?.jsonPrimitive?.int) { RatingType.LIKES.value -> RatingLikes.serializer(); RatingType.LIKEDISLIKES.value -> RatingLikeDislikes.serializer(); RatingType.SCALE.value -> RatingScaler.serializer(); - else -> throw NotImplementedError("Rating Value: ${obj?.jsonPrimitive?.int}") + else -> throw NotImplementedError("Rating Value: ${obj.jsonPrimitive.int}") }; + } } } \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/serializers/PlatformContentSerializer.kt b/app/src/main/java/com/futo/platformplayer/serializers/PlatformContentSerializer.kt index c2766a59..70808ba2 100644 --- a/app/src/main/java/com/futo/platformplayer/serializers/PlatformContentSerializer.kt +++ b/app/src/main/java/com/futo/platformplayer/serializers/PlatformContentSerializer.kt @@ -6,33 +6,40 @@ import com.futo.platformplayer.api.media.models.video.SerializedPlatformNestedCo import com.futo.platformplayer.api.media.models.video.SerializedPlatformPost import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo import kotlinx.serialization.DeserializationStrategy -import kotlinx.serialization.json.* +import kotlinx.serialization.json.JsonContentPolymorphicSerializer +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.booleanOrNull +import kotlinx.serialization.json.contentOrNull +import kotlinx.serialization.json.int +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive -class PlatformContentSerializer() : JsonContentPolymorphicSerializer(SerializedPlatformContent::class) { +class PlatformContentSerializer : JsonContentPolymorphicSerializer(SerializedPlatformContent::class) { - override fun selectDeserializer(element: JsonElement): DeserializationStrategy { + override fun selectDeserializer(element: JsonElement): DeserializationStrategy { val obj = element.jsonObject["contentType"]; //TODO: Remove this temporary fallback..at some point if(obj == null && element.jsonObject["isLive"]?.jsonPrimitive?.booleanOrNull != null) return SerializedPlatformVideo.serializer(); - if(obj?.jsonPrimitive?.isString ?: true) - return when(obj?.jsonPrimitive?.contentOrNull) { + if(obj?.jsonPrimitive?.isString != false) { + return when (obj?.jsonPrimitive?.contentOrNull) { "MEDIA" -> SerializedPlatformVideo.serializer(); "NESTED_VIDEO" -> SerializedPlatformNestedContent.serializer(); "ARTICLE" -> throw NotImplementedError("Articles not yet implemented"); "POST" -> SerializedPlatformPost.serializer(); else -> throw NotImplementedError("Unknown Content Type Value: ${obj?.jsonPrimitive?.contentOrNull}") }; - else - return when(obj?.jsonPrimitive?.int) { + } else { + return when (obj.jsonPrimitive.int) { ContentType.MEDIA.value -> SerializedPlatformVideo.serializer(); ContentType.NESTED_VIDEO.value -> SerializedPlatformNestedContent.serializer(); ContentType.ARTICLE.value -> throw NotImplementedError("Articles not yet implemented"); ContentType.POST.value -> SerializedPlatformPost.serializer(); - else -> throw NotImplementedError("Unknown Content Type Value: ${obj?.jsonPrimitive?.int}") + else -> throw NotImplementedError("Unknown Content Type Value: ${obj.jsonPrimitive.int}") }; + } } } \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/serializers/VideoDescriptorSerializer.kt b/app/src/main/java/com/futo/platformplayer/serializers/VideoDescriptorSerializer.kt index b9ffee13..5f960c1c 100644 --- a/app/src/main/java/com/futo/platformplayer/serializers/VideoDescriptorSerializer.kt +++ b/app/src/main/java/com/futo/platformplayer/serializers/VideoDescriptorSerializer.kt @@ -4,12 +4,16 @@ import com.futo.platformplayer.api.media.models.video.ISerializedVideoSourceDesc import com.futo.platformplayer.api.media.models.video.SerializedVideoMuxedSourceDescriptor import com.futo.platformplayer.api.media.models.video.SerializedVideoNonMuxedSourceDescriptor import kotlinx.serialization.DeserializationStrategy -import kotlinx.serialization.json.* +import kotlinx.serialization.json.JsonContentPolymorphicSerializer +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.boolean +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive class VideoDescriptorSerializer() : JsonContentPolymorphicSerializer(ISerializedVideoSourceDescriptor::class) { - override fun selectDeserializer(element: JsonElement): DeserializationStrategy { + override fun selectDeserializer(element: JsonElement): DeserializationStrategy { return when(element.jsonObject["isUnMuxed"]?.jsonPrimitive?.boolean) { false -> SerializedVideoMuxedSourceDescriptor.serializer(); true -> SerializedVideoNonMuxedSourceDescriptor.serializer(); diff --git a/app/src/main/java/com/futo/platformplayer/services/DownloadService.kt b/app/src/main/java/com/futo/platformplayer/services/DownloadService.kt index f58352ee..284328e2 100644 --- a/app/src/main/java/com/futo/platformplayer/services/DownloadService.kt +++ b/app/src/main/java/com/futo/platformplayer/services/DownloadService.kt @@ -7,15 +7,16 @@ import android.app.Service import android.content.Context import android.content.Intent import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC -import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK import android.os.Build import android.os.IBinder import androidx.core.app.NotificationCompat -import com.futo.platformplayer.* +import com.futo.platformplayer.R +import com.futo.platformplayer.Settings import com.futo.platformplayer.activities.MainActivity import com.futo.platformplayer.api.http.ManagedHttpClient import com.futo.platformplayer.downloads.VideoDownload import com.futo.platformplayer.exceptions.DownloadException +import com.futo.platformplayer.getNowDiffMinutes import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.states.Announcement import com.futo.platformplayer.states.AnnouncementType @@ -150,10 +151,16 @@ class DownloadService : Service() { currentVideo.changeState(VideoDownload.State.ERROR); ignore.add(currentVideo); - if(ex !is CancellationException) - StateAnnouncement.instance.registerAnnouncement(currentVideo?.id?.value?:"" + currentVideo?.id?.pluginId?:"" + "_FailDownload", + if(ex !is CancellationException) { + StateAnnouncement.instance.registerAnnouncement( + currentVideo.id.value ?: ("" + currentVideo.id.pluginId), "Download failed", - "Download for [${currentVideo.name}] failed.\nDownloads are automatically retried.\nReason: ${ex.message}", AnnouncementType.SESSION, null, "download"); + "Download for [${currentVideo.name}] failed.\nDownloads are automatically retried.\nReason: ${ex.message}", + AnnouncementType.SESSION, + null, + "download" + ); + } //Give it a sec Thread.sleep(500); @@ -262,7 +269,7 @@ class DownloadService : Service() { fun closeDownloadSession() { Logger.i(TAG, "closeDownloadSession"); - stopForeground(true); + stopForeground(STOP_FOREGROUND_DETACH); _notificationManager?.cancel(DOWNLOAD_NOTIF_ID); stopService(); _started = false; diff --git a/app/src/main/java/com/futo/platformplayer/services/ExportingService.kt b/app/src/main/java/com/futo/platformplayer/services/ExportingService.kt index 041a9552..fc5c24a3 100644 --- a/app/src/main/java/com/futo/platformplayer/services/ExportingService.kt +++ b/app/src/main/java/com/futo/platformplayer/services/ExportingService.kt @@ -6,23 +6,26 @@ import android.app.PendingIntent import android.app.Service import android.content.Context import android.content.Intent -import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import android.content.pm.ServiceInfo import android.os.Build import android.os.IBinder import androidx.core.app.NotificationCompat -import androidx.core.content.FileProvider -import com.futo.platformplayer.* +import com.futo.platformplayer.R import com.futo.platformplayer.activities.MainActivity import com.futo.platformplayer.api.http.ManagedHttpClient import com.futo.platformplayer.downloads.VideoExport import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.share import com.futo.platformplayer.states.Announcement import com.futo.platformplayer.states.AnnouncementType import com.futo.platformplayer.states.StateAnnouncement import com.futo.platformplayer.states.StateDownloads import com.futo.platformplayer.stores.FragmentedStorage -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import java.time.OffsetDateTime import java.util.UUID @@ -184,7 +187,7 @@ class ExportingService : Service() { fun closeExportSession() { Logger.i(TAG, "closeExportSession"); - stopForeground(true); + stopForeground(STOP_FOREGROUND_DETACH); _notificationManager?.cancel(EXPORT_NOTIF_ID); stopService(); _started = false; diff --git a/app/src/main/java/com/futo/platformplayer/services/MediaPlaybackService.kt b/app/src/main/java/com/futo/platformplayer/services/MediaPlaybackService.kt index 85171d2b..ba73594e 100644 --- a/app/src/main/java/com/futo/platformplayer/services/MediaPlaybackService.kt +++ b/app/src/main/java/com/futo/platformplayer/services/MediaPlaybackService.kt @@ -1,6 +1,10 @@ package com.futo.platformplayer.services -import android.app.* +import android.app.ActivityManager +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.app.Service import android.content.Context import android.content.Intent import android.content.pm.ServiceInfo @@ -21,14 +25,14 @@ import androidx.core.app.NotificationCompat import com.bumptech.glide.Glide import com.bumptech.glide.request.target.CustomTarget import com.bumptech.glide.request.transition.Transition -import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.R import com.futo.platformplayer.Settings -import com.futo.platformplayer.states.StatePlatform -import com.futo.platformplayer.states.StatePlayer import com.futo.platformplayer.activities.MainActivity import com.futo.platformplayer.api.media.models.video.IPlatformVideo +import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.receivers.MediaControlReceiver +import com.futo.platformplayer.states.StatePlatform +import com.futo.platformplayer.states.StatePlayer import com.futo.platformplayer.stores.FragmentedStorage class MediaPlaybackService : Service() { @@ -148,7 +152,7 @@ class MediaPlaybackService : Service() { fun closeMediaSession() { Logger.v(TAG, "closeMediaSession"); - stopForeground(true); + stopForeground(STOP_FOREGROUND_DETACH); val focusRequest = _focusRequest; if (focusRequest != null) { @@ -214,7 +218,7 @@ class MediaPlaybackService : Service() { else notifyMediaSession(video, null); } - private fun generateMediaAction(context: Context, icon: Int, title: String, intent: PendingIntent) : NotificationCompat.Action { + private fun generateMediaAction(icon: Int, title: String, intent: PendingIntent) : NotificationCompat.Action { return NotificationCompat.Action.Builder(icon, title, intent).build(); } private fun notifyMediaSession(video: IPlatformVideo?, desiredBitmap: Bitmap?) { @@ -259,17 +263,37 @@ class MediaPlaybackService : Service() { val playWhenReady = StatePlayer.instance.isPlaying; if(hasQueue) - builder = builder.addAction(generateMediaAction(this, R.drawable.ic_fast_rewind_notif, "Back", MediaControlReceiver.getPrevIntent(this, 3))) + builder = builder.addAction(generateMediaAction( + R.drawable.ic_fast_rewind_notif, + "Back", + MediaControlReceiver.getPrevIntent(this, 3) + )) if(playWhenReady) - builder = builder.addAction(generateMediaAction(this, R.drawable.ic_pause_notif, "Pause", MediaControlReceiver.getPauseIntent(this, 2))); + builder = builder.addAction(generateMediaAction( + R.drawable.ic_pause_notif, + "Pause", + MediaControlReceiver.getPauseIntent(this, 2) + )); else - builder = builder.addAction(generateMediaAction(this, R.drawable.ic_play_notif, "Play", MediaControlReceiver.getPlayIntent(this, 1))); + builder = builder.addAction(generateMediaAction( + R.drawable.ic_play_notif, + "Play", + MediaControlReceiver.getPlayIntent(this, 1) + )); if(hasQueue) - builder = builder.addAction(generateMediaAction(this, R.drawable.ic_fast_forward_notif, "Forward", MediaControlReceiver.getNextIntent(this, 4))); + builder = builder.addAction(generateMediaAction( + R.drawable.ic_fast_forward_notif, + "Forward", + MediaControlReceiver.getNextIntent(this, 4) + )); - builder = builder.addAction(generateMediaAction(this, R.drawable.ic_stop_notif, "Stop", MediaControlReceiver.getCloseIntent(this, 5))); + builder = builder.addAction(generateMediaAction( + R.drawable.ic_stop_notif, + "Stop", + MediaControlReceiver.getCloseIntent(this, 5) + )); if(bitmap?.isRecycled ?: false) bitmap = null; diff --git a/app/src/main/java/com/futo/platformplayer/states/StateAnnouncement.kt b/app/src/main/java/com/futo/platformplayer/states/StateAnnouncement.kt index 9bced80b..a2304d72 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateAnnouncement.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateAnnouncement.kt @@ -1,19 +1,15 @@ package com.futo.platformplayer.states -import android.content.Context import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.api.http.ManagedHttpClient import com.futo.platformplayer.constructs.Event0 -import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.serializers.OffsetDateTimeNullableSerializer import com.futo.platformplayer.stores.FragmentedStorage import com.futo.platformplayer.stores.StringHashSetStorage -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlinx.serialization.Serializable -import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import java.time.OffsetDateTime import java.util.Random @@ -70,9 +66,7 @@ class StateAnnouncement { synchronized(_lock) { val idActual = id ?: UUID.randomUUID().toString(); val announcement = SessionAnnouncement(idActual, title, msg, announceType, time, category, actionButton, idActual); - - if(action != null) - _sessionActions.put(idActual, action); + _sessionActions[idActual] = action; registerAnnouncementSession(announcement); } } @@ -81,20 +75,21 @@ class StateAnnouncement { val idActual = id ?: UUID.randomUUID().toString(); val announcement = SessionAnnouncement(idActual, title, msg, announceType, time, category, actionButton, idActual, cancelButton, if(cancelAction != null) idActual + "_cancel" else null); - if(action != null) - _sessionActions.put(idActual, action); - if(cancelAction != null) + _sessionActions.put(idActual, action); + if(cancelAction != null) { _sessionActions.put(idActual + "_cancel", cancelAction); + } registerAnnouncementSession(announcement); } } fun registerAnnouncement(id: String?, title: String, msg: String, announceType: AnnouncementType = AnnouncementType.DELETABLE, time: OffsetDateTime? = null, category: String? = null, actionButton: String? = null, actionId: String? = null) { - val newAnnouncement = Announcement(if(id == null) UUID.randomUUID().toString() else id, title, msg, announceType, time, category, actionButton, actionId); + val newAnnouncement = Announcement(id ?: UUID.randomUUID().toString(), title, msg, announceType, time, category, actionButton, actionId); - if(announceType == AnnouncementType.SESSION || announceType == AnnouncementType.SESSION_RECURRING) + if(announceType == AnnouncementType.SESSION || announceType == AnnouncementType.SESSION_RECURRING) { registerAnnouncementSession(newAnnouncement); - else + } else { registerAnnouncement(newAnnouncement); + } } fun registerAnnouncementSession(announcement: Announcement) { synchronized(_lock) { diff --git a/app/src/main/java/com/futo/platformplayer/states/StateApp.kt b/app/src/main/java/com/futo/platformplayer/states/StateApp.kt index 3129637c..771c98b0 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateApp.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateApp.kt @@ -13,7 +13,6 @@ import android.net.NetworkRequest import android.net.Uri import android.provider.DocumentsContract import android.util.DisplayMetrics -import android.util.Xml import androidx.documentfile.provider.DocumentFile import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope @@ -23,33 +22,23 @@ import com.futo.platformplayer.R import com.futo.platformplayer.activities.CaptchaActivity import com.futo.platformplayer.activities.IWithResultLauncher import com.futo.platformplayer.activities.MainActivity -import com.futo.platformplayer.api.media.models.video.SerializedPlatformContent -import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo import com.futo.platformplayer.api.media.platforms.js.DevJSClient import com.futo.platformplayer.api.media.platforms.js.JSClient import com.futo.platformplayer.background.BackgroundWorker import com.futo.platformplayer.casting.StateCasting import com.futo.platformplayer.constructs.Event0 import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException -import com.futo.platformplayer.engine.exceptions.ScriptLoginRequiredException import com.futo.platformplayer.fragment.mainactivity.main.HomeFragment import com.futo.platformplayer.fragment.mainactivity.main.SourceDetailFragment import com.futo.platformplayer.logging.AndroidLogConsumer import com.futo.platformplayer.logging.FileLogConsumer import com.futo.platformplayer.logging.LogLevel import com.futo.platformplayer.logging.Logger -import com.futo.platformplayer.models.HistoryVideo import com.futo.platformplayer.receivers.AudioNoisyReceiver -import com.futo.platformplayer.serializers.PlatformContentSerializer import com.futo.platformplayer.services.DownloadService import com.futo.platformplayer.stores.FragmentedStorage -import com.futo.platformplayer.stores.db.ManagedDBStore -import com.futo.platformplayer.stores.db.types.DBHistory -import com.futo.platformplayer.stores.v2.JsonStoreSerializer import com.futo.platformplayer.stores.v2.ManagedStore import kotlinx.coroutines.* -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.json.Json import java.io.File import java.time.OffsetDateTime import java.util.* @@ -393,7 +382,7 @@ class StateApp { scopeOrNull?.launch(Dispatchers.Main) { try { if (it != null) { - UIDialogs.toast("Uploaded " + (it ?: "null"), true); + UIDialogs.toast("Uploaded $it", true); } else { UIDialogs.toast("Failed to upload"); } @@ -752,9 +741,6 @@ class StateApp { }) } } - fun handleLoginException(client: JSClient, exception: ScriptLoginRequiredException, onSuccess: (client: JSClient)->Unit) { - - } fun getLocaleContext(baseContext: Context?): Context? { val locale = getLocaleSetting(baseContext); diff --git a/app/src/main/java/com/futo/platformplayer/states/StateAssets.kt b/app/src/main/java/com/futo/platformplayer/states/StateAssets.kt index 8fb08b77..605ea928 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateAssets.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateAssets.kt @@ -2,7 +2,6 @@ package com.futo.platformplayer.states import android.content.Context import kotlin.streams.asSequence -import kotlin.streams.toList /*** * Used to read assets @@ -33,28 +32,28 @@ class StateAssets { /** * Does basic asset resolving under certain conditions */ - fun readAssetRelative(context: Context, base: String, path: String, cache: Boolean = false) : String? { + fun readAssetRelative(context: Context, base: String, path: String) : String? { val finalPath = resolvePath(base, path); - return readAsset(context, finalPath, cache); + return readAsset(context, finalPath); } fun readAssetBinRelative(context: Context, base: String, path: String) : ByteArray? { val finalPath = resolvePath(base, path); return readAssetBin(context, finalPath); } - fun readAsset(context: Context, path: String, cache: Boolean = false) : String? { - var text: String? = null; + fun readAsset(context: Context, path: String) : String? { + var text: String?; synchronized(_cache) { if (!_cache.containsKey(path)) { - text = context - ?.assets + text = context.assets ?.open(path) ?.bufferedReader() ?.use { it.readText(); }; + _cache.put(path, text); + } else { + text = _cache[path]; } - else - text = _cache.get(path); } return text; } diff --git a/app/src/main/java/com/futo/platformplayer/states/StateBackup.kt b/app/src/main/java/com/futo/platformplayer/states/StateBackup.kt index b3a7ea3e..35bb14ce 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateBackup.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateBackup.kt @@ -27,19 +27,16 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.io.File import java.io.FileNotFoundException -import java.lang.Exception import java.time.OffsetDateTime import java.util.zip.ZipEntry import java.util.zip.ZipInputStream import java.util.zip.ZipOutputStream -import kotlin.IllegalStateException class StateBackup { companion object { @@ -102,7 +99,7 @@ class StateBackup { val backupFiles = getAutomaticBackupDocumentFiles(context, true); val exportFile = backupFiles.first; if (exportFile?.exists() == true && backupFiles.second != null) - exportFile!!.copyTo(context, backupFiles.second!!); + exportFile.copyTo(context, backupFiles.second!!); exportFile!!.writeBytes(context, encryptedZip); Settings.instance.backup.lastAutoBackupTime = OffsetDateTime.now(); //OffsetDateTime.now(); @@ -488,11 +485,11 @@ class StateBackup { companion object { fun fromZip(zipStream: ZipInputStream): ExportStructure { - var entry: ZipEntry? = null + var entry: ZipEntry? var exportInfo: Map = mapOf(); var settings: String? = null; - var stores: MutableMap> = mutableMapOf(); + val stores: MutableMap> = mutableMapOf(); var plugins: Map = mapOf(); var pluginSettings: Map> = mapOf(); diff --git a/app/src/main/java/com/futo/platformplayer/states/StateCache.kt b/app/src/main/java/com/futo/platformplayer/states/StateCache.kt index da9f96ea..409a503c 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateCache.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateCache.kt @@ -1,6 +1,5 @@ package com.futo.platformplayer.states -import com.futo.platformplayer.api.media.models.contents.ContentType import com.futo.platformplayer.api.media.models.contents.IPlatformContent import com.futo.platformplayer.api.media.models.video.SerializedPlatformContent import com.futo.platformplayer.api.media.structures.DedupContentPager @@ -12,15 +11,10 @@ import com.futo.platformplayer.resolveChannelUrl import com.futo.platformplayer.serializers.PlatformContentSerializer import com.futo.platformplayer.stores.db.ManagedDBStore import com.futo.platformplayer.stores.db.types.DBSubscriptionCache -import com.futo.platformplayer.stores.v2.JsonStoreSerializer import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json import java.time.OffsetDateTime -import kotlin.streams.asSequence -import kotlin.streams.toList import kotlin.system.measureTimeMillis class StateCache { @@ -140,9 +134,11 @@ class StateCache { Logger.i(TAG, "Caching ${results.size} subscription initial results [${pager.hashCode()}]"); scope.launch(Dispatchers.IO) { try { - val newCacheItems = StateCache.instance.cacheContents(results, true); - if(onNewCacheItem != null) - newCacheItems.forEach { onNewCacheItem!!(it) } + val newCacheItems = instance.cacheContents(results, true); + + onNewCacheItem?.let { f -> + newCacheItems.forEach { f(it) } + } } catch (e: Throwable) { Logger.e(TAG, "Failed to cache videos.", e); } @@ -163,10 +159,11 @@ class StateCache { val ms = measureTimeMillis { val newCacheItems = instance.cacheContents(results, true); newCacheItemsCount = newCacheItems.size; - if(onNewCacheItem != null) - newCacheItems.forEach { onNewCacheItem!!(it) } + onNewCacheItem?.let { f -> + newCacheItems.forEach { f(it) } + } } - Logger.i(TAG, "Caching ${results.size} subscription results, updated ${newCacheItemsCount} (${ms}ms)"); + Logger.i(TAG, "Caching ${results.size} subscription results, updated $newCacheItemsCount (${ms}ms)"); } catch (e: Throwable) { Logger.e(TAG, "Failed to cache ${results.size} videos.", e); } diff --git a/app/src/main/java/com/futo/platformplayer/states/StateDownloads.kt b/app/src/main/java/com/futo/platformplayer/states/StateDownloads.kt index f6cbd9b7..543f62c8 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateDownloads.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateDownloads.kt @@ -1,7 +1,6 @@ package com.futo.platformplayer.states import android.content.ContentResolver -import android.net.Uri import android.os.StatFs import com.futo.platformplayer.R import com.futo.platformplayer.Settings @@ -9,24 +8,28 @@ import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.api.http.ManagedHttpClient import com.futo.platformplayer.api.media.PlatformID import com.futo.platformplayer.api.media.exceptions.AlreadyQueuedException -import com.futo.platformplayer.api.media.models.streams.sources.* +import com.futo.platformplayer.api.media.models.streams.sources.IAudioUrlSource +import com.futo.platformplayer.api.media.models.streams.sources.IVideoUrlSource +import com.futo.platformplayer.api.media.models.streams.sources.LocalAudioSource +import com.futo.platformplayer.api.media.models.streams.sources.LocalSubtitleSource +import com.futo.platformplayer.api.media.models.streams.sources.LocalVideoSource +import com.futo.platformplayer.api.media.models.streams.sources.SubtitleRawSource import com.futo.platformplayer.api.media.models.subtitles.ISubtitleSource import com.futo.platformplayer.api.media.models.video.IPlatformVideo import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails import com.futo.platformplayer.constructs.Event0 import com.futo.platformplayer.downloads.PlaylistDownloadDescriptor -import com.futo.platformplayer.downloads.VideoLocal import com.futo.platformplayer.downloads.VideoDownload import com.futo.platformplayer.downloads.VideoExport +import com.futo.platformplayer.downloads.VideoLocal import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.models.DiskUsage import com.futo.platformplayer.models.Playlist import com.futo.platformplayer.models.PlaylistDownloaded import com.futo.platformplayer.services.DownloadService import com.futo.platformplayer.services.ExportingService -import com.futo.platformplayer.stores.* +import com.futo.platformplayer.stores.FragmentedStorage import com.futo.platformplayer.stores.v2.ManagedStore -import okhttp3.internal.platform.Platform import java.io.File /*** @@ -349,17 +352,19 @@ class StateDownloads { fun cleanupDownloads(): Pair { val expected = getDownloadedVideos(); - val validFiles = HashSet(expected.flatMap { it.videoSource.map { it.filePath } + it.audioSource.map { it.filePath } }); + val validFiles = HashSet(expected.flatMap { e -> e.videoSource.map { it.filePath } + e.audioSource.map { it.filePath } }); var totalDeleted: Long = 0; var totalDeletedCount = 0; - for(file in _downloadsDirectory.listFiles()) { - val absUrl = file.absolutePath; - if(!validFiles.contains(absUrl)) { - Logger.i("StateDownloads", "Deleting unresolved ${file.name}"); - totalDeletedCount++; - totalDeleted += file.length(); - file.delete(); + _downloadsDirectory.listFiles()?.let { + for(file in it) { + val absUrl = file.absolutePath; + if(!validFiles.contains(absUrl)) { + Logger.i("StateDownloads", "Deleting unresolved ${file.name}"); + totalDeletedCount++; + totalDeleted += file.length(); + file.delete(); + } } } return Pair(totalDeletedCount, totalDeleted); diff --git a/app/src/main/java/com/futo/platformplayer/states/StateHistory.kt b/app/src/main/java/com/futo/platformplayer/states/StateHistory.kt index b1151d0c..c6d2f4bf 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateHistory.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateHistory.kt @@ -10,13 +10,9 @@ import com.futo.platformplayer.stores.FragmentedStorage import com.futo.platformplayer.stores.db.ManagedDBStore import com.futo.platformplayer.stores.db.types.DBHistory import com.futo.platformplayer.stores.v2.ReconstructStore -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.json.Json import java.time.OffsetDateTime -import java.util.UUID import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentMap -import kotlin.system.measureTimeMillis class StateHistory { //Legacy @@ -57,8 +53,7 @@ class StateHistory { fun updateHistoryPosition(liveObj: IPlatformVideo, index: DBHistory.Index, updateExisting: Boolean, position: Long = -1L): Long { val pos = if(position < 0) 0 else position; - if(index.obj == null) throw IllegalStateException("Can only update history with a deserialized db item"); - val historyVideo = index.obj!!; + val historyVideo = index.obj; val positionBefore = historyVideo.position; if (updateExisting) { @@ -72,8 +67,6 @@ class StateHistory { } if (shouldUpdate) { - - //A unrecovered item if(historyVideo.video.author.id.value == null && historyVideo.video.duration == 0L) historyVideo.video = SerializedPlatformVideo.fromVideo(liveObj); @@ -88,14 +81,6 @@ class StateHistory { return positionBefore; } - - fun getHistoryLegacy(): List { - return _historyStore.getItems(); - } - fun getHistory() : List { - return _historyDBStore.getAllObjects(); - //return _historyStore.getItems().sortedByDescending { it.date }; - } fun getHistoryPager(): IPager { return _historyDBStore.getObjectPager(); } @@ -121,10 +106,6 @@ class StateHistory { val hist = getHistoryIndexByUrl(url); if(hist != null) _historyDBStore.delete(hist.id!!); - /* - val hist = _historyStore.findItem { it.video.url == url }; - if(hist != null) - _historyStore.delete(hist);*/ } fun removeHistoryRange(minutesToDelete: Long) { @@ -132,12 +113,6 @@ class StateHistory { val toDelete = _historyDBStore.getAllIndexes().filter { minutesToDelete == -1L || (now - it.datetime) < minutesToDelete * 60 }; for(item in toDelete) _historyDBStore.delete(item); - /* - val now = OffsetDateTime.now(); - val toDelete = _historyStore.findItems { minutesToDelete == -1L || ChronoUnit.MINUTES.between(it.date, now) < minutesToDelete }; - - for(item in toDelete) - _historyStore.delete(item);*/ } @@ -157,59 +132,4 @@ class StateHistory { } } } - - - fun testHistoryDB(count: Int) { - Logger.i(TAG, "TEST: Starting tests"); - _historyDBStore.deleteAll(); - - val testHistoryItem = getHistoryLegacy().first(); - val testItemJson = testHistoryItem.video.toJson(); - val now = OffsetDateTime.now(); - - val testSet = (0..count).map { HistoryVideo(Json.decodeFromString(testItemJson.replace(testHistoryItem.video.url, UUID.randomUUID().toString())), it.toLong(), now.minusHours(it.toLong())) } - - - Logger.i(TAG, "TEST: Inserting (${testSet.size})"); - val insertMS = measureTimeMillis { - for(item in testSet) - _historyDBStore.insert(item); - }; - Logger.i(TAG, "TEST: Inserting in ${insertMS}ms"); - - var fetched: List? = null; - val fetchMS = measureTimeMillis { - fetched = _historyDBStore.getAll(); - Logger.i(TAG, "TEST: Fetched: ${fetched?.size}"); - }; - Logger.i(TAG, "TEST: Fetch speed ${fetchMS}MS"); - val deserializeMS = measureTimeMillis { - val deserialized = _historyDBStore.convertObjects(fetched!!); - Logger.i(TAG, "TEST: Deserialized: ${deserialized.size}"); - }; - Logger.i(TAG, "TEST: Deserialize speed ${deserializeMS}MS"); - - var fetchedIndex: List? = null; - val fetchIndexMS = measureTimeMillis { - fetchedIndex = _historyDBStore.getAllIndexes(); - Logger.i(TAG, "TEST: Fetched Index: ${fetchedIndex!!.size}"); - }; - Logger.i(TAG, "TEST: Fetched Index speed ${fetchIndexMS}ms"); - val fetchFromIndex = measureTimeMillis { - for(preItem in testSet) { - val item = historyIndex[preItem.video.url]; - if(item == null) - throw IllegalStateException("Missing item [${preItem.video.url}]"); - if(item.url != preItem.video.url) - throw IllegalStateException("Mismatch item [${preItem.video.url}]"); - } - }; - Logger.i(TAG, "TEST: Index Lookup speed ${fetchFromIndex}ms"); - - val page1 = _historyDBStore.getPage(0, 20); - val page2 = _historyDBStore.getPage(1, 20); - val page3 = _historyDBStore.getPage(2, 20); - } - - } \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/states/StateNotifications.kt b/app/src/main/java/com/futo/platformplayer/states/StateNotifications.kt index b1fc2428..e5f01a84 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateNotifications.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateNotifications.kt @@ -16,7 +16,6 @@ import com.futo.platformplayer.activities.MainActivity import com.futo.platformplayer.api.media.models.contents.IPlatformContent import com.futo.platformplayer.api.media.models.video.IPlatformVideo import com.futo.platformplayer.api.media.models.video.SerializedPlatformContent -import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.receivers.PlannedNotificationReceiver import com.futo.platformplayer.serializers.PlatformContentSerializer @@ -54,7 +53,7 @@ class StateNotifications { _plannedContent.delete(existing); existing = null; } - if(existing == null && content.datetime != null) { + if(content.datetime != null) { val item = SerializedPlatformContent.fromContent(content); _plannedContent.saveAsync(item); @@ -92,7 +91,7 @@ class StateNotifications { } fun notifyNewContentWithThumbnail(context: Context, manager: NotificationManager, notificationChannel: NotificationChannel, id: Int, content: IPlatformContent) { - val thumbnail = if(content is IPlatformVideo) (content as IPlatformVideo).thumbnails.getHQThumbnail() + val thumbnail = if(content is IPlatformVideo) content.thumbnails.getHQThumbnail() else null; if(thumbnail != null) Glide.with(context).asBitmap() diff --git a/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt b/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt index a5c6d311..d82d6eff 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt @@ -7,7 +7,6 @@ import com.futo.platformplayer.Settings import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.api.media.IPlatformClient import com.futo.platformplayer.api.media.IPluginSourced -import com.futo.platformplayer.api.media.PlatformClientPool import com.futo.platformplayer.api.media.PlatformMultiClientPool import com.futo.platformplayer.api.media.exceptions.NoPlatformClientException import com.futo.platformplayer.api.media.models.FilterGroup @@ -27,7 +26,13 @@ import com.futo.platformplayer.api.media.models.video.IPlatformVideo import com.futo.platformplayer.api.media.platforms.js.DevJSClient import com.futo.platformplayer.api.media.platforms.js.JSClient import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig -import com.futo.platformplayer.api.media.structures.* +import com.futo.platformplayer.api.media.structures.EmptyPager +import com.futo.platformplayer.api.media.structures.IPager +import com.futo.platformplayer.api.media.structures.MultiChronoContentPager +import com.futo.platformplayer.api.media.structures.MultiDistributionChannelPager +import com.futo.platformplayer.api.media.structures.MultiDistributionContentPager +import com.futo.platformplayer.api.media.structures.PlaceholderPager +import com.futo.platformplayer.api.media.structures.RefreshDistributionContentPager import com.futo.platformplayer.awaitFirstNotNullDeferred import com.futo.platformplayer.constructs.BatchedTaskHandler import com.futo.platformplayer.constructs.Event0 @@ -37,14 +42,21 @@ import com.futo.platformplayer.getNowDiffDays import com.futo.platformplayer.getNowDiffSeconds import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.models.ImageVariable -import com.futo.platformplayer.stores.* -import kotlinx.coroutines.* +import com.futo.platformplayer.stores.FragmentedStorage +import com.futo.platformplayer.stores.StringArrayStorage +import com.futo.platformplayer.stores.StringStorage +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.cancel +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import okhttp3.internal.concat import java.lang.Thread.sleep import java.time.OffsetDateTime -import kotlin.reflect.jvm.internal.impl.builtins.jvm.JavaToKotlinClassMap.PlatformMutabilityMapping import kotlin.streams.asSequence -import kotlin.streams.toList /*** * Used to interact with sources/clients @@ -149,20 +161,20 @@ class StatePlatform { suspend fun updateAvailableClients(context: Context, reloadPlugins: Boolean = false) { - if(reloadPlugins) + if(reloadPlugins) { StatePlugins.instance.reloadPluginFile(); + } + withContext(Dispatchers.IO) { var enabled: Array; synchronized(_clientsLock) { - for(enabled in _enabledClients) { - enabled.disable(); - onSourceDisabled.emit(enabled); + for(e in _enabledClients) { + e.disable(); + onSourceDisabled.emit(e); } _enabledClients.clear(); _availableClients.clear(); - //_availableClients.add(YoutubeClient()); - //_availableClients.add(OdyseeClient()); _icons.clear(); _icons[StateDeveloper.DEV_ID] = ImageVariable(null, R.drawable.ic_security_red); @@ -170,40 +182,43 @@ class StatePlatform { StatePlugins.instance.updateEmbeddedPlugins(context); StatePlugins.instance.installMissingEmbeddedPlugins(context); - for(plugin in StatePlugins.instance.getPlugins()) { - + for (plugin in StatePlugins.instance.getPlugins()) { _icons[plugin.config.id] = StatePlugins.instance.getPluginIconOrNull(plugin.config.id) ?: ImageVariable(plugin.config.absoluteIconUrl, null); val client = JSClient(context, plugin); - client.onCaptchaException.subscribe { client, ex -> - StateApp.instance.handleCaptchaException(client, ex); + client.onCaptchaException.subscribe { c, ex -> + StateApp.instance.handleCaptchaException(c, ex); } _availableClients.add(client); } - if(_availableClients.distinctBy { it.id }.count() < _availableClients.size) + if(_availableClients.distinctBy { it.id }.count() < _availableClients.size) { throw IllegalStateException("Attempted to add 2 clients with the same ID"); + } enabled = _enabledClientsPersistent.getAllValues() .filter { _availableClients.any { ac -> ac.id == it } } .toTypedArray(); - if(enabled.isEmpty()) + if(enabled.isEmpty()) { enabled = StatePlugins.instance.getEmbeddedSourcesDefault(context) .filter { id -> _availableClients.any { it.id == id } } .toTypedArray(); + } val primary = _primaryClientPersistent.value; - if(primary.isNullOrEmpty() || primary == StateDeveloper.DEV_ID) + if(primary.isEmpty() || primary == StateDeveloper.DEV_ID) { selectPrimaryClient(enabled.firstOrNull() ?: _availableClients.first().id); - else if(!_availableClients.any { it.id == primary }) + } else if(!_availableClients.any { it.id == primary }) { selectPrimaryClient(_availableClients.firstOrNull()?.id!!); - else + } else { selectPrimaryClient(primary); + } - if(!enabled.any { it == primaryClient.id }) + if(!enabled.any { it == primaryClient.id }) { enabled = enabled.concat(primaryClient.id); + } } selectClients(*enabled); }; @@ -294,8 +309,8 @@ class StatePlatform { StatePlugins.instance.getPlugin(id) ?: throw IllegalStateException("Client existed, but plugin config didn't") ); - newClient.onCaptchaException.subscribe { client, ex -> - StateApp.instance.handleCaptchaException(client, ex); + newClient.onCaptchaException.subscribe { c, ex -> + StateApp.instance.handleCaptchaException(c, ex); } synchronized(_clientsLock) { @@ -654,10 +669,11 @@ class StatePlatform { fun getChannel(url: String, updateSubscriptions: Boolean = true): Deferred { Logger.i(TAG, "Platform - getChannel"); val channel = StateSubscriptions.instance.getSubscription(url); - if(channel != null) - return _scope.async { getChannelLive(url, updateSubscriptions) }; //_batchTaskGetChannel.execute(channel); - else - return _scope.async { getChannelLive(url, updateSubscriptions) }; + return if(channel != null) { + _scope.async { getChannelLive(url, updateSubscriptions) }; //_batchTaskGetChannel.execute(channel); + } else { + _scope.async { getChannelLive(url, updateSubscriptions) }; + } } fun getChannelContent(baseClient: IPlatformClient, channelUrl: String, isSubscriptionOptimized: Boolean = false, usePooledClients: Int = 0, ignorePlugins: List? = null): IPager { diff --git a/app/src/main/java/com/futo/platformplayer/states/StatePlayer.kt b/app/src/main/java/com/futo/platformplayer/states/StatePlayer.kt index 006bbb9a..e1243a8d 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StatePlayer.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StatePlayer.kt @@ -1,16 +1,15 @@ +@file:Suppress("DEPRECATION") + package com.futo.platformplayer.states import android.content.Context -import android.util.Log import com.futo.platformplayer.R import com.futo.platformplayer.UIDialogs -import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylist import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylistDetails import com.futo.platformplayer.api.media.models.video.IPlatformVideo import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails import com.futo.platformplayer.constructs.Event0 import com.futo.platformplayer.constructs.Event1 -import com.futo.platformplayer.constructs.Event2 import com.futo.platformplayer.models.Playlist import com.futo.platformplayer.services.MediaPlaybackService import com.futo.platformplayer.video.PlayerManager @@ -166,7 +165,7 @@ class StatePlayer { queueRepeat = enabled; } } - fun setQueueShuffle(shuffle: Boolean, excludeCurrent: Boolean = true) { + fun setQueueShuffle(shuffle: Boolean) { synchronized(_queue) { queueShuffle = shuffle; if (shuffle) { @@ -317,10 +316,11 @@ class StatePlayer { _queue.add(it); } } - if(_queue.isEmpty()) + if(_queue.isEmpty()) { _queue.add(video); - else + } else { _queue.add(_queuePosition.coerceAtLeast(0).coerceAtMost(_queue.size - 1), video); + } if (queueShuffle) { addToShuffledQueue(video); @@ -331,8 +331,9 @@ class StatePlayer { } } onQueueChanged.emit(true); - if(playNow) + if(playNow) { setQueuePosition(video); + } } fun setQueuePosition(video: IPlatformVideo) { synchronized(_queue) { @@ -347,10 +348,11 @@ class StatePlayer { fun getQueuePosition(video: IPlatformVideo): Int { synchronized(_queue) { val queueShuffled = _queueShuffled; - return if (queueRepeat && queueShuffled != null) + return if (queueRepeat && queueShuffled != null) { queueShuffled.indexOf(video); - else + } else { _queue.indexOf(video); + } } } fun removeFromQueue(video: IPlatformVideo, shouldSwapCurrentItem: Boolean = false) { @@ -384,10 +386,11 @@ class StatePlayer { } if(adjustIfNegative && queue.isNotEmpty()) { - if(_queuePosition == -1) + if(_queuePosition == -1) { return queue[0]; - else if(_queuePosition < queue.size) + } else if(_queuePosition < queue.size) { return queue[_queuePosition]; + } } else if(_queuePosition >= 0 && _queuePosition < queue.size) { return queue[_queuePosition]; } @@ -401,8 +404,9 @@ class StatePlayer { */ fun getPrevQueueItem(forceLoop: Boolean = false) : IPlatformVideo? { synchronized(_queue) { - if(_queue.size == 1) + if(_queue.size == 1) { return null; + } val shuffledQueue = _queueShuffled; val queue = if (queueShuffle && shuffledQueue != null) { @@ -412,14 +416,17 @@ class StatePlayer { } //Init Behavior - if(_queuePosition == -1 && queue.isNotEmpty()) + if(_queuePosition == -1 && queue.isNotEmpty()) { return queue[0]; + } //Standard Behavior - if(_queuePosition - 1 >= 0) + if(_queuePosition - 1 >= 0) { return queue[_queuePosition - 1]; + } //Repeat Behavior (End of queue) - if(_queuePosition - 1 < 0 && queue.isNotEmpty() && (forceLoop || queueRepeat)) + if(queue.isNotEmpty() && (forceLoop || queueRepeat)) { return queue[_queue.size - 1]; + } } return null; } @@ -429,8 +436,9 @@ class StatePlayer { */ fun getNextQueueItem(forceLoop: Boolean = false) : IPlatformVideo? { synchronized(_queue) { - if(_queue.size == 1) + if(_queue.size == 1) { return null; + } val shuffledQueue = _queueShuffled; val queue = if (queueShuffle && shuffledQueue != null) { @@ -440,14 +448,17 @@ class StatePlayer { } //Init Behavior - if(_queuePosition == -1 && queue.isNotEmpty()) + if(_queuePosition == -1 && queue.isNotEmpty()) { return queue[0]; + } //Standard Behavior - if(_queuePosition + 1 < queue.size) + if(_queuePosition + 1 < queue.size) { return queue[_queuePosition + 1]; + } //Repeat Behavior (End of queue) - if(_queuePosition + 1 == queue.size && queue.isNotEmpty() && (forceLoop || queueRepeat)) + if(_queuePosition + 1 == queue.size && queue.isNotEmpty() && (forceLoop || queueRepeat)) { return queue[0]; + } } return null; } @@ -464,11 +475,14 @@ class StatePlayer { * @param bypassVideoLoop Bypasses any single-video-looping behavior, should be true for manual user actions like next */ fun nextQueueItem(withoutRemoval: Boolean = false, bypassVideoLoop: Boolean = false) : IPlatformVideo? { - if(loopVideo && !bypassVideoLoop) + if(loopVideo && !bypassVideoLoop) { return currentVideo; + } + synchronized(_queue) { - if (_queue.isEmpty()) + if (_queue.isEmpty()) { return null; + } val nextPosition: Int; var isRepeat = false; @@ -510,22 +524,25 @@ class StatePlayer { } val currentPos = _queuePosition; - - if(_queueRemoveOnFinish && !withoutRemoval) { + _queuePosition = if(_queueRemoveOnFinish && !withoutRemoval) { _queue.removeAt(currentPos); - _queuePosition = (_queuePosition - 1); + (_queuePosition - 1); + } else { + (_queuePosition - 1); } - else - _queuePosition = (_queuePosition - 1); - if(_queuePosition < 0) + + if(_queuePosition < 0) { _queuePosition += _queue.size; - if(_queuePosition < _queue.size) + } + + if(_queuePosition < _queue.size) { return _queue[_queuePosition]; + } } return null; } - fun setQueueItem(video: IPlatformVideo) : IPlatformVideo? { + fun setQueueItem(video: IPlatformVideo) : IPlatformVideo { synchronized(_queue) { val index = _queue.indexOf(video); if(index >= 0) { diff --git a/app/src/main/java/com/futo/platformplayer/states/StatePlaylists.kt b/app/src/main/java/com/futo/platformplayer/states/StatePlaylists.kt index 5c959406..5aff46e4 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StatePlaylists.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StatePlaylists.kt @@ -3,35 +3,25 @@ package com.futo.platformplayer.states import android.content.Context import android.net.Uri import androidx.core.content.FileProvider -import androidx.sqlite.db.SimpleSQLiteQuery import com.futo.platformplayer.R -import com.futo.platformplayer.api.media.PlatformID import com.futo.platformplayer.api.media.exceptions.NoPlatformClientException import com.futo.platformplayer.api.media.models.channels.IPlatformChannel import com.futo.platformplayer.api.media.models.contents.IPlatformContent import com.futo.platformplayer.api.media.models.video.IPlatformVideo import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo -import com.futo.platformplayer.api.media.structures.IPager import com.futo.platformplayer.constructs.Event0 -import com.futo.platformplayer.constructs.Event2 import com.futo.platformplayer.engine.exceptions.ScriptUnavailableException import com.futo.platformplayer.exceptions.ReconstructionException import com.futo.platformplayer.logging.Logger -import com.futo.platformplayer.models.HistoryVideo import com.futo.platformplayer.models.Playlist import com.futo.platformplayer.stores.FragmentedStorage -import com.futo.platformplayer.stores.db.ManagedDBStore -import com.futo.platformplayer.stores.db.types.DBHistory import com.futo.platformplayer.stores.v2.ManagedStore import com.futo.platformplayer.stores.v2.ReconstructStore import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import java.io.File import java.time.OffsetDateTime -import java.time.temporal.ChronoUnit -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.ConcurrentMap /*** * Used to maintain playlists @@ -41,7 +31,7 @@ class StatePlaylists { .withUnique { it.url } .withRestore(object: ReconstructStore() { override fun toReconstruction(obj: SerializedPlatformVideo): String = obj.url; - override suspend fun toObject(id: String, backup: String, builder: Builder): SerializedPlatformVideo + override suspend fun toObject(id: String, backup: String, reconstructionBuilder: Builder): SerializedPlatformVideo = SerializedPlatformVideo.fromVideo(StatePlatform.instance.getContentDetails(backup).await() as IPlatformVideoDetails); }) .load(); @@ -186,22 +176,25 @@ class StatePlaylists { items.addAll(obj.videos.map { it.url }); return items.map { it.replace("\n","") }.joinToString("\n"); } - override suspend fun toObject(id: String, backup: String, builder: Builder): Playlist { + override suspend fun toObject(id: String, backup: String, reconstructionBuilder: Builder): Playlist { val items = backup.split("\n"); - if(items.size <= 0) + if(items.size <= 0) { throw IllegalStateException("Cannot reconstructor playlist ${id}"); + } val name = items[0]; val videos = items.drop(1).filter { it.isNotEmpty() }.map { try { val video = StatePlatform.instance.getContentDetails(it).await(); - if (video is IPlatformVideoDetails) + if (video is IPlatformVideoDetails) { return@map SerializedPlatformVideo.fromVideo(video); - else return@map null; + } else { + return@map null + } } catch(ex: ScriptUnavailableException) { Logger.w(TAG, "${name}:[${it}] is no longer available"); - builder.messages.add("${name}:[${it}] is no longer available"); + reconstructionBuilder.messages.add("${name}:[${it}] is no longer available"); return@map null; } catch(ex: NoPlatformClientException) { diff --git a/app/src/main/java/com/futo/platformplayer/states/StatePlugins.kt b/app/src/main/java/com/futo/platformplayer/states/StatePlugins.kt index 5c8a3dbd..85b16fd0 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StatePlugins.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StatePlugins.kt @@ -12,14 +12,15 @@ import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig import com.futo.platformplayer.api.media.platforms.js.SourcePluginDescriptor import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.models.ImageVariable -import com.futo.platformplayer.stores.* +import com.futo.platformplayer.stores.FragmentedStorage +import com.futo.platformplayer.stores.PluginIconStorage +import com.futo.platformplayer.stores.PluginScriptsDirectory import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlinx.serialization.Serializable import kotlinx.serialization.SerializationException -import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json /*** @@ -134,12 +135,12 @@ class StatePlugins { if(embeddedConfig != null) { val existing = getPlugin(embedded.key); if(existing != null && (existing.config.version < embeddedConfig.version || (force || FORCE_REINSTALL_EMBEDDED))) { - Logger.i(TAG, "Outdated Embedded plugin [${existing.config.id}] ${existing.config.name} (${existing.config.version} < ${embeddedConfig?.version}), reinstalling"); - //deletePlugin(embedded.key); + Logger.i(TAG, "Outdated Embedded plugin [${existing.config.id}] ${existing.config.name} (${existing.config.version} < ${embeddedConfig.version}), reinstalling"); installEmbeddedPlugin(context, embedded.value) } - else if(existing != null && _isFirstEmbedUpdate) - Logger.i(TAG, "Embedded plugin [${existing.config.id}] ${existing.config.name}, up to date (${existing.config.version} >= ${embeddedConfig?.version})"); + else if(existing != null && _isFirstEmbedUpdate) { + Logger.i(TAG, "Embedded plugin [${existing.config.id}] ${existing.config.name}, up to date (${existing.config.version} >= ${embeddedConfig.version})"); + } } } _isFirstEmbedUpdate = false; @@ -156,20 +157,18 @@ class StatePlugins { } } fun getEmbeddedPluginConfig(context: Context, assetConfigPath: String): SourcePluginConfig? { - val configJson = StateAssets.readAsset(context, assetConfigPath, false) ?: null; - if(configJson == null) - return null; + val configJson = StateAssets.readAsset(context, assetConfigPath) ?: return null; return SourcePluginConfig.fromJson(configJson, ""); } fun installEmbeddedPlugin(context: Context, assetConfigPath: String, id: String? = null): Boolean { try { - val configJson = StateAssets.readAsset(context, assetConfigPath, false) ?: + val configJson = StateAssets.readAsset(context, assetConfigPath) ?: throw IllegalStateException("Plugin config asset [${assetConfigPath}] not found"); val config = SourcePluginConfig.fromJson(configJson, ""); if(id != null && config.id != id) throw IllegalStateException("Attempted to install embedded plugin with different id [${config.id}]"); - val script = StateAssets.readAssetRelative(context, assetConfigPath, config.scriptUrl, false); + val script = StateAssets.readAssetRelative(context, assetConfigPath, config.scriptUrl); if(script.isNullOrEmpty()) throw IllegalStateException("Plugin script asset [${config.scriptUrl}] could not be found in assets"); @@ -222,9 +221,9 @@ class StatePlugins { catch(ex: Exception) { Logger.e(TAG, "Failed fetch config", ex); withContext(Dispatchers.Main) { - UIDialogs.showGeneralErrorDialog(context, "Failed to install plugin\n(${sourceUrl})", ex, "Ok", { + UIDialogs.showGeneralErrorDialog(context, "Failed to install plugin\n(${sourceUrl})", ex, "Ok") { handler?.invoke(false); - }); + }; }; return@launch; } diff --git a/app/src/main/java/com/futo/platformplayer/states/StateTelemetry.kt b/app/src/main/java/com/futo/platformplayer/states/StateTelemetry.kt index bc26581a..ac353c55 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateTelemetry.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateTelemetry.kt @@ -1,6 +1,5 @@ package com.futo.platformplayer.states -import android.content.Context import android.os.Build import com.futo.platformplayer.BuildConfig import com.futo.platformplayer.api.http.ManagedHttpClient @@ -8,13 +7,12 @@ import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.models.Telemetry import com.futo.platformplayer.stores.FragmentedStorage import com.futo.platformplayer.stores.StringStorage -import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json -import java.time.Instant import java.util.UUID class StateTelemetry { @@ -26,6 +24,7 @@ class StateTelemetry { } } + @OptIn(DelicateCoroutinesApi::class) fun upload() { GlobalScope.launch(Dispatchers.IO) { try { @@ -49,7 +48,6 @@ class StateTelemetry { val json = Json.encodeToString(telemetry); val url = "https://logs.grayjay.app/telemetry"; - //val url = "http://10.0.0.5:5413/telemetry"; val client = ManagedHttpClient(); val response = client.post(url, json, headers); if (response.isOk) { diff --git a/app/src/main/java/com/futo/platformplayer/stores/FragmentedStorage.kt b/app/src/main/java/com/futo/platformplayer/stores/FragmentedStorage.kt index a940a1ea..7a5b7cf2 100644 --- a/app/src/main/java/com/futo/platformplayer/stores/FragmentedStorage.kt +++ b/app/src/main/java/com/futo/platformplayer/stores/FragmentedStorage.kt @@ -1,20 +1,14 @@ package com.futo.platformplayer.stores -import com.futo.platformplayer.Settings import com.futo.platformplayer.logging.Logger -import com.futo.platformplayer.stores.db.ManagedDBIndex -import com.futo.platformplayer.stores.db.ManagedDBStore import com.futo.platformplayer.stores.v2.JsonStoreSerializer import com.futo.platformplayer.stores.v2.ManagedStore import com.futo.platformplayer.stores.v2.StoreSerializer -import kotlinx.serialization.* -import kotlinx.serialization.json.* -import kotlinx.serialization.modules.SerializersModule +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json import java.io.File import java.util.UUID -import kotlin.reflect.KType -import kotlin.reflect.full.createInstance -import kotlin.reflect.full.createType @Serializable() class FragmentedStorage { @@ -46,15 +40,17 @@ class FragmentedStorage { val tempFile = File(dir, UUID.randomUUID().toString()); tempFile.writeText(text); - val parsed = if (FragmentedStorageFileJson::class.java.isAssignableFrom(T::class.java)) - loadJsonFile(tempFile, null) - else if (FragmentedStorageFileString::class.java.isAssignableFrom(T::class.java)) - loadTextFile(tempFile, null) - else - throw NotImplementedError("Unknown file type for ${T::class.java.name}"); + val parsed = if (FragmentedStorageFileJson::class.java.isAssignableFrom(T::class.java)) { + loadJsonFile(tempFile, null) + } else if (FragmentedStorageFileString::class.java.isAssignableFrom(T::class.java)) { + loadTextFile(tempFile, null) + } else { + throw NotImplementedError("Unknown file type for ${T::class.java.name}"); + } - if (parsed == null) + if (parsed == null) { throw IllegalStateException("Failed to import type [${T::class.java.name}]"); + } } val name = T::class.java.name; @@ -71,9 +67,11 @@ class FragmentedStorage { val name = T::class.java.name; synchronized(_cachedFiles) { - if(reload) + if(reload) { _cachedFiles.remove(name); - var instance = _cachedFiles.get(name); + } + + var instance = _cachedFiles[name]; if (instance == null) { instance = load(); _cachedFiles[name] = instance; @@ -120,7 +118,7 @@ class FragmentedStorage { return loadFile(storageFile, storageBakFile); } inline fun load(dir: File, fileName: String): T where T : FragmentedStorageFile { - val storageFile = File(dir, "${fileName}"); + val storageFile = File(dir, fileName); val storageBakFile = File(dir, "${fileName}.bak"); return loadFile(storageFile, storageBakFile); } @@ -139,13 +137,16 @@ class FragmentedStorage { } fun deleteFile(dir: File, fileName: String) { - val storageFile = File(dir, "${fileName}"); + val storageFile = File(dir, fileName); val storageBakFile = File(dir, "${fileName}.bak"); - if(storageFile.exists()) + if(storageFile.exists()) { storageFile.delete(); - if(storageBakFile.exists()) + } + + if(storageBakFile.exists()) { storageBakFile.delete(); + } } @@ -162,67 +163,75 @@ class FragmentedStorage { inline fun loadFile(file: File, bakFile: File?): T where T : FragmentedStorageFile { val typeName = T::class.java.name; if (file.exists()) { - var resp = - if(FragmentedStorageFileJson::class.java.isAssignableFrom(T::class.java)) - loadJsonFile(file, bakFile) - else if(FragmentedStorageFileString::class.java.isAssignableFrom(T::class.java)) - loadTextFile(file, bakFile) - else - throw NotImplementedError("Unknown file type for ${typeName}"); - if(resp != null) + val resp = if(FragmentedStorageFileJson::class.java.isAssignableFrom(T::class.java)) { + loadJsonFile(file, bakFile) + } else if(FragmentedStorageFileString::class.java.isAssignableFrom(T::class.java)) { + loadTextFile(file, bakFile) + } else { + throw NotImplementedError("Unknown file type for $typeName"); + } + + if(resp != null) { return resp; + } } else { Logger.w(TAG, "Failed to fragment storage because the file does not exist. Attempting backup. [${typeName}]"); } - if (bakFile?.exists() ?: false) { - var resp = - if(FragmentedStorageFileJson::class.java.isAssignableFrom(T::class.java)) - loadJsonFile(file, bakFile, true) - else if(FragmentedStorageFileString::class.java.isAssignableFrom(T::class.java)) - loadTextFile(file, bakFile, true) - else - throw NotImplementedError("Unknown file type"); - if(resp != null) + if (bakFile?.exists() == true) { + val resp = if(FragmentedStorageFileJson::class.java.isAssignableFrom(T::class.java)) { + loadJsonFile(file, bakFile, true) + } else if(FragmentedStorageFileString::class.java.isAssignableFrom(T::class.java)) { + loadTextFile(file, bakFile, true) + } else { + throw NotImplementedError("Unknown file type"); + } + + if(resp != null) { return resp; + } } else { Logger.w(TAG, "Failed to fragment storage because the backup file does not exist. Using default instance. [${typeName}]"); } - return (T::class.java.newInstance() as T).withFile(file, bakFile) as T; + return (T::class.java.getDeclaredConstructor().newInstance() as T).withFile(file, bakFile) as T; } inline fun loadDirectory(file: File): T where T : IFragmentedStorageDirectory { - return (T::class.java.newInstance() as T).withDirectory(file) as T; + return (T::class.java.getDeclaredConstructor().newInstance() as T).withDirectory(file) as T; } inline fun loadJsonFile(file: File, bakFile: File?, fromBak: Boolean = false) : T? { try { val json = (if(!fromBak) file else bakFile)?.readText() ?: return null; val fileObj = jsonSerializer.decodeFromString(json); - if(fromBak && bakFile != null) + if(fromBak && bakFile != null) { bakFile.copyTo(file, true); + } return fileObj.withFile(file, bakFile) as T; } catch (e: Throwable) { - if(!fromBak) + if(!fromBak) { Logger.e(TAG, "Failed to load fragment storage. Attempting backup.", e); - else + } else { Logger.e(TAG, "Failed to load fragment storage. Using default instance.", e); + } } return null; } inline fun loadTextFile(file: File, bakFile: File?, fromBak: Boolean = false) : T? { try { val text = (if(!fromBak) file else bakFile)?.readText() ?: return null; - val fileObj = (T::class.java.newInstance() as T).withFile(file, bakFile) as T + val fileObj = (T::class.java.getDeclaredConstructor().newInstance() as T).withFile(file, bakFile) as T (fileObj as FragmentedStorageFileString).decode(text); - if(fromBak && bakFile != null) + if(fromBak && bakFile != null) { bakFile.copyTo(file, true); + } return fileObj.withFile(file, bakFile) as T; } catch (e: Throwable) { - if(!fromBak) + if(!fromBak) { Logger.w(TAG, "Failed to load fragment storage. Attempting backup.", e); - else + } else { Logger.w(TAG, "Failed to load fragment storage. Using default instance.", e); + } } return null; } diff --git a/app/src/main/java/com/futo/platformplayer/stores/FragmentedStorageDirectory.kt b/app/src/main/java/com/futo/platformplayer/stores/FragmentedStorageDirectory.kt index 08b2ec10..9ecb189f 100644 --- a/app/src/main/java/com/futo/platformplayer/stores/FragmentedStorageDirectory.kt +++ b/app/src/main/java/com/futo/platformplayer/stores/FragmentedStorageDirectory.kt @@ -16,10 +16,11 @@ open class FragmentedStorageDirectory : IFragmentedStorageDirectory { var directory: File? = null; fun getFiles() : List { - return directory!!.listFiles() - .filter { it.extension != ".bak" } - .map { it.name }; + return directory?.listFiles() + ?.filter { it.extension != ".bak" } + ?.map { it.name } ?: listOf(); } + fun hasFile(name: String) : Boolean { return File(directory, name).exists(); } diff --git a/app/src/main/java/com/futo/platformplayer/stores/SubscriptionStorage.kt b/app/src/main/java/com/futo/platformplayer/stores/SubscriptionStorage.kt index 1e047dca..647f80c7 100644 --- a/app/src/main/java/com/futo/platformplayer/stores/SubscriptionStorage.kt +++ b/app/src/main/java/com/futo/platformplayer/stores/SubscriptionStorage.kt @@ -1,10 +1,11 @@ package com.futo.platformplayer.stores -import com.futo.platformplayer.states.StateSubscriptions -import com.futo.platformplayer.models.Subscription import com.futo.platformplayer.api.media.models.PlatformAuthorLink import com.futo.platformplayer.api.media.models.channels.IPlatformChannel -import kotlinx.serialization.* +import com.futo.platformplayer.models.Subscription +import com.futo.platformplayer.states.StateSubscriptions +import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json @Serializable() @@ -18,7 +19,6 @@ class SubscriptionStorage : FragmentedStorageFileJson() { } fun removeSubscription(url : String) { - val toRemove = subscriptions.firstOrNull { it.channel.url == url }; subscriptions.removeIf { it.channel.url == url }; } diff --git a/app/src/main/java/com/futo/platformplayer/stores/db/ManagedDBContextPaged.kt b/app/src/main/java/com/futo/platformplayer/stores/db/ManagedDBContextPaged.kt index 3449d3c7..78539ca3 100644 --- a/app/src/main/java/com/futo/platformplayer/stores/db/ManagedDBContextPaged.kt +++ b/app/src/main/java/com/futo/platformplayer/stores/db/ManagedDBContextPaged.kt @@ -1,9 +1,6 @@ package com.futo.platformplayer.stores.db import androidx.room.Dao -import androidx.room.Delete -import androidx.room.Insert -import androidx.room.Update @Dao interface ManagedDBContextPaged> { diff --git a/app/src/main/java/com/futo/platformplayer/stores/db/ManagedDBDAOBase.kt b/app/src/main/java/com/futo/platformplayer/stores/db/ManagedDBDAOBase.kt index 05c19390..3341ef8b 100644 --- a/app/src/main/java/com/futo/platformplayer/stores/db/ManagedDBDAOBase.kt +++ b/app/src/main/java/com/futo/platformplayer/stores/db/ManagedDBDAOBase.kt @@ -3,7 +3,6 @@ package com.futo.platformplayer.stores.db import androidx.room.Dao import androidx.room.Delete import androidx.room.Insert -import androidx.room.Query import androidx.room.RawQuery import androidx.room.Update import androidx.sqlite.db.SupportSQLiteQuery diff --git a/app/src/main/java/com/futo/platformplayer/stores/db/ManagedDBDescriptor.kt b/app/src/main/java/com/futo/platformplayer/stores/db/ManagedDBDescriptor.kt index ce3c9c70..9d8606a1 100644 --- a/app/src/main/java/com/futo/platformplayer/stores/db/ManagedDBDescriptor.kt +++ b/app/src/main/java/com/futo/platformplayer/stores/db/ManagedDBDescriptor.kt @@ -1,8 +1,5 @@ package com.futo.platformplayer.stores.db -import androidx.sqlite.db.SimpleSQLiteQuery -import com.futo.platformplayer.models.HistoryVideo -import com.futo.platformplayer.stores.db.types.DBHistory import kotlin.reflect.KClass diff --git a/app/src/main/java/com/futo/platformplayer/stores/db/ManagedDBIndex.kt b/app/src/main/java/com/futo/platformplayer/stores/db/ManagedDBIndex.kt index c4cf5295..b95fa32d 100644 --- a/app/src/main/java/com/futo/platformplayer/stores/db/ManagedDBIndex.kt +++ b/app/src/main/java/com/futo/platformplayer/stores/db/ManagedDBIndex.kt @@ -3,7 +3,6 @@ package com.futo.platformplayer.stores.db import androidx.room.ColumnInfo import androidx.room.Ignore import androidx.room.PrimaryKey -import com.futo.platformplayer.api.media.Serializer open class ManagedDBIndex { @ColumnIndex diff --git a/app/src/main/java/com/futo/platformplayer/stores/db/ManagedDBIndexOnly.kt b/app/src/main/java/com/futo/platformplayer/stores/db/ManagedDBIndexOnly.kt index 0795555d..3d1551db 100644 --- a/app/src/main/java/com/futo/platformplayer/stores/db/ManagedDBIndexOnly.kt +++ b/app/src/main/java/com/futo/platformplayer/stores/db/ManagedDBIndexOnly.kt @@ -1,9 +1,6 @@ package com.futo.platformplayer.stores.db import androidx.room.Dao -import androidx.room.Delete -import androidx.room.Insert -import androidx.room.Update @Dao interface ManagedDBIndexOnly> { diff --git a/app/src/main/java/com/futo/platformplayer/stores/db/ManagedDBStore.kt b/app/src/main/java/com/futo/platformplayer/stores/db/ManagedDBStore.kt index e4d57744..dc873bc0 100644 --- a/app/src/main/java/com/futo/platformplayer/stores/db/ManagedDBStore.kt +++ b/app/src/main/java/com/futo/platformplayer/stores/db/ManagedDBStore.kt @@ -357,7 +357,7 @@ class ManagedDBStore, T, D: ManagedDBDatabase, DA fun getPage(page: Int, length: Int): List { if(_sqlPage == null) throw IllegalStateException("DB Store [${name}] does not have ordered fields to provide pages"); - val query = _sqlPage!!(page, length) ?: throw IllegalStateException("Paged db not setup for ${_name}"); + val query = _sqlPage!!(page, length); return deserializeIndexes(dbDaoBase.getMultiple(query)); } fun getPageObjects(page: Int, length: Int): List = convertObjects(getPage(page, length)); diff --git a/app/src/main/java/com/futo/platformplayer/stores/v2/ManagedStore.kt b/app/src/main/java/com/futo/platformplayer/stores/v2/ManagedStore.kt index 5346d861..ec0a9fa2 100644 --- a/app/src/main/java/com/futo/platformplayer/stores/v2/ManagedStore.kt +++ b/app/src/main/java/com/futo/platformplayer/stores/v2/ManagedStore.kt @@ -10,13 +10,9 @@ import kotlinx.serialization.json.Json import kotlinx.serialization.serializer import java.io.File import java.io.FileNotFoundException -import java.lang.Exception -import java.util.* -import kotlin.collections.ArrayList -import kotlin.collections.HashMap +import java.util.UUID import kotlin.reflect.KClass import kotlin.reflect.KType -import kotlin.reflect.javaType class ManagedStore{ private val _class: KType; @@ -68,8 +64,7 @@ class ManagedStore{ fun load(): ManagedStore { synchronized(_files) { _files.clear(); - val newObjs = _dir.listFiles().map { it.nameWithoutExtension }.distinct().toList().map { fileId -> - //Logger.i(TAG, "FILE:" + it.name); + val newObjs = _dir.listFiles()?.map { it.nameWithoutExtension }?.distinct()?.toList()?.map { fileId -> val mfile = ManagedFile(fileId, _dir); val obj = mfile.load(this, _withBackup); if(obj == null) { @@ -82,10 +77,12 @@ class ManagedStore{ } return@map Pair(obj, mfile); - }.filter { it.first != null }; + }?.filter { it.first != null }; - for (obj in newObjs) - _files.put(obj.first!!, obj.second); + if (newObjs != null) { + for (obj in newObjs) + _files[obj.first!!] = obj.second; + } } _isLoaded = true; return this; diff --git a/app/src/main/java/com/futo/platformplayer/subscription/SimpleSubscriptionAlgorithm.kt b/app/src/main/java/com/futo/platformplayer/subscription/SimpleSubscriptionAlgorithm.kt index bf40d738..f4a9818e 100644 --- a/app/src/main/java/com/futo/platformplayer/subscription/SimpleSubscriptionAlgorithm.kt +++ b/app/src/main/java/com/futo/platformplayer/subscription/SimpleSubscriptionAlgorithm.kt @@ -1,6 +1,5 @@ package com.futo.platformplayer.subscription -import com.futo.platformplayer.Settings import com.futo.platformplayer.api.media.models.ResultCapabilities import com.futo.platformplayer.api.media.models.contents.IPlatformContent import com.futo.platformplayer.api.media.platforms.js.JSClient @@ -15,15 +14,10 @@ import com.futo.platformplayer.exceptions.ChannelException import com.futo.platformplayer.findNonRuntimeException import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.models.Subscription -import com.futo.platformplayer.polycentric.PolycentricCache import com.futo.platformplayer.states.StateCache import com.futo.platformplayer.states.StatePlatform -import com.futo.platformplayer.states.StatePolycentric import com.futo.platformplayer.states.StateSubscriptions import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.runBlocking -import java.lang.Exception -import java.lang.IllegalStateException import java.util.concurrent.ExecutionException import java.util.concurrent.ForkJoinPool import java.util.concurrent.ForkJoinTask @@ -42,14 +36,16 @@ class SimpleSubscriptionAlgorithm( for(sub in subs) { for(subUrl in sub.value) { val client = StatePlatform.instance.getChannelClientOrNull(sub.key.channel.url); - if (client !is JSClient) + if (client !is JSClient) { continue; + } val channelCaps = client.getChannelCapabilities(); - if (!pluginReqCounts.containsKey(client)) + if (!pluginReqCounts.containsKey(client)) { pluginReqCounts[client] = 1; - else + } else { pluginReqCounts[client] = pluginReqCounts[client]!! + 1; + } if (channelCaps.hasType(ResultCapabilities.TYPE_STREAMS) && sub.key.shouldFetchStreams()) pluginReqCounts[client] = pluginReqCounts[client]!! + 1; @@ -71,7 +67,6 @@ class SimpleSubscriptionAlgorithm( val tasks = mutableListOf?>>>(); var finished = 0; val exceptionMap: HashMap = hashMapOf(); - val concurrency = Settings.instance.subscriptions.getSubscriptionsConcurrency(); val failedPlugins = arrayListOf(); for (sub in subs.filter { StatePlatform.instance.hasEnabledChannelClient(it.key.channel.url) }) tasks.add(getSubscription(sub.key, sub.value, failedPlugins){ channelEx -> @@ -79,7 +74,6 @@ class SimpleSubscriptionAlgorithm( onProgress.emit(finished, tasks.size); val ex = channelEx?.cause; - if(channelEx != null) { synchronized(exceptionMap) { exceptionMap.put(sub.key, channelEx); @@ -107,42 +101,46 @@ class SimpleSubscriptionAlgorithm( val timeTotal = measureTimeMillis { val taskResults = arrayListOf>(); - for(task in tasks) { + for (task in tasks) { try { val result = task.get(); - if(result != null) { - if(result.second != null) + if (result != null) { + if(result.second != null) { taskResults.add(result.second!!); - if(exceptionMap.containsKey(result.first)) { + } + + if (exceptionMap.containsKey(result.first)) { val ex = exceptionMap[result.first]; - if(ex != null) { + if (ex != null) { val nonRuntimeEx = findNonRuntimeException(ex); - if (nonRuntimeEx != null && (nonRuntimeEx is PluginException || nonRuntimeEx is ChannelException)) + if (nonRuntimeEx != null && (nonRuntimeEx is PluginException || nonRuntimeEx is ChannelException)) { exs.add(nonRuntimeEx); - else + } else { throw ex.cause ?: ex; + } } } } } catch (ex: ExecutionException) { val nonRuntimeEx = findNonRuntimeException(ex.cause); - if(nonRuntimeEx != null && (nonRuntimeEx is PluginException || nonRuntimeEx is ChannelException)) + if (nonRuntimeEx != null && (nonRuntimeEx is PluginException || nonRuntimeEx is ChannelException)) { exs.add(nonRuntimeEx); - else + } else { throw ex.cause ?: ex; + } }; } subsPager = taskResults.toTypedArray(); } Logger.i("StateSubscriptions", "Subscriptions results in ${timeTotal}ms") - if(subsPager.size <= 0 && exs.any()) + if(subsPager.isEmpty() && exs.any()) { throw exs.first(); + } Logger.i(StateSubscriptions.TAG, "Subscription pager with ${subsPager.size} channels"); val pager = MultiChronoContentPager(subsPager, allowFailure, 15); pager.initialize(); - //return Pair(pager, exs); return Result(DedupContentPager(pager), exs); } @@ -156,7 +154,6 @@ class SimpleSubscriptionAlgorithm( val platformClient = StatePlatform.instance.getChannelClientOrNull(url, toIgnore) ?: continue; val time = measureTimeMillis { pager = StatePlatform.instance.getChannelContent(platformClient, url, true, threadPool.poolSize, toIgnore); - pager = StateCache.cachePagerResults(scope, pager!!) { onNewCacheHit.emit(sub, it); }; @@ -172,16 +169,19 @@ class SimpleSubscriptionAlgorithm( Logger.e(StateSubscriptions.TAG, "Subscription [${sub.channel.name}] failed", ex); val channelEx = ChannelException(sub.channel, ex); onFinished(channelEx); - if(!withCacheFallback) + if(!withCacheFallback) { throw channelEx; - else { + } else { Logger.i(StateSubscriptions.TAG, "Channel ${sub.channel.name} failed, substituting with cache"); pager = StateCache.instance.getChannelCachePager(sub.channel.url); } } } - if(pager == null) + + if(pager == null) { throw IllegalStateException("Uncaught nullable pager"); + } + return@submit Pair(sub, pager); }; } diff --git a/app/src/main/java/com/futo/platformplayer/subscription/SmartSubscriptionAlgorithm.kt b/app/src/main/java/com/futo/platformplayer/subscription/SmartSubscriptionAlgorithm.kt index 1c3355b5..840fc288 100644 --- a/app/src/main/java/com/futo/platformplayer/subscription/SmartSubscriptionAlgorithm.kt +++ b/app/src/main/java/com/futo/platformplayer/subscription/SmartSubscriptionAlgorithm.kt @@ -1,10 +1,7 @@ package com.futo.platformplayer.subscription import com.futo.platformplayer.api.media.models.ResultCapabilities -import com.futo.platformplayer.api.media.models.contents.IPlatformContent import com.futo.platformplayer.api.media.platforms.js.JSClient -import com.futo.platformplayer.api.media.structures.IPager -import com.futo.platformplayer.getNowDiffDays import com.futo.platformplayer.getNowDiffHours import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.models.Subscription @@ -28,35 +25,37 @@ class SmartSubscriptionAlgorithm( //For every platform, get all sub-queries associated with that platform return@flatMap allPlatforms .filter { it.value != null } - .flatMap { - val url = it.key; - val client = it.value!! as JSClient; + .flatMap innerFlatMap@ { pair -> + val url = pair.key; + val client = pair.value!! as JSClient; val capabilities = client.getChannelCapabilities(); if(capabilities.hasType(ResultCapabilities.TYPE_MIXED) || capabilities.types.isEmpty()) - return@flatMap listOf(SubscriptionTask(client, sub, it.key, ResultCapabilities.TYPE_MIXED)); + return@innerFlatMap listOf(SubscriptionTask(client, sub, pair.key, ResultCapabilities.TYPE_MIXED)); else if(capabilities.hasType(ResultCapabilities.TYPE_SUBSCRIPTIONS)) - return@flatMap listOf(SubscriptionTask(client, sub, it.key, ResultCapabilities.TYPE_SUBSCRIPTIONS)) + return@innerFlatMap listOf(SubscriptionTask(client, sub, pair.key, ResultCapabilities.TYPE_SUBSCRIPTIONS)) else { - val types = listOf( - if(sub.shouldFetchVideos()) ResultCapabilities.TYPE_VIDEOS else null, - if(sub.shouldFetchStreams()) ResultCapabilities.TYPE_STREAMS else null, - if(sub.shouldFetchPosts()) ResultCapabilities.TYPE_POSTS else null, - if(sub.shouldFetchLiveStreams()) ResultCapabilities.TYPE_LIVE else null - ).filterNotNull().filter { capabilities.hasType(it) }; + val types = listOfNotNull( + if (sub.shouldFetchVideos()) ResultCapabilities.TYPE_VIDEOS else null, + if (sub.shouldFetchStreams()) ResultCapabilities.TYPE_STREAMS else null, + if (sub.shouldFetchPosts()) ResultCapabilities.TYPE_POSTS else null, + if (sub.shouldFetchLiveStreams()) ResultCapabilities.TYPE_LIVE else null + ).filter { capabilities.hasType(it) }; - if(!types.isEmpty()) - return@flatMap types.map { + if(types.isNotEmpty()) { + return@innerFlatMap types.map { SubscriptionTask(client, sub, url, it); }; - else + } else { listOf(SubscriptionTask(client, sub, url, ResultCapabilities.TYPE_VIDEOS, true)) + } } }; }; - for(task in allTasks) + for(task in allTasks) { task.urgency = calculateUpdateUrgency(task.sub, task.type); + } val ordering = allTasks.groupBy { it.client } .map { Pair(it.key, it.value.sortedBy { it.urgency }) }; @@ -66,16 +65,16 @@ class SmartSubscriptionAlgorithm( for(clientTasks in ordering) { val limit = clientTasks.first.getSubscriptionRateLimit(); - if(limit == null || limit <= 0) + if(limit == null || limit <= 0) { finalTasks.addAll(clientTasks.second); - else { + } else { val fetchTasks = mutableListOf(); val cacheTasks = mutableListOf(); for(task in clientTasks.second) { - if(!task.fromCache && fetchTasks.size < limit) + if (!task.fromCache && fetchTasks.size < limit) { fetchTasks.add(task); - else { + } else { task.fromCache = true; cacheTasks.add(task); } diff --git a/app/src/main/java/com/futo/platformplayer/subscription/SubscriptionFetchAlgorithm.kt b/app/src/main/java/com/futo/platformplayer/subscription/SubscriptionFetchAlgorithm.kt index 4b5dd8b7..a34f0e33 100644 --- a/app/src/main/java/com/futo/platformplayer/subscription/SubscriptionFetchAlgorithm.kt +++ b/app/src/main/java/com/futo/platformplayer/subscription/SubscriptionFetchAlgorithm.kt @@ -1,14 +1,11 @@ package com.futo.platformplayer.subscription -import com.futo.platformplayer.api.media.models.channels.IPlatformChannel import com.futo.platformplayer.api.media.models.contents.IPlatformContent import com.futo.platformplayer.api.media.platforms.js.JSClient import com.futo.platformplayer.api.media.structures.IPager import com.futo.platformplayer.constructs.Event2 import com.futo.platformplayer.models.Subscription import kotlinx.coroutines.CoroutineScope -import java.lang.Exception -import java.lang.IllegalStateException import java.util.concurrent.ForkJoinPool abstract class SubscriptionFetchAlgorithm( @@ -41,7 +38,6 @@ abstract class SubscriptionFetchAlgorithm( SubscriptionFetchAlgorithms.CACHE -> CachedSubscriptionAlgorithm(scope, allowFailure, withCacheFallback, pool, 50); SubscriptionFetchAlgorithms.SIMPLE -> SimpleSubscriptionAlgorithm(scope, allowFailure, withCacheFallback, pool); SubscriptionFetchAlgorithms.SMART -> SmartSubscriptionAlgorithm(scope, allowFailure, withCacheFallback, pool); - else -> throw IllegalStateException("Unknown algorithm ${algo}"); } } } diff --git a/app/src/main/java/com/futo/platformplayer/subscription/SubscriptionsTaskFetchAlgorithm.kt b/app/src/main/java/com/futo/platformplayer/subscription/SubscriptionsTaskFetchAlgorithm.kt index d51688df..b4b90624 100644 --- a/app/src/main/java/com/futo/platformplayer/subscription/SubscriptionsTaskFetchAlgorithm.kt +++ b/app/src/main/java/com/futo/platformplayer/subscription/SubscriptionsTaskFetchAlgorithm.kt @@ -1,6 +1,5 @@ package com.futo.platformplayer.subscription -import com.futo.platformplayer.Settings import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.activities.MainActivity import com.futo.platformplayer.api.media.models.ResultCapabilities @@ -24,7 +23,6 @@ import com.futo.platformplayer.states.StateCache import com.futo.platformplayer.states.StatePlatform import com.futo.platformplayer.states.StateSubscriptions import kotlinx.coroutines.CoroutineScope -import java.lang.IllegalStateException import java.util.concurrent.ExecutionException import java.util.concurrent.ForkJoinPool import java.util.concurrent.ForkJoinTask @@ -47,8 +45,6 @@ abstract class SubscriptionsTaskFetchAlgorithm( val tasks = getSubscriptionTasks(subs); val tasksGrouped = tasks.groupBy { it.client } - val taskCount = tasks.filter { !it.fromCache }.size; - val cacheCount = tasks.size - taskCount; Logger.i(TAG, "Starting Subscriptions Fetch:\n" + tasksGrouped.map { " ${it.key.name}: ${it.value.count { !it.fromCache }}, Cached(${it.value.count { it.fromCache } })" }.joinToString("\n")); @@ -63,7 +59,9 @@ abstract class SubscriptionsTaskFetchAlgorithm( } } - } catch (ex: Throwable){} + } catch (e: Throwable){ + Logger.e(TAG, "Error occurred in task.", e) + } val exs: ArrayList = arrayListOf(); @@ -78,25 +76,27 @@ abstract class SubscriptionsTaskFetchAlgorithm( try { val result = task.get(); if(result != null) { - if(result.pager != null) + if(result.pager != null) { taskResults.add(result); + } + if(result.exception != null) { val ex = result.exception; - if(ex != null) { - val nonRuntimeEx = findNonRuntimeException(ex); - if (nonRuntimeEx != null && (nonRuntimeEx is PluginException || nonRuntimeEx is ChannelException)) - exs.add(nonRuntimeEx); - else - throw ex.cause ?: ex; + val nonRuntimeEx = findNonRuntimeException(ex); + if (nonRuntimeEx != null && (nonRuntimeEx is PluginException || nonRuntimeEx is ChannelException)) { + exs.add(nonRuntimeEx); + } else { + throw ex.cause ?: ex; } } } } catch (ex: ExecutionException) { val nonRuntimeEx = findNonRuntimeException(ex.cause); - if(nonRuntimeEx != null && (nonRuntimeEx is PluginException || nonRuntimeEx is ChannelException)) + if(nonRuntimeEx != null && (nonRuntimeEx is PluginException || nonRuntimeEx is ChannelException)) { exs.add(nonRuntimeEx); - else + } else { throw ex.cause ?: ex; + } }; } } @@ -108,18 +108,19 @@ abstract class SubscriptionsTaskFetchAlgorithm( val sub = if(!entry.value.isEmpty()) entry.value[0].task.sub else null; val liveTasks = entry.value.filter { !it.task.fromCache }; val cachedTasks = entry.value.filter { it.task.fromCache }; - val livePager = if(!liveTasks.isEmpty()) StateCache.cachePagerResults(scope, MultiChronoContentPager(liveTasks.map { it.pager!! }, true).apply { this.initialize() }, { + val livePager = if(liveTasks.isNotEmpty()) StateCache.cachePagerResults(scope, MultiChronoContentPager(liveTasks.map { it.pager!! }, true).apply { this.initialize() }) { onNewCacheHit.emit(sub!!, it); - }) else null; - val cachedPager = if(!cachedTasks.isEmpty()) MultiChronoContentPager(cachedTasks.map { it.pager!! }, true).apply { this.initialize() } else null; - if(livePager != null && cachedPager == null) + } else null; + val cachedPager = if(cachedTasks.isNotEmpty()) MultiChronoContentPager(cachedTasks.map { it.pager!! }, true).apply { this.initialize() } else null; + if(livePager != null && cachedPager == null) { return@map livePager; - else if(cachedPager != null && livePager == null) + } else if(cachedPager != null && livePager == null) { return@map cachedPager; - else if(cachedPager == null && livePager == null) + } else if(cachedPager == null) { return@map EmptyPager(); - else - return@map MultiChronoContentPager(listOf(livePager!!, cachedPager!!), true).apply { this.initialize() } + } else { + return@map MultiChronoContentPager(listOf(livePager!!, cachedPager), true).apply { this.initialize() } + } } val pager = MultiChronoContentPager(groupedPagers, allowFailure, 15); @@ -138,9 +139,9 @@ abstract class SubscriptionsTaskFetchAlgorithm( if(task.fromCache) { finished++; onProgress.emit(finished, forkTasks.size); - if(cachedChannels.contains(task.url)) + if(cachedChannels.contains(task.url)) { return@submit SubscriptionTaskResult(task, null, null); - else { + } else { cachedChannels.add(task.url); return@submit SubscriptionTaskResult(task, StateCache.instance.getChannelCachePager(task.url), null); } @@ -148,10 +149,11 @@ abstract class SubscriptionsTaskFetchAlgorithm( } val shouldIgnore = synchronized(failedPlugins) { failedPlugins.contains(task.client.id) }; - if(shouldIgnore) + if(shouldIgnore) { return@submit SubscriptionTaskResult(task, null, null); //skipped + } - var taskEx: Throwable? = null; + val taskEx: Throwable?; var pager: IPager; try { val time = measureTimeMillis { @@ -202,7 +204,6 @@ abstract class SubscriptionsTaskFetchAlgorithm( return@submit SubscriptionTaskResult(task, pager, taskEx); } } - return@submit SubscriptionTaskResult(task, null, taskEx); } forkTasks.add(forkTask); } @@ -211,7 +212,6 @@ abstract class SubscriptionsTaskFetchAlgorithm( abstract fun getSubscriptionTasks(subs: Map>): List; - class SubscriptionTask( val client: JSClient, val sub: Subscription, diff --git a/app/src/main/java/com/futo/platformplayer/video/PlayerManager.kt b/app/src/main/java/com/futo/platformplayer/video/PlayerManager.kt index f13442a0..adce7f3a 100644 --- a/app/src/main/java/com/futo/platformplayer/video/PlayerManager.kt +++ b/app/src/main/java/com/futo/platformplayer/video/PlayerManager.kt @@ -1,12 +1,11 @@ +@file:Suppress("DEPRECATION") + package com.futo.platformplayer.video -import android.media.MediaPlayer import android.media.session.PlaybackState import android.support.v4.media.session.PlaybackStateCompat -import com.futo.platformplayer.constructs.Event1 import com.google.android.exoplayer2.ExoPlayer import com.google.android.exoplayer2.Player -import com.google.android.exoplayer2.ui.AspectRatioFrameLayout import com.google.android.exoplayer2.ui.StyledPlayerView class PlayerManager { @@ -102,7 +101,5 @@ class PlayerManager { var volume: Float = 1f; var listener: Player.Listener? = null; - - var resizMode: Int = AspectRatioFrameLayout.RESIZE_MODE_FIT; } } \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/MonetizationView.kt b/app/src/main/java/com/futo/platformplayer/views/MonetizationView.kt index d9e1011c..f5cc1389 100644 --- a/app/src/main/java/com/futo/platformplayer/views/MonetizationView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/MonetizationView.kt @@ -9,10 +9,9 @@ import android.widget.FrameLayout import android.widget.LinearLayout import android.widget.TextView import androidx.recyclerview.widget.RecyclerView -import com.futo.platformplayer.R import com.futo.platformplayer.HorizontalSpaceItemDecoration +import com.futo.platformplayer.R import com.futo.platformplayer.api.http.ManagedHttpClient -import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig import com.futo.platformplayer.constructs.Event0 import com.futo.platformplayer.constructs.TaskHandler import com.futo.platformplayer.logging.Logger @@ -22,7 +21,6 @@ import com.futo.platformplayer.views.AnyAdapterView.Companion.asAny import com.futo.platformplayer.views.adapters.viewholders.StoreItemViewHolder import com.futo.platformplayer.views.platform.PlatformIndicator import kotlinx.serialization.Serializable -import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json @Serializable @@ -122,7 +120,7 @@ class MonetizationView : LinearLayout { } } - fun setPolycentricProfile(cachedPolycentricProfile: PolycentricCache.CachedPolycentricProfile?, animate: Boolean) { + fun setPolycentricProfile(cachedPolycentricProfile: PolycentricCache.CachedPolycentricProfile?) { val profile = cachedPolycentricProfile?.profile; if (profile != null) { if (profile.systemState.store.isNotEmpty()) { diff --git a/app/src/main/java/com/futo/platformplayer/views/SupportView.kt b/app/src/main/java/com/futo/platformplayer/views/SupportView.kt index 0da86a64..3d846fe2 100644 --- a/app/src/main/java/com/futo/platformplayer/views/SupportView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/SupportView.kt @@ -4,31 +4,20 @@ import android.content.ClipData import android.content.ClipboardManager import android.content.Context import android.content.Intent -import android.graphics.drawable.Drawable import android.net.Uri import android.util.AttributeSet import android.view.View -import android.widget.ImageView import android.widget.LinearLayout import android.widget.TextView import android.widget.Toast import com.bumptech.glide.Glide -import com.bumptech.glide.request.target.CustomTarget -import com.bumptech.glide.request.transition.Transition import com.futo.platformplayer.R -import com.futo.platformplayer.dp import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile import com.futo.platformplayer.images.GlideHelper.Companion.crossfade import com.futo.platformplayer.logging.Logger -import com.futo.platformplayer.polycentric.PolycentricCache import com.futo.platformplayer.views.buttons.BigButton import com.futo.polycentric.core.toURLInfoSystemLinkUrl import com.google.android.material.imageview.ShapeableImageView -import com.google.android.material.shape.CornerFamily -import com.google.android.material.shape.ShapeAppearanceModel -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.json.Json -import userpackage.Protocol.ImageManifest class SupportView : LinearLayout { private val _layoutStore: LinearLayout @@ -141,7 +130,7 @@ class SupportView : LinearLayout { private fun createDonationButton(destination: String): BigButton { val uri = Uri.parse(destination) - var action: (() -> Unit)? = null + var action: (() -> Unit)? val (name, iconDrawableId, cryptoType) = if (uri.scheme == "http" || uri.scheme == "https") { val hostName = uri.host ?: "" @@ -202,7 +191,7 @@ class SupportView : LinearLayout { } } - fun setPolycentricProfile(profile: PolycentricProfile?, animate: Boolean) { + fun setPolycentricProfile(profile: PolycentricProfile?) { if (_polycentricProfile == profile) { return } diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/AnyAdapter.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/AnyAdapter.kt index 310b4c7e..225c498b 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/AnyAdapter.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/AnyAdapter.kt @@ -3,7 +3,6 @@ package com.futo.platformplayer.views.adapters import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView -import androidx.recyclerview.widget.RecyclerView.Recycler import androidx.recyclerview.widget.RecyclerView.ViewHolder import java.lang.reflect.Constructor @@ -102,7 +101,7 @@ class AnyAdapter> : BaseAnyAdapter { } abstract class AnyViewHolder(protected val _view: View) : ViewHolder(_view) { - abstract fun bind(i: I); + abstract fun bind(value: I); } companion object { diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/CommentWithReferenceViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/CommentWithReferenceViewHolder.kt index c47db9f6..6da01bd3 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/CommentWithReferenceViewHolder.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/CommentWithReferenceViewHolder.kt @@ -8,15 +8,20 @@ import android.widget.FrameLayout import android.widget.TextView import androidx.constraintlayout.widget.ConstraintLayout import androidx.recyclerview.widget.RecyclerView.ViewHolder -import com.futo.platformplayer.* +import com.futo.platformplayer.R +import com.futo.platformplayer.Settings import com.futo.platformplayer.api.media.models.comments.IPlatformComment import com.futo.platformplayer.api.media.models.comments.PolycentricPlatformComment import com.futo.platformplayer.api.media.models.ratings.RatingLikeDislikes import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.constructs.TaskHandler +import com.futo.platformplayer.fixHtmlLinks +import com.futo.platformplayer.fullyBackfillServersAnnounceExceptions import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.setPlatformPlayerLinkMovementMethod import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.states.StatePolycentric +import com.futo.platformplayer.toHumanNowDiffString import com.futo.platformplayer.views.others.CreatorThumbnail import com.futo.platformplayer.views.pills.PillButton import com.futo.platformplayer.views.pills.PillRatingLikesDislikes @@ -169,7 +174,7 @@ class CommentWithReferenceViewHolder : ViewHolder { _buttonReplies.setLoading(false) - val replies = likesDislikesReplies.replyCount ?: 0; + val replies = likesDislikesReplies.replyCount; _buttonReplies.visibility = View.VISIBLE; _buttonReplies.text.text = "$replies " + itemView.context.getString(R.string.replies); } else { diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/DeviceViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/DeviceViewHolder.kt index 8d4a6f0a..3c17617d 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/DeviceViewHolder.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/DeviceViewHolder.kt @@ -7,7 +7,12 @@ import android.widget.LinearLayout import android.widget.TextView import androidx.recyclerview.widget.RecyclerView.ViewHolder import com.futo.platformplayer.R -import com.futo.platformplayer.casting.* +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.constructs.Event1 class DeviceViewHolder : ViewHolder { @@ -48,15 +53,15 @@ class DeviceViewHolder : ViewHolder { }; _buttonConnect.setOnClickListener { - val d = device ?: return@setOnClickListener; + val dev = device ?: return@setOnClickListener; StateCasting.instance.activeDevice?.stopCasting(); - StateCasting.instance.connectDevice(d); + StateCasting.instance.connectDevice(dev); updateButton(); }; _buttonRemove.setOnClickListener { - val d = device ?: return@setOnClickListener; - onRemove.emit(d); + val dev = device ?: return@setOnClickListener; + onRemove.emit(dev); }; setIsRememberedDevice(false); diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/EmptyPreviewViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/EmptyPreviewViewHolder.kt index bfd86f56..05b33db2 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/EmptyPreviewViewHolder.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/EmptyPreviewViewHolder.kt @@ -2,7 +2,6 @@ package com.futo.platformplayer.views.adapters import android.view.View import android.view.ViewGroup -import androidx.recyclerview.widget.RecyclerView.ViewHolder import com.futo.platformplayer.api.media.models.contents.IPlatformContent import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/EnabledSourceViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/EnabledSourceViewHolder.kt index b6796e84..78cb55c4 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/EnabledSourceViewHolder.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/EnabledSourceViewHolder.kt @@ -1,8 +1,8 @@ package com.futo.platformplayer.views.adapters +import android.annotation.SuppressLint import android.view.MotionEvent import android.view.View -import android.view.View.OnTouchListener import android.widget.ImageView import android.widget.LinearLayout import android.widget.TextView @@ -10,7 +10,6 @@ import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView.ViewHolder import com.futo.platformplayer.R import com.futo.platformplayer.api.media.IPlatformClient -import com.futo.platformplayer.api.media.platforms.js.JSClient import com.futo.platformplayer.constructs.Event1 class EnabledSourceViewHolder : ViewHolder { @@ -25,6 +24,7 @@ class EnabledSourceViewHolder : ViewHolder { var source: IPlatformClient? = null private set + @SuppressLint("ClickableViewAccessibility") constructor(view: View, touchHelper: ItemTouchHelper) : super(view) { _imageSource = view.findViewById(R.id.image_source); _textSource = view.findViewById(R.id.text_source); @@ -37,12 +37,13 @@ class EnabledSourceViewHolder : ViewHolder { source?.let { onClick.emit(it); }; }; - _imageDragDrop.setOnTouchListener(OnTouchListener { v, event -> + _imageDragDrop.setOnTouchListener { _, event -> if (event.action == MotionEvent.ACTION_DOWN) { touchHelper.startDrag(this); } + false - }); + }; _buttonRemove.setOnClickListener { source?.let { onRemove.emit(it); }; diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/SubscriptionAdapter.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/SubscriptionAdapter.kt index fe33c0da..034b6e66 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/SubscriptionAdapter.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/SubscriptionAdapter.kt @@ -4,9 +4,9 @@ import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.models.Subscription import com.futo.platformplayer.states.StateSubscriptions -import com.futo.platformplayer.constructs.Event1 class SubscriptionAdapter : RecyclerView.Adapter { private lateinit var _sortedDataset: List; @@ -30,7 +30,7 @@ class SubscriptionAdapter : RecyclerView.Adapter { _inflater = inflater; _confirmationMessage = confirmationMessage; - StateSubscriptions.instance.onSubscriptionsChanged.subscribe { subs, added -> updateDataset(); } + StateSubscriptions.instance.onSubscriptionsChanged.subscribe { _, _ -> updateDataset(); } updateDataset(); } diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/VideoListEditorViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/VideoListEditorViewHolder.kt index 43e610db..8f94f6d2 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/VideoListEditorViewHolder.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/VideoListEditorViewHolder.kt @@ -1,5 +1,6 @@ package com.futo.platformplayer.views.adapters +import android.annotation.SuppressLint import android.view.MotionEvent import android.view.View import android.widget.FrameLayout @@ -11,8 +12,6 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView.ViewHolder import com.bumptech.glide.Glide -import com.bumptech.glide.RequestBuilder -import com.bumptech.glide.load.resource.bitmap.RoundedCorners import com.futo.platformplayer.R import com.futo.platformplayer.api.media.models.video.IPlatformVideo import com.futo.platformplayer.constructs.Event1 @@ -43,6 +42,7 @@ class VideoListEditorViewHolder : ViewHolder { val onClick = Event1(); val onRemove = Event1(); + @SuppressLint("ClickableViewAccessibility") constructor(view: View, touchHelper: ItemTouchHelper) : super(view) { _root = view.findViewById(R.id.root); _imageThumbnail = view.findViewById(R.id.image_video_thumbnail); @@ -58,12 +58,12 @@ class VideoListEditorViewHolder : ViewHolder { _platformIndicator = view.findViewById(R.id.thumbnail_platform); _layoutDownloaded = view.findViewById(R.id.layout_downloaded); - _imageDragDrop.setOnTouchListener(View.OnTouchListener { v, event -> + _imageDragDrop.setOnTouchListener { _, event -> if (event.action == MotionEvent.ACTION_DOWN) { touchHelper.startDrag(this); } false - }); + }; _root.setOnClickListener { val v = video ?: return@setOnClickListener; diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewContentListAdapter.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewContentListAdapter.kt index 112fab64..2026ffd1 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewContentListAdapter.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewContentListAdapter.kt @@ -2,7 +2,8 @@ package com.futo.platformplayer.views.adapters.feedtypes import android.content.Context import android.util.Log -import android.view.* +import android.view.View +import android.view.ViewGroup import com.futo.platformplayer.api.media.models.PlatformAuthorLink import com.futo.platformplayer.api.media.models.contents.ContentType import com.futo.platformplayer.api.media.models.contents.IPlatformContent @@ -44,7 +45,7 @@ class PreviewContentListAdapter : InsertedViewAdapterWithLoader, exoPlayer: PlayerManager? = null, @@ -111,15 +112,16 @@ class PreviewContentListAdapter : InsertedViewAdapterWithLoader(); - - constructor(context: Context, feedStyle : FeedStyle) : super(context) { + constructor(context: Context, feedStyle: FeedStyle) : super(context) { inflate(feedStyle); _feedStyle = feedStyle; _textVideoName = findViewById(R.id.text_video_name); _textChannelName = findViewById(R.id.text_channel_name); - //_imageNeoPassChannel = findViewById(R.id.image_neopass_channel); _imageChannelThumbnail = findViewById(R.id.image_channel_thumbnail); _imageVideoThumbnail = findViewById(R.id.image_video_thumbnail); - _platformIndicator = findViewById(R.id.thumbnail_platform) - _textLockedDescription = findViewById(R.id.text_locked_description); - //_textBrowserOpen = findViewById(R.id.text_browser_open); _textLockedUrl = findViewById(R.id.text_locked_url); - _textVideoMetadata = findViewById(R.id.text_video_metadata); setOnClickListener { @@ -82,14 +51,14 @@ class PreviewLockedView : LinearLayout { } } - protected open fun inflate(feedStyle: FeedStyle) { + fun inflate(feedStyle: FeedStyle) { inflate(context, when(feedStyle) { FeedStyle.PREVIEW -> R.layout.list_locked_preview else -> R.layout.list_locked_thumbnail }, this) } - open fun bind(content: IPlatformContent) { + fun bind(content: IPlatformContent) { _textVideoName.text = content.name; _textChannelName.text = content.author.name; _platformIndicator.setPlatformFromClientID(content.id.pluginId); @@ -119,15 +88,11 @@ class PreviewLockedView : LinearLayout { } else { _textLockedUrl.visibility = VISIBLE; - _textVideoMetadata.text = "Tap to open in browser"; + _textVideoMetadata.text = context.getString(R.string.tap_to_open_in_browser); } } - open fun preview(video: IPlatformContentDetails?, paused: Boolean) { - - } - companion object { private val TAG = "PreviewLockedView" } diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewLockedViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewLockedViewHolder.kt index 6d8c2cc9..2c058fa3 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewLockedViewHolder.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewLockedViewHolder.kt @@ -1,22 +1,12 @@ package com.futo.platformplayer.views.adapters.feedtypes import android.content.Context -import android.graphics.drawable.Animatable -import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup -import android.widget.ImageView -import android.widget.TextView -import com.futo.platformplayer.* -import com.futo.platformplayer.api.media.models.contents.ContentType import com.futo.platformplayer.api.media.models.contents.IPlatformContent import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails -import com.futo.platformplayer.api.media.models.contents.PlatformContentPlaceholder import com.futo.platformplayer.constructs.Event1 -import com.futo.platformplayer.constructs.Event2 import com.futo.platformplayer.views.FeedStyle import com.futo.platformplayer.views.adapters.ContentPreviewViewHolder -import com.futo.platformplayer.views.platform.PlatformIndicator class PreviewLockedViewHolder : ContentPreviewViewHolder { @@ -35,7 +25,7 @@ class PreviewLockedViewHolder : ContentPreviewViewHolder { override fun bind(content: IPlatformContent) = view.bind(content); - override fun preview(video: IPlatformContentDetails?, paused: Boolean) { } + override fun preview(details: IPlatformContentDetails?, paused: Boolean) { } override fun stopPreview() { } override fun pausePreview() { } override fun resumePreview() { } diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewNestedVideoView.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewNestedVideoView.kt index d66bb466..64c28643 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewNestedVideoView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewNestedVideoView.kt @@ -22,6 +22,7 @@ import com.futo.platformplayer.views.FeedStyle import com.futo.platformplayer.views.LoaderView import com.futo.platformplayer.views.platform.PlatformIndicator import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch class PreviewNestedVideoView : PreviewVideoView { @@ -50,29 +51,33 @@ class PreviewNestedVideoView : PreviewVideoView { _textChannelName.setOnClickListener { _contentNested?.let { onChannelClicked.emit(it.author) } }; _textVideoMetadata.setOnClickListener { _contentNested?.let { onChannelClicked.emit(it.author) } }; _button_add_to.setOnClickListener { - if(_contentNested is IPlatformVideo) + if(_contentNested is IPlatformVideo) { _contentNested?.let { onAddToClicked.emit(it as IPlatformVideo) } - else _content?.let { - if(it is IPlatformNestedContent) - loadNested(it) { - if(it is IPlatformVideo) + } else _content?.let { c -> + if(c is IPlatformNestedContent) { + loadNested(c) { + if (it is IPlatformVideo) { onAddToClicked.emit(it); - else + } else { UIDialogs.toast(context, "Content is not a video"); + } } + } } }; _button_add_to_queue.setOnClickListener { - if(_contentNested is IPlatformVideo) + if(_contentNested is IPlatformVideo) { _contentNested?.let { onAddToQueueClicked.emit(it as IPlatformVideo) } - else _content?.let { - if(it is IPlatformNestedContent) - loadNested(it) { - if(it is IPlatformVideo) + } else _content?.let { c -> + if(c is IPlatformNestedContent) { + loadNested(c) { + if (it is IPlatformVideo) { onAddToQueueClicked.emit(it); - else + } else { UIDialogs.toast(context, "Content is not a video"); + } } + } } }; } @@ -85,9 +90,9 @@ class PreviewNestedVideoView : PreviewVideoView { } override fun onOpenClicked() { - if(_contentNested is IPlatformVideoDetails) + if(_contentNested is IPlatformVideoDetails) { super.onOpenClicked(); - else if(_content is IPlatformNestedContent) { + } else if(_content is IPlatformNestedContent) { (_content as IPlatformNestedContent).let { onContentUrlClicked.emit(it.contentUrl, if(_contentSupported) it.nestedContentType else ContentType.URL); }; @@ -111,24 +116,24 @@ class PreviewNestedVideoView : PreviewVideoView { .into(_imageVideo); }; - _contentSupported = content.contentSupported; if(!_contentSupported) { _containerUnavailable.visibility = View.VISIBLE; _containerLoader.visibility = View.GONE; _loaderView.stop(); - } - else { - if(_feedStyle == FeedStyle.THUMBNAIL) + } else { + if(_feedStyle == FeedStyle.THUMBNAIL) { _platformIndicator.setPlatformFromClientID(content.contentPlugin); - else + } else { _platformIndicatorNested.setPlatformFromClientID(content.contentPlugin); + } + _containerUnavailable.visibility = View.GONE; - if(_feedStyle == FeedStyle.PREVIEW) + if(_feedStyle == FeedStyle.PREVIEW) { loadNested(content); + } } - } - else { + } else { _contentSupported = false; _containerUnavailable.visibility = View.VISIBLE; _containerLoader.visibility = View.GONE; @@ -136,6 +141,7 @@ class PreviewNestedVideoView : PreviewVideoView { } } + @OptIn(ExperimentalCoroutinesApi::class) private fun loadNested(content: IPlatformNestedContent, onCompleted: ((IPlatformContentDetails)->Unit)? = null) { Logger.i(TAG, "Loading nested content [${content.contentUrl}]"); _containerLoader.visibility = View.VISIBLE; @@ -159,20 +165,20 @@ class PreviewNestedVideoView : PreviewVideoView { _loaderView.stop(); val nestedContent = def.getCompleted(); _contentNested = nestedContent; - if(nestedContent is IPlatformVideoDetails) { + if (nestedContent is IPlatformVideoDetails) { super.bind(nestedContent); if(_feedStyle == FeedStyle.PREVIEW) { _platformIndicator.setPlatformFromClientID(content.id.pluginId); _platformIndicatorNested.setPlatformFromClientID(nestedContent.id.pluginId); - } - else + } else { _platformIndicatorNested.setPlatformFromClientID(content.id.pluginId); - } - else { + } + } else { _containerUnavailable.visibility = View.VISIBLE; } - if(onCompleted != null) + if (onCompleted != null) { onCompleted(nestedContent); + } } } }; @@ -180,9 +186,9 @@ class PreviewNestedVideoView : PreviewVideoView { } override fun preview(video: IPlatformContentDetails?, paused: Boolean) { - if(video != null) + if(video != null) { super.preview(video, paused); - else if(_content is IPlatformVideoDetails) _contentNested?.let { + } else if(_content is IPlatformVideoDetails) _contentNested?.let { super.preview(it, paused); }; } diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewPlaceholderViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewPlaceholderViewHolder.kt index 6155717f..02fa3187 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewPlaceholderViewHolder.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewPlaceholderViewHolder.kt @@ -7,7 +7,7 @@ import android.view.View import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView -import com.futo.platformplayer.* +import com.futo.platformplayer.R import com.futo.platformplayer.api.media.models.contents.IPlatformContent import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails import com.futo.platformplayer.api.media.models.contents.PlatformContentPlaceholder @@ -58,7 +58,7 @@ class PreviewPlaceholderViewHolder : ContentPreviewViewHolder { } } - override fun preview(video: IPlatformContentDetails?, paused: Boolean) { } + override fun preview(details: IPlatformContentDetails?, paused: Boolean) { } override fun stopPreview() { } override fun pausePreview() { } override fun resumePreview() { } diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewPostViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewPostViewHolder.kt index 8007826c..5749e2b1 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewPostViewHolder.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewPostViewHolder.kt @@ -5,7 +5,6 @@ import com.futo.platformplayer.api.media.models.PlatformAuthorLink import com.futo.platformplayer.api.media.models.contents.IPlatformContent import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails import com.futo.platformplayer.constructs.Event1 -import com.futo.platformplayer.video.PlayerManager import com.futo.platformplayer.views.FeedStyle import com.futo.platformplayer.views.adapters.ContentPreviewViewHolder @@ -19,7 +18,7 @@ class PreviewPostViewHolder : ContentPreviewViewHolder { private val view: PreviewPostView get() = itemView as PreviewPostView; - constructor(viewGroup: ViewGroup, feedStyle : FeedStyle, exoPlayer: PlayerManager? = null): super( + constructor(viewGroup: ViewGroup, feedStyle : FeedStyle): super( PreviewPostView(viewGroup.context, feedStyle) ) { view.onContentClicked.subscribe(onContentClicked::emit); @@ -29,7 +28,7 @@ class PreviewPostViewHolder : ContentPreviewViewHolder { override fun bind(content: IPlatformContent) = view.bind(content); - override fun preview(video: IPlatformContentDetails?, paused: Boolean) {}; + override fun preview(details: IPlatformContentDetails?, paused: Boolean) {}; override fun stopPreview() {}; override fun pausePreview() {}; override fun resumePreview() {}; diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewVideoView.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewVideoView.kt index fad5a394..7d38b4a8 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewVideoView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewVideoView.kt @@ -2,16 +2,14 @@ package com.futo.platformplayer.views.adapters.feedtypes import android.animation.ObjectAnimator import android.content.Context -import android.content.res.Resources import android.util.Log -import android.util.TypedValue import android.view.View import android.widget.FrameLayout import android.widget.ImageView import android.widget.LinearLayout import android.widget.TextView import com.bumptech.glide.Glide -import com.futo.platformplayer.* +import com.futo.platformplayer.R import com.futo.platformplayer.api.media.PlatformID import com.futo.platformplayer.api.media.models.PlatformAuthorLink import com.futo.platformplayer.api.media.models.contents.IPlatformContent @@ -21,17 +19,22 @@ import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.constructs.Event2 import com.futo.platformplayer.constructs.TaskHandler +import com.futo.platformplayer.dp +import com.futo.platformplayer.getNowDiffSeconds import com.futo.platformplayer.images.GlideHelper.Companion.crossfade import com.futo.platformplayer.images.GlideHelper.Companion.loadThumbnails import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.polycentric.PolycentricCache +import com.futo.platformplayer.selectBestImage import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.states.StateDownloads import com.futo.platformplayer.states.StateHistory -import com.futo.platformplayer.states.StatePlaylists +import com.futo.platformplayer.toHumanNowDiffString +import com.futo.platformplayer.toHumanNumber +import com.futo.platformplayer.toHumanTime import com.futo.platformplayer.video.PlayerManager -import com.futo.platformplayer.views.others.CreatorThumbnail import com.futo.platformplayer.views.FeedStyle +import com.futo.platformplayer.views.others.CreatorThumbnail import com.futo.platformplayer.views.others.ProgressBar import com.futo.platformplayer.views.platform.PlatformIndicator import com.futo.platformplayer.views.video.FutoThumbnailPlayer @@ -88,26 +91,6 @@ open class PreviewVideoView : LinearLayout { inflate(feedStyle); _feedStyle = feedStyle; this.shouldShowTimeBar = shouldShowTimeBar - val playerContainer = findViewById(R.id.player_container); - - val displayMetrics = Resources.getSystem().displayMetrics; - val width: Double = if (feedStyle == FeedStyle.PREVIEW) { - displayMetrics.widthPixels.toDouble(); - } else { - TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 177.0f, displayMetrics).toDouble(); - }; - - /* - val ar = 16.0 / 9.0; - var height = width / ar; - height += TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 6.0f, displayMetrics); - - val layoutParams = playerContainer.layoutParams; - layoutParams.height = height.roundToInt(); - playerContainer.layoutParams = layoutParams;*/ - - //Logger.i(TAG, "Player container height calculated to be $height."); - _playerContainer = findViewById(R.id.player_container); _imageVideo = findViewById(R.id.image_video_thumbnail) @@ -135,6 +118,7 @@ open class PreviewVideoView : LinearLayout { setOnClickListener { onOpenClicked() }; + _imageChannel.setOnClickListener { currentVideo?.let { onChannelClicked.emit(it.author) } }; _textChannelName.setOnClickListener { currentVideo?.let { onChannelClicked.emit(it.author) } }; _textVideoMetadata.setOnClickListener { currentVideo?.let { onChannelClicked.emit(it.author) } }; @@ -212,14 +196,12 @@ open class PreviewVideoView : LinearLayout { metadata += "${content.viewCount.toHumanNumber()} ${context.getString(R.string.views)} • "; } - var timeMeta = ""; - if(isPlanned) { + var timeMeta = if(isPlanned) { val ago = content.datetime?.toHumanNowDiffString(true) ?: "" - timeMeta = context.getString(R.string.available_in) + " ${ago}"; - } - else { + context.getString(R.string.available_in) + " $ago"; + } else { val ago = content.datetime?.toHumanNowDiffString() ?: "" - timeMeta = if (ago.isNotBlank()) ago + " ago" else ago; + if (ago.isNotBlank()) ago + " ago" else ago; } if(content is IPlatformVideo) { diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewVideoViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewVideoViewHolder.kt index 8f998365..27e76c92 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewVideoViewHolder.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewVideoViewHolder.kt @@ -40,7 +40,7 @@ class PreviewVideoViewHolder : ContentPreviewViewHolder { override fun bind(content: IPlatformContent) = view.bind(content); - override fun preview(video: IPlatformContentDetails?, paused: Boolean) = view.preview(video, paused); + override fun preview(details: IPlatformContentDetails?, paused: Boolean) = view.preview(details, paused); override fun stopPreview() = view.stopPreview(); override fun pausePreview() = view.pausePreview(); override fun resumePreview() = view.resumePreview(); diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/CreatorViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/CreatorViewHolder.kt index 8187d21b..750b4fc2 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/CreatorViewHolder.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/CreatorViewHolder.kt @@ -15,10 +15,8 @@ import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.polycentric.PolycentricCache import com.futo.platformplayer.selectBestImage import com.futo.platformplayer.states.StateApp -import com.futo.platformplayer.states.StatePolycentric import com.futo.platformplayer.toHumanNumber import com.futo.platformplayer.views.adapters.AnyAdapter -import com.futo.platformplayer.views.adapters.SubscriptionViewHolder import com.futo.platformplayer.views.others.CreatorThumbnail import com.futo.platformplayer.views.platform.PlatformIndicator import com.futo.platformplayer.views.subscriptions.SubscribeButton @@ -62,31 +60,31 @@ class CreatorViewHolder(private val _viewGroup: ViewGroup, private val _tiny: Bo } } - override fun bind(authorLink: PlatformAuthorLink) { + override fun bind(value: PlatformAuthorLink) { _taskLoadProfile.cancel(); - _creatorThumbnail.setThumbnail(authorLink.thumbnail, false); - _textName.text = authorLink.name; + _creatorThumbnail.setThumbnail(value.thumbnail, false); + _textName.text = value.name; - val cachedProfile = PolycentricCache.instance.getCachedProfile(authorLink.url, true); + val cachedProfile = PolycentricCache.instance.getCachedProfile(value.url, true); if (cachedProfile != null) { onProfileLoaded(cachedProfile, false); if (cachedProfile.expired) { - _taskLoadProfile.run(authorLink.id); + _taskLoadProfile.run(value.id); } } else { - _taskLoadProfile.run(authorLink.id); + _taskLoadProfile.run(value.id); } - if(authorLink.subscribers == null || (authorLink.subscribers ?: 0) <= 0L) + if(value.subscribers == null || (value.subscribers ?: 0) <= 0L) _textMetadata.visibility = View.GONE; else { - _textMetadata.text = if((authorLink.subscribers ?: 0) > 0) authorLink.subscribers!!.toHumanNumber() + " " + _view.context.getString(R.string.subscribers) else ""; + _textMetadata.text = if((value.subscribers ?: 0) > 0) value.subscribers!!.toHumanNumber() + " " + _view.context.getString(R.string.subscribers) else ""; _textMetadata.visibility = View.VISIBLE; } - _buttonSubscribe.setSubscribeChannel(authorLink.url); - _platformIndicator.setPlatformFromClientID(authorLink.id.pluginId); - _authorLink = authorLink; + _buttonSubscribe.setSubscribeChannel(value.url); + _platformIndicator.setPlatformFromClientID(value.id.pluginId); + _authorLink = value; } private fun onProfileLoaded(cachedPolycentricProfile: PolycentricCache.CachedPolycentricProfile?, animate: Boolean) { diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/ImportPlaylistsViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/ImportPlaylistsViewHolder.kt index dc54dd3f..9cbf400c 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/ImportPlaylistsViewHolder.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/ImportPlaylistsViewHolder.kt @@ -9,8 +9,8 @@ import com.bumptech.glide.Glide import com.futo.platformplayer.R import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.models.Playlist -import com.futo.platformplayer.views.others.Checkbox import com.futo.platformplayer.views.adapters.AnyAdapter +import com.futo.platformplayer.views.others.Checkbox class ImportPlaylistsViewHolder(private val _viewGroup: ViewGroup) : AnyAdapter.AnyViewHolder( LayoutInflater.from(_viewGroup.context).inflate(R.layout.list_import_playlist, _viewGroup, false)) { @@ -43,12 +43,12 @@ class ImportPlaylistsViewHolder(private val _viewGroup: ViewGroup) : AnyAdapter. }; } - override fun bind(playlist: SelectablePlaylist) { - _textName.text = playlist.playlist.name; - _textMetadata.text = "${playlist.playlist.videos.size} " + _view.context.getString(R.string.videos); - _checkbox.value = playlist.selected; + override fun bind(value: SelectablePlaylist) { + _textName.text = value.playlist.name; + _textMetadata.text = "${value.playlist.videos.size} " + _view.context.getString(R.string.videos); + _checkbox.value = value.selected; - val thumbnail = playlist.playlist.videos.firstOrNull()?.thumbnails?.getHQThumbnail(); + val thumbnail = value.playlist.videos.firstOrNull()?.thumbnails?.getHQThumbnail(); if (thumbnail != null) Glide.with(_imageThumbnail) .load(thumbnail) @@ -57,7 +57,7 @@ class ImportPlaylistsViewHolder(private val _viewGroup: ViewGroup) : AnyAdapter. else Glide.with(_imageThumbnail).clear(_imageThumbnail); - _playlist = playlist; + _playlist = value; } } diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/ImportSubscriptionViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/ImportSubscriptionViewHolder.kt index 499fd3e2..5992f6b7 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/ImportSubscriptionViewHolder.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/ImportSubscriptionViewHolder.kt @@ -9,9 +9,9 @@ import com.bumptech.glide.Glide import com.futo.platformplayer.R import com.futo.platformplayer.api.media.models.channels.IPlatformChannel import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.views.adapters.AnyAdapter import com.futo.platformplayer.views.others.Checkbox import com.futo.platformplayer.views.platform.PlatformIndicator -import com.futo.platformplayer.views.adapters.AnyAdapter class ImportSubscriptionViewHolder(private val _viewGroup: ViewGroup) : AnyAdapter.AnyViewHolder( LayoutInflater.from(_viewGroup.context).inflate(R.layout.list_import_subscription, _viewGroup, false)) { @@ -46,11 +46,11 @@ class ImportSubscriptionViewHolder(private val _viewGroup: ViewGroup) : AnyAdapt }; } - override fun bind(channel: SelectableIPlatformChannel) { - _textName.text = channel.channel.name; - _checkbox.value = channel.selected; + override fun bind(value: SelectableIPlatformChannel) { + _textName.text = value.channel.name; + _checkbox.value = value.selected; - val thumbnail = channel.channel.thumbnail; + val thumbnail = value.channel.thumbnail; if (thumbnail != null) Glide.with(_imageThumbnail) .load(thumbnail) @@ -59,8 +59,8 @@ class ImportSubscriptionViewHolder(private val _viewGroup: ViewGroup) : AnyAdapt else Glide.with(_imageThumbnail).clear(_imageThumbnail); - _platform.setPlatformFromClientID(channel.channel.id.pluginId); - _channel = channel; + _platform.setPlatformFromClientID(value.channel.id.pluginId); + _channel = value; } } diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/StoreItemViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/StoreItemViewHolder.kt index 47c97acd..3faa153c 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/StoreItemViewHolder.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/StoreItemViewHolder.kt @@ -8,7 +8,6 @@ import android.widget.LinearLayout import android.widget.TextView import com.bumptech.glide.Glide import com.futo.platformplayer.R -import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.images.GlideHelper.Companion.crossfade import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.views.StoreItem @@ -39,14 +38,14 @@ class StoreItemViewHolder(private val _viewGroup: ViewGroup) : AnyAdapter.AnyVie } } - override fun bind(storeItem: StoreItem) { + override fun bind(value: StoreItem) { Glide.with(_image) - .load(storeItem.image) + .load(value.image) .crossfade() .into(_image); - _name.text = storeItem.name; - _storeItem = storeItem; + _name.text = value.name; + _storeItem = value; } companion object { diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/SubscriptionBarViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/SubscriptionBarViewHolder.kt index c8f25f89..d92e60ae 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/SubscriptionBarViewHolder.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/SubscriptionBarViewHolder.kt @@ -4,19 +4,19 @@ import android.view.LayoutInflater import android.view.ViewGroup import android.widget.LinearLayout import android.widget.TextView -import com.futo.platformplayer.logging.Logger -import com.futo.platformplayer.polycentric.PolycentricCache import com.futo.platformplayer.R -import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.api.media.PlatformID import com.futo.platformplayer.api.media.models.channels.SerializedChannel import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.constructs.TaskHandler import com.futo.platformplayer.dp +import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.models.Subscription +import com.futo.platformplayer.polycentric.PolycentricCache import com.futo.platformplayer.selectBestImage -import com.futo.platformplayer.views.others.CreatorThumbnail +import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.views.adapters.AnyAdapter +import com.futo.platformplayer.views.others.CreatorThumbnail import com.futo.polycentric.core.toURLInfoSystemLinkUrl class SubscriptionBarViewHolder(private val _viewGroup: ViewGroup) : AnyAdapter.AnyViewHolder( @@ -46,25 +46,25 @@ class SubscriptionBarViewHolder(private val _viewGroup: ViewGroup) : AnyAdapter. } } - override fun bind(subscription: Subscription) { + override fun bind(value: Subscription) { _taskLoadProfile.cancel(); - _channel = subscription.channel; + _channel = value.channel; - _creatorThumbnail.setThumbnail(subscription.channel.thumbnail, false); - _name.text = subscription.channel.name; + _creatorThumbnail.setThumbnail(value.channel.thumbnail, false); + _name.text = value.channel.name; - val cachedProfile = PolycentricCache.instance.getCachedProfile(subscription.channel.url, true); + val cachedProfile = PolycentricCache.instance.getCachedProfile(value.channel.url, true); if (cachedProfile != null) { onProfileLoaded(cachedProfile, false); if (cachedProfile.expired) { - _taskLoadProfile.run(subscription.channel.id); + _taskLoadProfile.run(value.channel.id); } } else { - _taskLoadProfile.run(subscription.channel.id); + _taskLoadProfile.run(value.channel.id); } - _subscription = subscription; + _subscription = value; } private fun onProfileLoaded(cachedPolycentricProfile: PolycentricCache.CachedPolycentricProfile?, animate: Boolean) { diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/TabViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/TabViewHolder.kt index 262311cf..53ec72fc 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/TabViewHolder.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/TabViewHolder.kt @@ -1,5 +1,6 @@ package com.futo.platformplayer.views.adapters.viewholders +import android.annotation.SuppressLint import android.view.LayoutInflater import android.view.MotionEvent import android.view.View @@ -7,16 +8,17 @@ import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView import androidx.recyclerview.widget.RecyclerView.ViewHolder -import com.futo.platformplayer.* +import com.futo.platformplayer.R import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.fragment.mainactivity.bottombar.MenuBottomBarFragment -import com.futo.platformplayer.views.others.Toggle import com.futo.platformplayer.views.adapters.AnyAdapter +import com.futo.platformplayer.views.others.Toggle data class TabViewHolderData(val buttonDefinition: MenuBottomBarFragment.ButtonDefinition, var enabled: Boolean); -class TabViewHolder(_viewGroup: ViewGroup) : AnyAdapter.AnyViewHolder( - LayoutInflater.from(_viewGroup.context).inflate(R.layout.list_tab, _viewGroup, false)) { +@SuppressLint("ClickableViewAccessibility") +class TabViewHolder(viewGroup: ViewGroup) : AnyAdapter.AnyViewHolder( + LayoutInflater.from(viewGroup.context).inflate(R.layout.list_tab, viewGroup, false)) { var data: TabViewHolderData? = null; private val _imageDragDrop: ImageView = _view.findViewById(R.id.image_drag_drop); @@ -41,18 +43,18 @@ class TabViewHolder(_viewGroup: ViewGroup) : AnyAdapter.AnyViewHolder + _imageDragDrop.setOnTouchListener { _, event -> if (event.action == MotionEvent.ACTION_DOWN) { onDragDrop.emit(this); } false - }); + }; } - override fun bind(i: TabViewHolderData) { - _textTabName.text = _view.context.resources.getString(i.buttonDefinition.string); - _toggleTab.visibility = if (i.buttonDefinition.canToggle) View.VISIBLE else View.GONE; - _toggleTab.setValue(i.enabled, false); - data = i; + override fun bind(value: TabViewHolderData) { + _textTabName.text = _view.context.resources.getString(value.buttonDefinition.string); + _toggleTab.visibility = if (value.buttonDefinition.canToggle) View.VISIBLE else View.GONE; + _toggleTab.setValue(value.enabled, false); + data = value; } } \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/VideoDownloadViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/VideoDownloadViewHolder.kt index a8d58d5b..ae4e6ec9 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/VideoDownloadViewHolder.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/VideoDownloadViewHolder.kt @@ -1,6 +1,6 @@ package com.futo.platformplayer.views.adapters.viewholders -import android.app.Activity +import android.annotation.SuppressLint import android.view.LayoutInflater import android.view.ViewGroup import android.widget.ImageButton @@ -65,26 +65,26 @@ class VideoDownloadViewHolder(_viewGroup: ViewGroup) : AnyAdapter.AnyViewHolder< } } - override fun bind(video: VideoLocal) { - _video = video; - _videoName.text = video.name; - _videoDuration.text = video.duration.toHumanTime(false); - _videoAuthor.text = video.author.name; - _videoSize.text = (video.videoSource.sumOf { it.fileSize } + video.audioSource.sumOf { it.fileSize }).toHumanBytesSize(false); + @SuppressLint("SetTextI18n") + override fun bind(value: VideoLocal) { + _video = value; + _videoName.text = value.name; + _videoDuration.text = value.duration.toHumanTime(false); + _videoAuthor.text = value.author.name; + _videoSize.text = (value.videoSource.sumOf { it.fileSize } + value.audioSource.sumOf { it.fileSize }).toHumanBytesSize(false); val tokens = arrayListOf(); - - if(video.videoSource.isNotEmpty()) { - tokens.add(video.videoSource.maxBy { it.width * it.height }.let { "${it.width}x${it.height} (${it.container})" }); + if(value.videoSource.isNotEmpty()) { + tokens.add(value.videoSource.maxBy { it.width * it.height }.let { "${it.width}x${it.height} (${it.container})" }); } - if (video.audioSource.isNotEmpty()) { - tokens.add(video.audioSource.maxBy { it.bitrate }.let { it.bitrate.toHumanBitrate() }); + if (value.audioSource.isNotEmpty()) { + tokens.add(value.audioSource.maxBy { it.bitrate }.let { it.bitrate.toHumanBitrate() }); } - _videoInfo.text =tokens.joinToString(" • "); + _videoInfo.text = tokens.joinToString(" • "); - _videoImage.loadThumbnails(video.thumbnails, true) { + _videoImage.loadThumbnails(value.thumbnails, true) { it.placeholder(R.drawable.placeholder_video_thumbnail) .into(_videoImage); }; diff --git a/app/src/main/java/com/futo/platformplayer/views/announcements/AnnouncementView.kt b/app/src/main/java/com/futo/platformplayer/views/announcements/AnnouncementView.kt index d1b75a5b..7767265f 100644 --- a/app/src/main/java/com/futo/platformplayer/views/announcements/AnnouncementView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/announcements/AnnouncementView.kt @@ -3,7 +3,9 @@ package com.futo.platformplayer.views.announcements import android.content.Context import android.util.AttributeSet import android.view.View -import android.widget.* +import android.widget.FrameLayout +import android.widget.LinearLayout +import android.widget.TextView import androidx.constraintlayout.widget.ConstraintLayout import androidx.lifecycle.findViewTreeLifecycleOwner import androidx.lifecycle.lifecycleScope @@ -55,7 +57,6 @@ class AnnouncementView : LinearLayout { _buttonAction.setOnClickListener { val a = _currentAnnouncement ?: return@setOnClickListener; - val scope = StateApp.instance.scopeOrNull ?: return@setOnClickListener; StateAnnouncement.instance.actionAnnouncement(a); }; @@ -73,6 +74,7 @@ class AnnouncementView : LinearLayout { val attrArr = context.obtainStyledAttributes(attrs, R.styleable.AnnouncementView, 0, 0); _category = attrArr.getText(R.styleable.AnnouncementView_category)?.toString(); + attrArr.recycle() refresh(); } diff --git a/app/src/main/java/com/futo/platformplayer/views/buttons/BigButton.kt b/app/src/main/java/com/futo/platformplayer/views/buttons/BigButton.kt index 5db79617..f3bbc7b8 100644 --- a/app/src/main/java/com/futo/platformplayer/views/buttons/BigButton.kt +++ b/app/src/main/java/com/futo/platformplayer/views/buttons/BigButton.kt @@ -64,15 +64,14 @@ open class BigButton : LinearLayout { val attrArr = context.obtainStyledAttributes(attrs, R.styleable.BigButton, 0, 0); val attrIconRef = attrArr.getResourceId(R.styleable.BigButton_buttonIcon, -1); - withIcon(attrIconRef); - val attrBackgroundRef = attrArr.getResourceId(R.styleable.BigButton_buttonBackground, -1); - withBackground(attrBackgroundRef); - val attrText = attrArr.getText(R.styleable.BigButton_buttonText) ?: ""; - _textPrimary.text = attrText; - val attrTextSecondary = attrArr.getText(R.styleable.BigButton_buttonSubText) ?: ""; + attrArr.recycle() + + withIcon(attrIconRef); + withBackground(attrBackgroundRef); + _textPrimary.text = attrText; _textSecondary.text = attrTextSecondary; } @@ -123,11 +122,8 @@ open class BigButton : LinearLayout { fun withIcon(bitmap: Bitmap, rounded: Boolean = false): BigButton { - if (bitmap != null) { - _icon.visibility = View.VISIBLE; - _icon.setImageBitmap(bitmap); - } else - _icon.visibility = View.GONE; + _icon.visibility = View.VISIBLE; + _icon.setImageBitmap(bitmap); if (rounded) { val shapeAppearanceModel = ShapeAppearanceModel().toBuilder() diff --git a/app/src/main/java/com/futo/platformplayer/views/buttons/DescButton.kt b/app/src/main/java/com/futo/platformplayer/views/buttons/DescButton.kt index f5e13eae..81fc89d5 100644 --- a/app/src/main/java/com/futo/platformplayer/views/buttons/DescButton.kt +++ b/app/src/main/java/com/futo/platformplayer/views/buttons/DescButton.kt @@ -1,16 +1,12 @@ package com.futo.platformplayer.views.buttons import android.content.Context -import android.graphics.Color import android.util.AttributeSet -import android.view.LayoutInflater -import android.widget.FrameLayout import android.widget.ImageView import android.widget.LinearLayout import android.widget.TextView import com.futo.platformplayer.R import com.futo.platformplayer.constructs.Event0 -import com.futo.platformplayer.constructs.Event1 class DescButton : LinearLayout { @@ -31,6 +27,7 @@ class DescButton : LinearLayout { imageIcon.setImageResource(attrArr.getResourceId(R.styleable.DescButton_desc_icon, 0)) textTitle.text = attrArr.getText(R.styleable.DescButton_desc_title) ?: ""; textDescription.text = attrArr.getText(R.styleable.DescButton_desc_description) ?: ""; + attrArr.recycle() this.setOnClickListener { onClick.emit() } } @@ -40,8 +37,8 @@ class DescButton : LinearLayout { textDescription = findViewById(R.id.text_description) imageIcon.setImageResource(icon); - textTitle.text = title ?: ""; - textDescription.text = description ?: ""; + textTitle.text = title; + textDescription.text = description; this.setOnClickListener { onClick.emit() } } diff --git a/app/src/main/java/com/futo/platformplayer/views/casting/CastView.kt b/app/src/main/java/com/futo/platformplayer/views/casting/CastView.kt index f322eaeb..535520a7 100644 --- a/app/src/main/java/com/futo/platformplayer/views/casting/CastView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/casting/CastView.kt @@ -1,3 +1,5 @@ +@file:Suppress("DEPRECATION") + package com.futo.platformplayer.views.casting import android.content.Context @@ -7,20 +9,29 @@ import android.util.AttributeSet import android.util.TypedValue import android.view.LayoutInflater import android.view.View -import android.widget.* +import android.widget.FrameLayout +import android.widget.ImageButton +import android.widget.ImageView +import android.widget.TextView import androidx.constraintlayout.widget.ConstraintLayout import com.bumptech.glide.Glide -import com.futo.platformplayer.* +import com.futo.platformplayer.R import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails import com.futo.platformplayer.casting.AirPlayCastingDevice import com.futo.platformplayer.casting.StateCasting import com.futo.platformplayer.constructs.Event0 import com.futo.platformplayer.states.StatePlayer +import com.futo.platformplayer.toHumanTime import com.futo.platformplayer.views.behavior.GestureControlView import com.google.android.exoplayer2.ui.DefaultTimeBar import com.google.android.exoplayer2.ui.TimeBar import com.google.android.exoplayer2.ui.TimeBar.OnScrubListener -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.cancel +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch class CastView : ConstraintLayout { private val _thumbnail: ImageView; @@ -39,7 +50,6 @@ class CastView : ConstraintLayout { private var _scope: CoroutineScope = CoroutineScope(Dispatchers.Main); private var _updateTimeJob: Job? = null; private var _inPictureInPicture: Boolean = false; - private var _originalBottomMargin: Int = 0; val onMinimizeClick = Event0(); val onSettingsClick = Event0(); diff --git a/app/src/main/java/com/futo/platformplayer/views/fields/DropdownField.kt b/app/src/main/java/com/futo/platformplayer/views/fields/DropdownField.kt index f2f95d1a..590335d3 100644 --- a/app/src/main/java/com/futo/platformplayer/views/fields/DropdownField.kt +++ b/app/src/main/java/com/futo/platformplayer/views/fields/DropdownField.kt @@ -3,9 +3,12 @@ package com.futo.platformplayer.views.fields import android.content.Context import android.util.AttributeSet import android.view.View -import android.widget.* +import android.widget.AdapterView +import android.widget.ArrayAdapter +import android.widget.Spinner +import android.widget.TableRow +import android.widget.TextView import com.futo.platformplayer.R -import com.futo.platformplayer.constructs.Event2 import com.futo.platformplayer.constructs.Event3 import com.futo.platformplayer.logging.Logger import java.lang.reflect.Field @@ -136,20 +139,19 @@ class DropdownField : TableRow, IField { arrayOf("Unset")) .toList().toTypedArray(); - if(_options != null){ - _spinner.adapter = ArrayAdapter(context, R.layout.spinner_item_simple, _options).also { - it.setDropDownViewResource(R.layout.spinner_dropdownitem_simple); - }; + _spinner.adapter = ArrayAdapter(context, R.layout.spinner_item_simple, _options).also { + it.setDropDownViewResource(R.layout.spinner_dropdownitem_simple); + }; - if(field.type == Int::class.java) - _selected = field.get(obj) as Int; - else { - val valStr = field.get(obj)?.toString(); - _selected = if (_options.contains(valStr)) _options.indexOf(valStr) else 0; - } - _spinner.isSelected = false; - _spinner.setSelection(_selected, true); + _selected = if(field.type == Int::class.java) { + field.get(obj) as Int; + } else { + val valStr = field.get(obj)?.toString(); + if (_options.contains(valStr)) _options.indexOf(valStr) else 0; } + + _spinner.isSelected = false; + _spinner.setSelection(_selected, true); return this; } override fun setField() { diff --git a/app/src/main/java/com/futo/platformplayer/views/fields/FieldForm.kt b/app/src/main/java/com/futo/platformplayer/views/fields/FieldForm.kt index 24ebf38e..e5b73757 100644 --- a/app/src/main/java/com/futo/platformplayer/views/fields/FieldForm.kt +++ b/app/src/main/java/com/futo/platformplayer/views/fields/FieldForm.kt @@ -50,15 +50,14 @@ class FieldForm : LinearLayout { fun updateSettingsVisibility(group: GroupField? = null) { val settings = group?.getFields() ?: _fields; - val query = _editSearch.text.toString().lowercase(); var groupVisible = false; val isGroupMatch = query.isEmpty() || group?.searchContent?.lowercase()?.contains(query) == true; for(field in settings) { - if(field is GroupField) + if(field is GroupField) { updateSettingsVisibility(field); - else if(field is View && field.descriptor != null) { + } else if(field is View && field.descriptor != null) { val txt = field.searchContent?.lowercase(); if(txt != null) { val visible = isGroupMatch || txt.contains(query); @@ -67,8 +66,9 @@ class FieldForm : LinearLayout { } } } - if(group != null) - group.visibility = if(groupVisible) View.VISIBLE else View.GONE; + if(group != null) { + group.visibility = if (groupVisible) View.VISIBLE else View.GONE; + } } fun setSearchVisible(visible: Boolean) { @@ -84,11 +84,12 @@ class FieldForm : LinearLayout { withContext(Dispatchers.Main) { for (field in newFields) { - if (field !is View) + if (field !is View) { throw java.lang.IllegalStateException("Only views can be IFields"); + } _fieldsContainer.addView(field as View); - field.onChanged.subscribe { a1, a2, oldValue -> + field.onChanged.subscribe { a1, a2, _ -> onChanged.emit(a1, a2); }; } @@ -102,11 +103,12 @@ class FieldForm : LinearLayout { _fieldsContainer.removeAllViews(); val newFields = getFieldsFromObject(context, obj); for(field in newFields) { - if(field !is View) + if(field !is View) { throw java.lang.IllegalStateException("Only views can be IFields"); + } _fieldsContainer.addView(field as View); - field.onChanged.subscribe { a1, a2, oldValue -> + field.onChanged.subscribe { a1, a2, _ -> onChanged.emit(a1, a2); }; } @@ -121,10 +123,13 @@ class FieldForm : LinearLayout { if(groupTitle == null) { for(field in newFields) { - if(field.second !is View) + val v = field.second + if(v !is View) { throw java.lang.IllegalStateException("Only views can be IFields"); - finalizePluginSettingField(field.first, field.second, newFields); - _fieldsContainer.addView(field as View); + } + + finalizePluginSettingField(field.first, v, newFields); + _fieldsContainer.addView(v); } _fields = newFields.map { it.second }; } else { @@ -137,34 +142,36 @@ class FieldForm : LinearLayout { } } private fun finalizePluginSettingField(setting: SourcePluginConfig.Setting, field: IField, others: List>) { - field.onChanged.subscribe { field, value, oldValue -> - onChanged.emit(field, value); + field.onChanged.subscribe { f, value, oldValue -> + onChanged.emit(f, value); setting.warningDialog?.let { - if(it.isNotBlank() && IField.isValueTrue(value)) + if(it.isNotBlank() && IField.isValueTrue(value)) { UIDialogs.showDialog(context, R.drawable.ic_warning_yellow, setting.warningDialog, null, null, 0, UIDialogs.Action("Cancel", { - field.setValue(oldValue); + f.setValue(oldValue); }, UIDialogs.ActionStyle.NONE), - UIDialogs.Action("Ok", { - - }, UIDialogs.ActionStyle.PRIMARY)); + UIDialogs.Action("Ok", { }, UIDialogs.ActionStyle.PRIMARY)); + } } } if(setting.dependency != null) { val dependentField = others.firstOrNull { it.first.variableOrName == setting.dependency }; - if(dependentField == null || dependentField.second !is View) + if (dependentField == null || dependentField.second !is View) { (field as View).visibility = View.GONE; - else { + } else { val dependencyReady = IField.isValueTrue(dependentField.second.value); - if(!dependencyReady) + if (!dependencyReady) { (field as View).visibility = View.GONE; - dependentField.second.onChanged.subscribe { dependentField, value, oldValue -> + } + + dependentField.second.onChanged.subscribe { _, value, _ -> val isValid = IField.isValueTrue(value); - if(isValid) + if (isValid) { (field as View).visibility = View.VISIBLE; - else + } else { (field as View).visibility = View.GONE; + } } } } @@ -172,19 +179,20 @@ class FieldForm : LinearLayout { fun setObjectValues(){ val fields = _fields; - for (field in fields) + for (field in fields) { field.setField(); + } } fun findField(id: String) : IField? { for(field in _fields) { - if(field?.descriptor?.id == id) + if(field.descriptor?.id == id) { return field; - else if(field is GroupField) - { + } else if(field is GroupField) { val subField = field.findField(id); - if(subField != null) + if(subField != null) { return subField; + } } } return null; @@ -198,7 +206,7 @@ class FieldForm : LinearLayout { const val TOGGLE = "toggle"; const val BUTTON = "button"; - private val _json = Json {}; + private val _json = Json; fun getFieldsFromPluginSettings(context: Context, settings: List, values: HashMap): List> { @@ -216,17 +224,17 @@ class FieldForm : LinearLayout { val field = ToggleField(context).withValue(setting.name, setting.description, value == "true" || value == "1" || value == "True"); - field.onChanged.subscribe { field, value, oldValue -> - values[setting.variableOrName] = _json.encodeToString (value == 1 || value == true); + field.onChanged.subscribe { _, v, _ -> + values[setting.variableOrName] = _json.encodeToString (v == 1 || v == true); } field; } "dropdown" -> { - if(setting.options != null && !setting.options.isEmpty()) { + if(!setting.options.isNullOrEmpty()) { var selected = value?.toIntOrNull()?.coerceAtLeast(0) ?: 0; val field = DropdownField(context).withValue(setting.name, setting.description, setting.options, selected); - field.onChanged.subscribe { field, value, oldValue -> - values[setting.variableOrName] = value.toString(); + field.onChanged.subscribe { _, v, _ -> + values[setting.variableOrName] = v.toString(); } field; } @@ -235,8 +243,9 @@ class FieldForm : LinearLayout { else -> null; } - if(field != null) + if(field != null) { fields.add(Pair(setting, field)); + } } return fields; } @@ -269,11 +278,11 @@ class FieldForm : LinearLayout { if(field.field != null) { val warning = propertyMap[field.field]?.findAnnotation(); if(warning != null) { - field.onChanged.subscribe { field, value, oldValue -> + field.onChanged.subscribe { f, value, oldValue -> if(IField.isValueTrue(value)) UIDialogs.showDialog(context, R.drawable.ic_warning_yellow, context.getString(warning.messageRes), null, null, 0, UIDialogs.Action("Cancel", { - field.setValue(oldValue); + f.setValue(oldValue); }, UIDialogs.ActionStyle.NONE), UIDialogs.Action("Ok", { @@ -304,14 +313,18 @@ class FieldForm : LinearLayout { .asSequence() .asStream() .filter { it.getAnnotation(FormField::class.java) != null && !it.name.startsWith("get") && !it.name.startsWith("set") } - .map { Pair(it, it.getAnnotation(FormField::class.java)) } + .map { Pair(it, it.getAnnotation(FormField::class.java)) } for(meth in objMethods) { + if (meth.second == null) { + continue + } + meth.first.isAccessible = true; - val field = when(meth.second.type) { + val field = when(meth.second!!.type) { BUTTON -> ButtonField(context).fromMethod(obj, meth.first); - else -> throw java.lang.IllegalStateException("Unknown method type ${meth.second.type} for ${meth.second.title}") + else -> throw java.lang.IllegalStateException("Unknown method type ${meth.second!!.type} for ${meth.second!!.title}") } fields.add(field as IField); } diff --git a/app/src/main/java/com/futo/platformplayer/views/fields/GroupField.kt b/app/src/main/java/com/futo/platformplayer/views/fields/GroupField.kt index 6ea48ee7..133fb788 100644 --- a/app/src/main/java/com/futo/platformplayer/views/fields/GroupField.kt +++ b/app/src/main/java/com/futo/platformplayer/views/fields/GroupField.kt @@ -6,10 +6,8 @@ import android.view.View import android.widget.LinearLayout import android.widget.TextView import com.futo.platformplayer.R -import com.futo.platformplayer.constructs.Event2 import com.futo.platformplayer.constructs.Event3 import java.lang.reflect.Field -import kotlin.reflect.KProperty class GroupField : LinearLayout, IField { override var descriptor : FormField? = null; @@ -39,7 +37,7 @@ class GroupField : LinearLayout, IField { override val value: Any? = null; - override val searchContent: String? get() = "${_title.text} ${_subtitle.text}"; + override val searchContent: String get() = "${_title.text} ${_subtitle.text}"; constructor(context : Context, attrs : AttributeSet? = null) : super(context, attrs) { inflate(context, R.layout.field_group, this); @@ -59,25 +57,28 @@ class GroupField : LinearLayout, IField { _title.text = title; _subtitle.text = description ?: ""; - if(!(_title.text?.isEmpty() ?: true)) + if(_title.text?.isEmpty() == false) { _title.visibility = VISIBLE; - else + } else { _title.visibility = GONE; - if(!(_subtitle.text?.isEmpty() ?: true)) + } + + if (_subtitle.text?.isEmpty() == false) { _subtitle.visibility = VISIBLE; - else + } else { _subtitle.visibility = GONE; + } } fun findField(id: String) : IField? { for(field in _fields) { - if(field.descriptor?.id == id) + if(field.descriptor?.id == id) { return field; - else if(field is GroupField) - { + } else if(field is GroupField) { val subField = field.findField(id); - if(subField != null) + if(subField != null) { return subField; + } } } return null; @@ -87,7 +88,7 @@ class GroupField : LinearLayout, IField { _container.removeAllViews(); val newFields = mutableListOf() for(field in fields) { - if(!(field is View)) + if(field !is View) throw java.lang.IllegalStateException("Only views can be IFields"); field.onChanged.subscribe(onChanged::emit); @@ -99,7 +100,7 @@ class GroupField : LinearLayout, IField { return this; } - override fun fromField(obj : Any, field : Field, formField: FormField?) : GroupField { + override fun fromField(obj: Any, field: Field, formField: FormField?) : GroupField { this._field = field; this._obj = obj; @@ -116,21 +117,23 @@ class GroupField : LinearLayout, IField { _container.removeAllViews(); val newFields = mutableListOf() - for(field in FieldForm.getFieldsFromObject(context, value)) { - if(!(field is View)) - throw java.lang.IllegalStateException("Only views can be IFields"); + if (value != null) { + for (f in FieldForm.getFieldsFromObject(context, value)) { + if (f !is View) + throw java.lang.IllegalStateException("Only views can be IFields"); - field.onChanged.subscribe(onChanged::emit); - _container.addView(field as View); - newFields.add(field); + f.onChanged.subscribe(onChanged::emit); + _container.addView(f as View); + newFields.add(f); + } } _fields = newFields; - if(!(_title.text?.isEmpty() ?: true)) + if(_title.text?.isEmpty() == false) _title.visibility = VISIBLE; else _title.visibility = GONE; - if(!(_subtitle.text?.isEmpty() ?: true)) + if(_subtitle.text?.isEmpty() == false) _subtitle.visibility = VISIBLE; else _subtitle.visibility = GONE; diff --git a/app/src/main/java/com/futo/platformplayer/views/fields/ReadOnlyTextField.kt b/app/src/main/java/com/futo/platformplayer/views/fields/ReadOnlyTextField.kt index 7b0244dc..d0cfb7dc 100644 --- a/app/src/main/java/com/futo/platformplayer/views/fields/ReadOnlyTextField.kt +++ b/app/src/main/java/com/futo/platformplayer/views/fields/ReadOnlyTextField.kt @@ -2,9 +2,9 @@ package com.futo.platformplayer.views.fields import android.content.Context import android.util.AttributeSet -import android.widget.* +import android.widget.TableRow +import android.widget.TextView import com.futo.platformplayer.R -import com.futo.platformplayer.constructs.Event2 import com.futo.platformplayer.constructs.Event3 import java.lang.reflect.Field import java.lang.reflect.Method @@ -34,7 +34,7 @@ class ReadOnlyTextField : TableRow, IField { override val value: Any? = null; - override val searchContent: String? + override val searchContent: String get() = "${_title.text}"; constructor(context : Context, attrs : AttributeSet? = null) : super(context, attrs){ @@ -50,17 +50,19 @@ class ReadOnlyTextField : TableRow, IField { this._obj = obj; val attrField = formField ?: field.getAnnotation(FormField::class.java); - if(attrField != null) { + if (attrField != null) { _title.text = context.getString(attrField.title); descriptor = attrField; - } - else + } else { _title.text = field.name; + } - if(field.type == String::class.java) + if (field.type == String::class.java) { _value.text = field.get(obj) as String; - else - _value.text = field.get(obj).toString(); + } else { + _value.text = field.get(obj)?.toString() ?: ""; + } + return this; } fun fromProp(obj : Any, field : Method, formField: FormField?) : ReadOnlyTextField { @@ -68,17 +70,19 @@ class ReadOnlyTextField : TableRow, IField { this._obj = obj; val attrField = formField ?: field.getAnnotation(FormField::class.java); - if(attrField != null) { + if (attrField != null) { _title.text = context.getString(attrField.title); descriptor = attrField; - } - else + } else { _title.text = field.name; + } - if(field.returnType == String::class.java) + if (field.returnType == String::class.java) { _value.text = field.invoke(obj) as String; - else + } else { _value.text = field.invoke(obj)?.toString() ?: ""; + } + return this; } override fun setField() { diff --git a/app/src/main/java/com/futo/platformplayer/views/fields/ToggleField.kt b/app/src/main/java/com/futo/platformplayer/views/fields/ToggleField.kt index 2bbbbb21..8e1cfbbb 100644 --- a/app/src/main/java/com/futo/platformplayer/views/fields/ToggleField.kt +++ b/app/src/main/java/com/futo/platformplayer/views/fields/ToggleField.kt @@ -3,9 +3,9 @@ package com.futo.platformplayer.views.fields import android.content.Context import android.util.AttributeSet import android.view.View -import android.widget.* +import android.widget.TableRow +import android.widget.TextView import com.futo.platformplayer.R -import com.futo.platformplayer.constructs.Event2 import com.futo.platformplayer.constructs.Event3 import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.views.others.Toggle @@ -94,15 +94,13 @@ class ToggleField : TableRow, IField { _description.visibility = View.VISIBLE; } - val value = field.get(obj); - val toggleValue = if(value is Boolean) - value; - else if(value is Number) - (value as Number).toInt() > 0; - else if(value == null) - false; - else - false; + val toggleValue = when (val value = field.get(obj)) { + is Boolean -> value + is Number -> value.toInt() > 0 + null -> false + else -> false + }; + _toggle.setValue(toggleValue, true); _lastValue = toggleValue; diff --git a/app/src/main/java/com/futo/platformplayer/views/livechat/LiveChatListAdapter.kt b/app/src/main/java/com/futo/platformplayer/views/livechat/LiveChatListAdapter.kt index eecc6d7e..13d1c18e 100644 --- a/app/src/main/java/com/futo/platformplayer/views/livechat/LiveChatListAdapter.kt +++ b/app/src/main/java/com/futo/platformplayer/views/livechat/LiveChatListAdapter.kt @@ -1,15 +1,10 @@ package com.futo.platformplayer.views.livechat -import android.content.Context -import android.view.* +import android.view.ViewGroup import android.widget.LinearLayout import androidx.recyclerview.widget.RecyclerView -import com.futo.platformplayer.api.media.models.contents.ContentType -import com.futo.platformplayer.api.media.models.contents.IPlatformContent import com.futo.platformplayer.api.media.models.live.LiveEventComment import com.futo.platformplayer.api.media.models.live.LiveEventDonation -import com.futo.platformplayer.views.adapters.ContentPreviewViewHolder -import com.futo.platformplayer.views.adapters.EmptyPreviewViewHolder import com.futo.platformplayer.views.overlays.LiveChatOverlay class LiveChatListAdapter : RecyclerView.Adapter { @@ -17,7 +12,7 @@ class LiveChatListAdapter : RecyclerView.Adapter { private val _dataSet: ArrayList; - constructor(context: Context, dataSet: ArrayList): super() { + constructor(dataSet: ArrayList): super() { this._dataSet = dataSet; } @@ -28,13 +23,11 @@ class LiveChatListAdapter : RecyclerView.Adapter { } val item = _dataSet.getOrNull(position) ?: return -1; - if(item.event is LiveEventComment) - return 1; - else if(item.event is LiveEventDonation) - return 2; - else - return -1; - + return when (item.event) { + is LiveEventComment -> 1 + is LiveEventDonation -> 2 + else -> -1 + }; } override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): LiveChatListItem { diff --git a/app/src/main/java/com/futo/platformplayer/views/livechat/LiveChatListItem.kt b/app/src/main/java/com/futo/platformplayer/views/livechat/LiveChatListItem.kt index 88f5a68a..bc712b16 100644 --- a/app/src/main/java/com/futo/platformplayer/views/livechat/LiveChatListItem.kt +++ b/app/src/main/java/com/futo/platformplayer/views/livechat/LiveChatListItem.kt @@ -1,23 +1,8 @@ package com.futo.platformplayer.views.livechat -import android.graphics.Color -import android.graphics.drawable.LevelListDrawable -import android.text.Spannable -import android.text.style.ImageSpan -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup -import android.widget.ImageView -import android.widget.TextView import androidx.recyclerview.widget.RecyclerView -import com.bumptech.glide.Glide -import com.futo.platformplayer.R -import com.futo.platformplayer.api.media.models.live.LiveEventComment -import com.futo.platformplayer.dp -import com.futo.platformplayer.views.adapters.AnyAdapter import com.futo.platformplayer.views.overlays.LiveChatOverlay -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch abstract class LiveChatListItem(view: View): RecyclerView.ViewHolder(view) { protected val _view = view; diff --git a/app/src/main/java/com/futo/platformplayer/views/others/RadioView.kt b/app/src/main/java/com/futo/platformplayer/views/others/RadioView.kt index 22ff167e..13a865a2 100644 --- a/app/src/main/java/com/futo/platformplayer/views/others/RadioView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/others/RadioView.kt @@ -6,6 +6,7 @@ import android.view.LayoutInflater import android.widget.FrameLayout import android.widget.LinearLayout import android.widget.TextView +import androidx.core.content.ContextCompat import com.futo.platformplayer.R import com.futo.platformplayer.constructs.Event0 import com.futo.platformplayer.constructs.Event1 @@ -33,7 +34,7 @@ class RadioView : LinearLayout { }; _root.setBackgroundResource(R.drawable.background_radio_unselected); - _textTag.setTextColor(resources.getColor(R.color.gray_67)); + _textTag.setTextColor(ContextCompat.getColor(context, R.color.gray_67)); } fun setInfo(text: String, selected: Boolean) { @@ -50,7 +51,7 @@ class RadioView : LinearLayout { _selected = selected; _root.setBackgroundResource(if (selected) R.drawable.background_radio_selected else R.drawable.background_radio_unselected); - _textTag.setTextColor(resources.getColor(if (selected) R.color.white else R.color.gray_67)); + _textTag.setTextColor(ContextCompat.getColor(context, if (selected) R.color.white else R.color.gray_67)); onSelectedChange.emit(_selected); } diff --git a/app/src/main/java/com/futo/platformplayer/views/overlays/LiveChatOverlay.kt b/app/src/main/java/com/futo/platformplayer/views/overlays/LiveChatOverlay.kt index 385be76d..1e6160df 100644 --- a/app/src/main/java/com/futo/platformplayer/views/overlays/LiveChatOverlay.kt +++ b/app/src/main/java/com/futo/platformplayer/views/overlays/LiveChatOverlay.kt @@ -7,8 +7,6 @@ import android.graphics.PointF import android.util.AttributeSet import android.util.DisplayMetrics import android.view.View -import android.webkit.CookieManager -import android.webkit.ValueCallback import android.webkit.WebView import android.webkit.WebViewClient import android.widget.Button @@ -24,7 +22,6 @@ import androidx.recyclerview.widget.LinearSmoothScroller import androidx.recyclerview.widget.RecyclerView import com.bumptech.glide.Glide import com.futo.platformplayer.R -import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.api.media.LiveChatManager import com.futo.platformplayer.api.media.models.live.ILiveChatWindowDescriptor import com.futo.platformplayer.api.media.models.live.ILiveEventChatMessage @@ -38,17 +35,11 @@ import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.dp import com.futo.platformplayer.isHexColor import com.futo.platformplayer.logging.Logger -import com.futo.platformplayer.toHumanBitrate import com.futo.platformplayer.toHumanNumber -import com.futo.platformplayer.views.AnyAdapterView -import com.futo.platformplayer.views.AnyAdapterView.Companion.asAny import com.futo.platformplayer.views.livechat.LiveChatDonationPill import com.futo.platformplayer.views.livechat.LiveChatListAdapter -import com.futo.platformplayer.views.livechat.LiveChatMessageListItem -import com.stripe.android.core.utils.encodeToJson import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json @@ -123,8 +114,7 @@ class LiveChatOverlay : LinearLayout { _chatContainer = findViewById(R.id.chatContainer); _chatLayoutManager = ChatLayoutManager(context); - //_chatAdapter = _chatContainer.asAny(_chats); - _chatAdapter = LiveChatListAdapter(context, _chats); + _chatAdapter = LiveChatListAdapter(_chats); _chatContainer.adapter = _chatAdapter; _chatContainer.layoutManager = _chatLayoutManager; diff --git a/app/src/main/java/com/futo/platformplayer/views/overlays/SupportOverlay.kt b/app/src/main/java/com/futo/platformplayer/views/overlays/SupportOverlay.kt index d43bddd4..36034d43 100644 --- a/app/src/main/java/com/futo/platformplayer/views/overlays/SupportOverlay.kt +++ b/app/src/main/java/com/futo/platformplayer/views/overlays/SupportOverlay.kt @@ -23,8 +23,8 @@ class SupportOverlay : LinearLayout { } - fun setPolycentricProfile(profile: PolycentricProfile?, animate: Boolean) { - _support.setPolycentricProfile(profile, animate) + fun setPolycentricProfile(profile: PolycentricProfile?) { + _support.setPolycentricProfile(profile) } fun cleanup() { diff --git a/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuGroup.kt b/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuGroup.kt index 32e36027..9f19018d 100644 --- a/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuGroup.kt +++ b/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuGroup.kt @@ -61,7 +61,7 @@ class SlideUpMenuGroup : LinearLayout { return didSelect; } - private fun addItems(items: List) { + private fun addItems(items: List) { for (item in items) { item.setParentClickListener { parentClickListener?.invoke() } itemContainer.addView(item); diff --git a/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuOverlay.kt b/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuOverlay.kt index 2c34dc5e..157c13a6 100644 --- a/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuOverlay.kt +++ b/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuOverlay.kt @@ -17,7 +17,6 @@ import com.futo.platformplayer.R import com.futo.platformplayer.constructs.Event0 class SlideUpMenuOverlay : RelativeLayout { - private var _container: ViewGroup? = null; private lateinit var _textTitle: TextView; private lateinit var _textCancel: TextView; @@ -27,7 +26,7 @@ class SlideUpMenuOverlay : RelativeLayout { private lateinit var _viewContainer: LinearLayout; private var _animated: Boolean = true; - private lateinit var _groupItems: List; + private var _groupItems: List; var isVisible = false private set; diff --git a/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuTitle.kt b/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuTitle.kt index 25135cfa..a084aa8d 100644 --- a/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuTitle.kt +++ b/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuTitle.kt @@ -2,15 +2,10 @@ package com.futo.platformplayer.views.overlays.slideup import android.content.Context import android.util.AttributeSet -import android.util.TypedValue -import android.view.Gravity import android.view.LayoutInflater import android.widget.LinearLayout import android.widget.TextView -import androidx.core.content.ContextCompat -import androidx.core.content.res.ResourcesCompat import com.futo.platformplayer.R -import com.futo.platformplayer.constructs.Event1 class SlideUpMenuTitle : LinearLayout { private val _title: TextView; diff --git a/app/src/main/java/com/futo/platformplayer/views/sources/SourceHeaderView.kt b/app/src/main/java/com/futo/platformplayer/views/sources/SourceHeaderView.kt index 7d961d77..2b0c4e10 100644 --- a/app/src/main/java/com/futo/platformplayer/views/sources/SourceHeaderView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/sources/SourceHeaderView.kt @@ -8,10 +8,11 @@ import android.util.AttributeSet import android.widget.ImageView import android.widget.LinearLayout import android.widget.TextView +import androidx.core.content.ContextCompat import com.bumptech.glide.Glide import com.futo.platformplayer.R -import com.futo.platformplayer.states.StatePlugins import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig +import com.futo.platformplayer.states.StatePlugins class SourceHeaderView : LinearLayout { private val _sourceImage: ImageView; @@ -89,8 +90,8 @@ class SourceHeaderView : LinearLayout { else _sourcePlatformUrlContainer.visibility = GONE; - if(!config.authorUrl.isNullOrEmpty()) - _sourceBy.setTextColor(resources.getColor(R.color.colorPrimary)); + if(config.authorUrl.isNotEmpty()) + _sourceBy.setTextColor(ContextCompat.getColor(context, R.color.colorPrimary)); else _sourceBy.setTextColor(Color.WHITE); diff --git a/app/src/main/java/com/futo/platformplayer/views/sources/SourceInfoView.kt b/app/src/main/java/com/futo/platformplayer/views/sources/SourceInfoView.kt index ec04f7ec..c8f21bae 100644 --- a/app/src/main/java/com/futo/platformplayer/views/sources/SourceInfoView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/sources/SourceInfoView.kt @@ -6,6 +6,7 @@ import android.util.AttributeSet import android.widget.ImageView import android.widget.LinearLayout import android.widget.TextView +import androidx.core.content.ContextCompat import com.futo.platformplayer.R import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.views.others.BulletPointView @@ -37,7 +38,7 @@ class SourceInfoView : LinearLayout { textTitle.text = title; textDescription.text = description; - val primaryColor = resources.getColor(R.color.colorPrimary); + val primaryColor = ContextCompat.getColor(context, R.color.colorPrimary); bulletPoints.removeAllViews(); for(point in points) { diff --git a/app/src/main/java/com/futo/platformplayer/views/video/FutoThumbnailPlayer.kt b/app/src/main/java/com/futo/platformplayer/views/video/FutoThumbnailPlayer.kt index e8ef2e6f..f4701155 100644 --- a/app/src/main/java/com/futo/platformplayer/views/video/FutoThumbnailPlayer.kt +++ b/app/src/main/java/com/futo/platformplayer/views/video/FutoThumbnailPlayer.kt @@ -1,3 +1,5 @@ +@file:Suppress("DEPRECATION") + package com.futo.platformplayer.views.video import android.content.Context @@ -7,12 +9,13 @@ import android.view.View import android.widget.ImageButton import android.widget.LinearLayout import android.widget.TextView -import com.futo.platformplayer.* -import com.futo.platformplayer.api.media.models.streams.VideoUnMuxedSourceDescriptor +import com.futo.platformplayer.R +import com.futo.platformplayer.Settings import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails import com.futo.platformplayer.helpers.VideoHelper +import com.futo.platformplayer.toHumanTime import com.futo.platformplayer.video.PlayerManager import com.google.android.exoplayer2.ui.PlayerControlView import com.google.android.exoplayer2.ui.StyledPlayerView @@ -49,7 +52,7 @@ class FutoThumbnailPlayer : FutoVideoPlayerBase { containerDuration = videoControls.findViewById(R.id.exo_duration_container); containerLive = videoControls.findViewById(R.id.exo_live_container); - videoControls.setProgressUpdateListener { position, bufferedPosition -> + videoControls.setProgressUpdateListener { position, _ -> if(position < 0) textDurationInverse.visibility = View.INVISIBLE; else diff --git a/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt index 16a51e99..d306b090 100644 --- a/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt +++ b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt @@ -1,3 +1,5 @@ +@file:Suppress("DEPRECATION") + package com.futo.platformplayer.views.video import android.content.Context @@ -12,11 +14,12 @@ import android.view.View import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.widget.FrameLayout import android.widget.ImageButton -import android.widget.RelativeLayout import android.widget.TextView import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.setMargins -import com.futo.platformplayer.* +import com.futo.platformplayer.R +import com.futo.platformplayer.Settings +import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.api.media.models.chapters.IChapter import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource @@ -37,7 +40,6 @@ import com.google.android.exoplayer2.ui.TimeBar import com.google.android.exoplayer2.video.VideoSize import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import java.util.concurrent.Executors @@ -130,8 +132,6 @@ class FutoVideoPlayer : FutoVideoPlayerBase { _root = findViewById(R.id.videoview_root); _videoView = findViewById(R.id.video_player); - val subs = _videoView.subtitleView; - videoControls = findViewById(R.id.video_player_controller); _control_fullscreen = videoControls.findViewById(R.id.exo_fullscreen); _control_videosettings = videoControls.findViewById(R.id.exo_settings); @@ -192,22 +192,32 @@ class FutoVideoPlayer : FutoVideoPlayerBase { if(!isInEditMode) { _videoView.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_ZOOM; val player = StatePlayer.instance.getPlayerOrCreate(context); - //player.modifyState(PLAYER_STATE_NAME, { it.scaleType = MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING}) changePlayer(player); videoControls.player = player.player; _videoControls_fullscreen.player = player.player; } - val attrShowSettings = if(attrs != null) - context.obtainStyledAttributes(attrs, R.styleable.FutoVideoPlayer, 0, 0).getBoolean(R.styleable.FutoVideoPlayer_showSettings, false) ?: false; - else false; - val attrShowFullScreen = if(attrs != null) - context.obtainStyledAttributes(attrs, R.styleable.FutoVideoPlayer, 0, 0).getBoolean(R.styleable.FutoVideoPlayer_showFullScreen, false) ?: false; - else false; - val attrShowMinimize = if(attrs != null) - context.obtainStyledAttributes(attrs, R.styleable.FutoVideoPlayer, 0, 0).getBoolean(R.styleable.FutoVideoPlayer_showMinimize, false) ?: false; - else false; + val attrShowSettings = if(attrs != null) { + val attrArr = context.obtainStyledAttributes(attrs, R.styleable.FutoVideoPlayer, 0, 0) + val result = attrArr.getBoolean(R.styleable.FutoVideoPlayer_showSettings, false) + attrArr.recycle() + result + } else false; + + val attrShowFullScreen = if(attrs != null) { + val attrArr = context.obtainStyledAttributes(attrs, R.styleable.FutoVideoPlayer, 0, 0) + val result = attrArr.getBoolean(R.styleable.FutoVideoPlayer_showFullScreen, false) + attrArr.recycle() + result + } else false; + + val attrShowMinimize = if(attrs != null) { + val attrArr = context.obtainStyledAttributes(attrs, R.styleable.FutoVideoPlayer, 0, 0) + val result = attrArr.getBoolean(R.styleable.FutoVideoPlayer_showMinimize, false) + attrArr.recycle() + result + } else false; if (!attrShowSettings) _control_videosettings.visibility = View.GONE; @@ -501,8 +511,6 @@ class FutoVideoPlayer : FutoVideoPlayerBase { override fun onPlaybackStateChanged(playbackState: Int) { Logger.v(TAG, "onPlaybackStateChanged $playbackState"); - val timeLeft = abs(position - duration); - if (playbackState == ExoPlayer.STATE_ENDED) { if (abs(position - duration) < 2000) { onSourceEnded.emit(); @@ -556,9 +564,9 @@ class FutoVideoPlayer : FutoVideoPlayerBase { val maxHeight = deviceHeight * 0.6; val determinedHeight = if(w > h) - ((h * (viewWidth.toDouble() / w)).toInt().toInt()) + ((h * (viewWidth.toDouble() / w)).toInt()) else - ((h * (viewWidth.toDouble() / w)).toInt().toInt()); + ((h * (viewWidth.toDouble() / w)).toInt()); _lastSourceFit = determinedHeight; _lastSourceFit = Math.max(_lastSourceFit!!, 250); _lastSourceFit = Math.min(_lastSourceFit!!, maxHeight.toInt()); @@ -572,23 +580,20 @@ class FutoVideoPlayer : FutoVideoPlayerBase { } val marginBottom = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 7f, resources.displayMetrics).toInt(); - val rootParams = RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, _lastSourceFit!! + marginBottom) + val rootParams = LayoutParams(LayoutParams.MATCH_PARENT, _lastSourceFit!! + marginBottom) rootParams.bottomMargin = marginBottom; _root.layoutParams = rootParams isFitMode = true; } fun fillHeight(){ Logger.i(TAG, "Video Fill Height"); - val width = resources.displayMetrics.heightPixels; - val height = resources.displayMetrics.widthPixels; - val layoutParams = _videoView.layoutParams as ConstraintLayout.LayoutParams; _originalBottomMargin = if(layoutParams.bottomMargin > 0) layoutParams.bottomMargin else _originalBottomMargin; layoutParams.setMargins(0); _videoView.layoutParams = layoutParams; _videoView.invalidate(); - val rootParams = RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); + val rootParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); _root.layoutParams = rootParams; _root.invalidate(); isFitMode = false; diff --git a/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayerBase.kt b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayerBase.kt index 6d8396d8..9a23b935 100644 --- a/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayerBase.kt +++ b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayerBase.kt @@ -1,3 +1,5 @@ +@file:Suppress("DEPRECATION") + package com.futo.platformplayer.views.video import android.content.Context @@ -6,20 +8,31 @@ import android.util.AttributeSet import android.widget.RelativeLayout import com.futo.platformplayer.Settings import com.futo.platformplayer.api.media.models.chapters.IChapter -import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.api.media.models.streams.VideoMuxedSourceDescriptor -import com.futo.platformplayer.helpers.VideoHelper -import com.futo.platformplayer.api.media.models.streams.sources.* +import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource +import com.futo.platformplayer.api.media.models.streams.sources.IAudioUrlSource +import com.futo.platformplayer.api.media.models.streams.sources.IDashManifestSource +import com.futo.platformplayer.api.media.models.streams.sources.IHLSManifestAudioSource +import com.futo.platformplayer.api.media.models.streams.sources.IHLSManifestSource +import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource +import com.futo.platformplayer.api.media.models.streams.sources.IVideoUrlSource +import com.futo.platformplayer.api.media.models.streams.sources.LocalAudioSource +import com.futo.platformplayer.api.media.models.streams.sources.LocalVideoSource import com.futo.platformplayer.api.media.models.subtitles.ISubtitleSource import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails import com.futo.platformplayer.api.media.platforms.js.models.sources.JSAudioUrlRangeSource import com.futo.platformplayer.api.media.platforms.js.models.sources.JSHLSManifestAudioSource import com.futo.platformplayer.api.media.platforms.js.models.sources.JSVideoUrlRangeSource import com.futo.platformplayer.constructs.Event1 -import com.futo.platformplayer.receivers.MediaControlReceiver +import com.futo.platformplayer.helpers.VideoHelper +import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.video.PlayerManager -import com.google.android.exoplayer2.* +import com.google.android.exoplayer2.C +import com.google.android.exoplayer2.ExoPlayer +import com.google.android.exoplayer2.MediaItem +import com.google.android.exoplayer2.PlaybackException +import com.google.android.exoplayer2.Player import com.google.android.exoplayer2.source.MediaSource import com.google.android.exoplayer2.source.MergingMediaSource import com.google.android.exoplayer2.source.ProgressiveMediaSource @@ -392,10 +405,9 @@ abstract class FutoVideoPlayerBase : RelativeLayout { } private fun swapVideoSourceDash(videoSource: IDashManifestSource) { Logger.i(TAG, "Loading VideoSource [Dash]"); - _lastVideoMediaSource = if (videoSource != null) DashMediaSource.Factory(DefaultHttpDataSource.Factory() + _lastVideoMediaSource = DashMediaSource.Factory(DefaultHttpDataSource.Factory() .setUserAgent(DEFAULT_USER_AGENT)) - .createMediaSource(MediaItem.fromUri(videoSource.url)); - else null; + .createMediaSource(MediaItem.fromUri(videoSource.url)) } private fun swapVideoSourceHLS(videoSource: IHLSManifestSource) { Logger.i(TAG, "Loading VideoSource [HLS]"); @@ -513,7 +525,7 @@ abstract class FutoVideoPlayerBase : RelativeLayout { this.onSourceChanged(lastVideoSource, lastAudioSource, resume); } else - player.player?.stop(); + player.player.stop(); } fun clear() { @@ -535,6 +547,7 @@ abstract class FutoVideoPlayerBase : RelativeLayout { exoPlayer?.setVolume(volume); } + @Suppress("DEPRECATION") protected open fun onPlayerError(error: PlaybackException) { Logger.i(TAG, "onPlayerError error=$error error.errorCode=${error.errorCode} connectivityLoss"); diff --git a/app/src/main/java/com/futo/platformplayer/views/videometa/UpNextView.kt b/app/src/main/java/com/futo/platformplayer/views/videometa/UpNextView.kt index 8d9f136a..26a908c6 100644 --- a/app/src/main/java/com/futo/platformplayer/views/videometa/UpNextView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/videometa/UpNextView.kt @@ -145,7 +145,7 @@ class UpNextView : LinearLayout { _layoutQueueBox.visibility = View.VISIBLE; _layoutEndOfPlaylist.visibility = View.GONE; - _textTitle.text = nextItem.name ?: ""; + _textTitle.text = nextItem.name; val metadataTokens = mutableListOf(); if (nextItem.viewCount > 0) { @@ -157,7 +157,7 @@ class UpNextView : LinearLayout { } _textMetadata.text = metadataTokens.joinToString(" • "); - _textChannelName.text = nextItem.author.name ?: ""; + _textChannelName.text = nextItem.author.name; Glide.with(_imageThumbnail) .load(nextItem.thumbnails.getHQThumbnail()) .placeholder(R.drawable.placeholder_video_thumbnail)