From 49f15e16372f40839245d147660f2dd44b05a192 Mon Sep 17 00:00:00 2001 From: Kai Date: Mon, 10 Feb 2025 22:32:56 -0600 Subject: [PATCH 01/29] don't show audio only download option if there aren't any audio sources available for HLS and DASH the HLS and DASH pickers give the option to only download audio Changelog: changed --- .../main/java/com/futo/platformplayer/UISlideOverlays.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt b/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt index 582a74f7..f1d42838 100644 --- a/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt +++ b/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt @@ -335,7 +335,9 @@ class UISlideOverlays { call = { selectedVideoVariant = it slideUpMenuOverlay.selectOption(videoButtons, it) - slideUpMenuOverlay.setOk(container.context.getString(R.string.download)) + if (audioButtons.isEmpty()){ + slideUpMenuOverlay.setOk(container.context.getString(R.string.download)) + } }, invokeParent = false )) @@ -417,7 +419,7 @@ class UISlideOverlays { } items.add(SlideUpMenuGroup(container.context, container.context.getString(R.string.video), videoSources, - listOf(listOf(SlideUpMenuItem( + listOf((if (audioSources != null) listOf(SlideUpMenuItem( container.context, R.drawable.ic_movie, container.context.getString(R.string.none), @@ -430,7 +432,7 @@ class UISlideOverlays { menu?.setOk(container.context.getString(R.string.download)); }, invokeParent = false - )) + + )) else listOf()) + videoSources .filter { it.isDownloadable() } .map { From 62618224daf32efbd3b925d40e6f57bbd49dd79c Mon Sep 17 00:00:00 2001 From: Koen J Date: Tue, 11 Feb 2025 10:32:11 +0100 Subject: [PATCH 02/29] Casting server did not bind to an automatically selected port. --- .../main/java/com/futo/platformplayer/casting/StateCasting.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 db2e231b..e329a495 100644 --- a/app/src/main/java/com/futo/platformplayer/casting/StateCasting.kt +++ b/app/src/main/java/com/futo/platformplayer/casting/StateCasting.kt @@ -64,7 +64,7 @@ class StateCasting { private val _scopeMain = CoroutineScope(Dispatchers.Main); private val _storage: CastingDeviceInfoStorage = FragmentedStorage.get(); - private val _castServer = ManagedHttpServer(9999); + private val _castServer = ManagedHttpServer(); private var _started = false; var devices: HashMap = hashMapOf(); From 5cde1650f4ccf91394845887aab4f490c756fd4b Mon Sep 17 00:00:00 2001 From: Kelvin Date: Tue, 11 Feb 2025 14:13:48 +0100 Subject: [PATCH 03/29] DashRaw streammetadata fetching --- .../sources/JSDashManifestRawAudioSource.kt | 23 ++++++++++++-- .../models/sources/JSDashManifestRawSource.kt | 31 ++++++++++++++++--- 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawAudioSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawAudioSource.kt index 35435f95..239a3ef9 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawAudioSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawAudioSource.kt @@ -4,6 +4,8 @@ import com.caoccao.javet.values.reference.V8ValueObject 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.IVideoUrlSource +import com.futo.platformplayer.api.media.models.streams.sources.other.IStreamMetaDataSource +import com.futo.platformplayer.api.media.models.streams.sources.other.StreamMetaData import com.futo.platformplayer.api.media.platforms.js.DevJSClient import com.futo.platformplayer.api.media.platforms.js.JSClient import com.futo.platformplayer.engine.IV8PluginConfig @@ -14,7 +16,7 @@ import com.futo.platformplayer.getOrThrow import com.futo.platformplayer.others.Language import com.futo.platformplayer.states.StateDeveloper -class JSDashManifestRawAudioSource : JSSource, IAudioSource, IJSDashManifestRawSource { +class JSDashManifestRawAudioSource : JSSource, IAudioSource, IJSDashManifestRawSource, IStreamMetaDataSource { override val container : String = "application/dash+xml"; override val name : String; override val codec: String; @@ -29,6 +31,8 @@ class JSDashManifestRawAudioSource : JSSource, IAudioSource, IJSDashManifestRawS override val hasGenerate: Boolean; + override var streamMetaData: StreamMetaData? = null; + constructor(plugin: JSClient, obj: V8ValueObject) : super(TYPE_DASH_RAW, plugin, obj) { val contextName = "DashRawSource"; val config = plugin.config; @@ -50,15 +54,28 @@ class JSDashManifestRawAudioSource : JSSource, IAudioSource, IJSDashManifestRawS throw IllegalStateException("Source object already closed"); val plugin = _plugin.getUnderlyingPlugin(); + + var result: String? = null; if(_plugin is DevJSClient) - return StateDeveloper.instance.handleDevCall(_plugin.devID, "DashManifestRaw", false) { + result = StateDeveloper.instance.handleDevCall(_plugin.devID, "DashManifestRaw", false) { _plugin.getUnderlyingPlugin().catchScriptErrors("DashManifestRaw", "dashManifestRaw.generate()") { _obj.invokeString("generate"); } } else - return _plugin.getUnderlyingPlugin().catchScriptErrors("DashManifestRaw", "dashManifestRaw.generate()") { + result = _plugin.getUnderlyingPlugin().catchScriptErrors("DashManifestRaw", "dashManifestRaw.generate()") { _obj.invokeString("generate"); } + + if(result != null){ + val initStart = _obj.getOrDefault(_config, "initStart", "JSDashManifestRawSource", null) ?: 0; + val initEnd = _obj.getOrDefault(_config, "initEnd", "JSDashManifestRawSource", null) ?: 0; + val indexStart = _obj.getOrDefault(_config, "indexStart", "JSDashManifestRawSource", null) ?: 0; + val indexEnd = _obj.getOrDefault(_config, "indexEnd", "JSDashManifestRawSource", null) ?: 0; + if(initEnd > 0 && indexStart > 0 && indexEnd > 0) { + streamMetaData = StreamMetaData(initStart, initEnd, indexStart, indexEnd); + } + } + return result; } } \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawSource.kt index cfe20f46..88511a52 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawSource.kt @@ -6,6 +6,8 @@ import com.caoccao.javet.values.reference.V8ValueObject import com.futo.platformplayer.api.media.models.streams.sources.IDashManifestSource 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.other.IStreamMetaDataSource +import com.futo.platformplayer.api.media.models.streams.sources.other.StreamMetaData import com.futo.platformplayer.api.media.platforms.js.DevJSClient import com.futo.platformplayer.api.media.platforms.js.JSClient import com.futo.platformplayer.engine.IV8PluginConfig @@ -20,7 +22,7 @@ interface IJSDashManifestRawSource { var manifest: String?; fun generate(): String?; } -open class JSDashManifestRawSource: JSSource, IVideoSource, IJSDashManifestRawSource { +open class JSDashManifestRawSource: JSSource, IVideoSource, IJSDashManifestRawSource, IStreamMetaDataSource { override val container : String = "application/dash+xml"; override val name : String; override val width: Int; @@ -36,6 +38,8 @@ open class JSDashManifestRawSource: JSSource, IVideoSource, IJSDashManifestRawSo override val hasGenerate: Boolean; val canMerge: Boolean; + override var streamMetaData: StreamMetaData? = null; + constructor(plugin: JSClient, obj: V8ValueObject) : super(TYPE_DASH_RAW, plugin, obj) { val contextName = "DashRawSource"; val config = plugin.config; @@ -57,17 +61,30 @@ open class JSDashManifestRawSource: JSSource, IVideoSource, IJSDashManifestRawSo return manifest; if(_obj.isClosed) throw IllegalStateException("Source object already closed"); + + var result: String? = null; if(_plugin is DevJSClient) { - return StateDeveloper.instance.handleDevCall(_plugin.devID, "DashManifestRawSource.generate()") { + result = StateDeveloper.instance.handleDevCall(_plugin.devID, "DashManifestRawSource.generate()") { _plugin.getUnderlyingPlugin().catchScriptErrors("DashManifestRaw.generate", "generate()", { _obj.invokeString("generate"); }); } } else - return _plugin.getUnderlyingPlugin().catchScriptErrors("DashManifestRaw.generate", "generate()", { + result = _plugin.getUnderlyingPlugin().catchScriptErrors("DashManifestRaw.generate", "generate()", { _obj.invokeString("generate"); }); + + if(result != null){ + val initStart = _obj.getOrDefault(_config, "initStart", "JSDashManifestRawSource", null) ?: 0; + val initEnd = _obj.getOrDefault(_config, "initEnd", "JSDashManifestRawSource", null) ?: 0; + val indexStart = _obj.getOrDefault(_config, "indexStart", "JSDashManifestRawSource", null) ?: 0; + val indexEnd = _obj.getOrDefault(_config, "indexEnd", "JSDashManifestRawSource", null) ?: 0; + if(initEnd > 0 && indexStart > 0 && indexEnd > 0) { + streamMetaData = StreamMetaData(initStart, initEnd, indexStart, indexEnd); + } + } + return result; } } @@ -100,12 +117,16 @@ class JSDashManifestMergingRawSource( if(videoDash == null) return null; //TODO: Temporary simple solution..make more reliable version + + var result: String? = null; val audioAdaptationSet = adaptationSetRegex.find(audioDash!!); if(audioAdaptationSet != null) { - return videoDash.replace("", "\n" + audioAdaptationSet.value) + result = videoDash.replace("", "\n" + audioAdaptationSet.value) } else - return videoDash; + result = videoDash; + + return result; } companion object { From c1c2000c986d015a42ee78e28d1e83c11c49a4d6 Mon Sep 17 00:00:00 2001 From: Kelvin Date: Tue, 11 Feb 2025 16:13:07 +0100 Subject: [PATCH 04/29] Download container fixes --- .../streams/sources/LocalAudioSource.kt | 4 +-- .../streams/sources/LocalVideoSource.kt | 4 +-- .../sources/JSDashManifestRawAudioSource.kt | 3 +- .../models/sources/JSDashManifestRawSource.kt | 3 +- .../platformplayer/downloads/VideoDownload.kt | 30 ++++++++++++------- 5 files changed, 28 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/LocalAudioSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/LocalAudioSource.kt index 397c24a4..254b9731 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/LocalAudioSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/LocalAudioSource.kt @@ -33,13 +33,13 @@ class LocalAudioSource : IAudioSource, IStreamMetaDataSource { } companion object { - fun fromSource(source: IAudioSource, path: String, fileSize: Long): LocalAudioSource { + fun fromSource(source: IAudioSource, path: String, fileSize: Long, overrideContainer: String? = null): LocalAudioSource { return LocalAudioSource( source.name, path, fileSize, source.bitrate, - source.container, + overrideContainer ?: source.container, source.codec, source.language ); diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/LocalVideoSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/LocalVideoSource.kt index 43455a50..5d15ddb8 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/LocalVideoSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/LocalVideoSource.kt @@ -35,7 +35,7 @@ class LocalVideoSource : IVideoSource, IStreamMetaDataSource { } companion object { - fun fromSource(source: IVideoSource, path: String, fileSize: Long): LocalVideoSource { + fun fromSource(source: IVideoSource, path: String, fileSize: Long, overrideContainer: String? = null): LocalVideoSource { return LocalVideoSource( source.name, path, @@ -43,7 +43,7 @@ class LocalVideoSource : IVideoSource, IStreamMetaDataSource { source.width, source.height, source.duration, - source.container, + overrideContainer ?: source.container, source.codec, source.bitrate?:0 ); diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawAudioSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawAudioSource.kt index 239a3ef9..93ed6a01 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawAudioSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawAudioSource.kt @@ -17,7 +17,7 @@ import com.futo.platformplayer.others.Language import com.futo.platformplayer.states.StateDeveloper class JSDashManifestRawAudioSource : JSSource, IAudioSource, IJSDashManifestRawSource, IStreamMetaDataSource { - override val container : String = "application/dash+xml"; + override val container : String; override val name : String; override val codec: String; override val bitrate: Int; @@ -38,6 +38,7 @@ class JSDashManifestRawAudioSource : JSSource, IAudioSource, IJSDashManifestRawS val config = plugin.config; name = _obj.getOrThrow(config, "name", contextName); url = _obj.getOrThrow(config, "url", contextName); + container = _obj.getOrDefault(config, "container", contextName, null) ?: "application/dash+xml"; manifest = _obj.getOrThrow(config, "manifest", contextName); codec = _obj.getOrDefault(config, "codec", contextName, "") ?: ""; bitrate = _obj.getOrDefault(config, "bitrate", contextName, 0) ?: 0; diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawSource.kt index 88511a52..d6ff7455 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawSource.kt @@ -23,7 +23,7 @@ interface IJSDashManifestRawSource { fun generate(): String?; } open class JSDashManifestRawSource: JSSource, IVideoSource, IJSDashManifestRawSource, IStreamMetaDataSource { - override val container : String = "application/dash+xml"; + override val container : String; override val name : String; override val width: Int; override val height: Int; @@ -45,6 +45,7 @@ open class JSDashManifestRawSource: JSSource, IVideoSource, IJSDashManifestRawSo val config = plugin.config; name = _obj.getOrThrow(config, "name", contextName); url = _obj.getOrThrow(config, "url", contextName); + container = _obj.getOrDefault(config, "container", contextName, null) ?: "application/dash+xml"; manifest = _obj.getOrDefault(config, "manifest", contextName, null); width = _obj.getOrDefault(config, "width", contextName, 0) ?: 0; height = _obj.getOrDefault(config, "height", contextName, 0) ?: 0; 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 8ba2814f..cd4ae885 100644 --- a/app/src/main/java/com/futo/platformplayer/downloads/VideoDownload.kt +++ b/app/src/main/java/com/futo/platformplayer/downloads/VideoDownload.kt @@ -141,11 +141,17 @@ class VideoDownload { var error: String? = null; var videoFilePath: String? = null; - var videoFileName: String? = null; + var videoFileNameBase: String? = null; + var videoFileNameExt: String? = null; + val videoFileName: String? get() = if(videoFileNameBase.isNullOrEmpty()) null else videoFileNameBase + (if(!videoFileNameExt.isNullOrEmpty()) "." + videoFileNameExt else ""); + var videoOverrideContainer: String? = null; var videoFileSize: Long? = null; var audioFilePath: String? = null; - var audioFileName: String? = null; + var audioFileNameBase: String? = null; + var audioFileNameExt: String? = null; + val audioFileName: String? get() = if(audioFileNameBase.isNullOrEmpty()) null else audioFileNameBase + (if(!audioFileNameExt.isNullOrEmpty()) "." + audioFileNameExt else ""); + var audioOverrideContainer: String? = null; var audioFileSize: Long? = null; var subtitleFilePath: String? = null; @@ -235,11 +241,13 @@ class VideoDownload { videoDetails = null; videoSource = null; videoSourceLive = null; + videoOverrideContainer = null; } if(requiresLiveAudioSource && !isLiveAudioSourceValid) { videoDetails = null; audioSource = null; videoSourceLive = null; + audioOverrideContainer = null; } if(video == null && videoDetails == null) throw IllegalStateException("Missing information for download to complete"); @@ -410,11 +418,13 @@ class VideoDownload { else audioSource; if(actualVideoSource != null) { - videoFileName = "${videoDetails!!.id.value!!} [${actualVideoSource!!.width}x${actualVideoSource!!.height}].${videoContainerToExtension(actualVideoSource!!.container)}".sanitizeFileName(); + videoFileNameBase = "${videoDetails!!.id.value!!} [${actualVideoSource!!.width}x${actualVideoSource!!.height}]".sanitizeFileName(); + videoFileNameExt = videoContainerToExtension(actualVideoSource!!.container); videoFilePath = File(downloadDir, videoFileName!!).absolutePath; } if(actualAudioSource != null) { - audioFileName = "${videoDetails!!.id.value!!} [${actualAudioSource!!.language}-${actualAudioSource!!.bitrate}].${audioContainerToExtension(actualAudioSource!!.container)}".sanitizeFileName(); + audioFileNameBase = "${videoDetails!!.id.value!!} [${actualAudioSource!!.language}-${actualAudioSource!!.bitrate}]".sanitizeFileName(); + audioFileNameExt = audioContainerToExtension(actualAudioSource!!.container); audioFilePath = File(downloadDir, audioFileName!!).absolutePath; } if(subtitleSource != null) { @@ -1062,8 +1072,8 @@ class VideoDownload { fun complete() { Logger.i(TAG, "VideoDownload Complete [${name}]"); val existing = StateDownloads.instance.getCachedVideo(id); - val localVideoSource = videoFilePath?.let { LocalVideoSource.fromSource(videoSourceToUse!!, it, videoFileSize ?: 0) }; - val localAudioSource = audioFilePath?.let { LocalAudioSource.fromSource(audioSourceToUse!!, it, audioFileSize ?: 0) }; + val localVideoSource = videoFilePath?.let { LocalVideoSource.fromSource(videoSourceToUse!!, it, videoFileSize ?: 0, videoOverrideContainer) }; + val localAudioSource = audioFilePath?.let { LocalAudioSource.fromSource(audioSourceToUse!!, it, audioFileSize ?: 0, audioOverrideContainer) }; val localSubtitleSource = subtitleFilePath?.let { LocalSubtitleSource.fromSource(subtitleSource!!, it) }; if(localVideoSource != null && videoSourceToUse != null && videoSourceToUse is IStreamMetaDataSource) @@ -1144,7 +1154,7 @@ class VideoDownload { else if (container.contains("video/x-matroska")) return "mkv"; else - return "video"; + return "video";//throw IllegalStateException("Unknown container: " + container) } fun audioContainerToExtension(container: String): String { @@ -1155,11 +1165,11 @@ class VideoDownload { else if (container.contains("audio/mp3")) return "mp3"; else if (container.contains("audio/webm")) - return "webma"; + return "webm"; else if (container == "application/vnd.apple.mpegurl") - return "mp4"; + return "mp4a"; else - return "audio"; + return "audio";// throw IllegalStateException("Unknown container: " + container) } fun subtitleContainerToExtension(container: String?): String { From 65ae8610fd386407dabb78eef21bcb97914c938f Mon Sep 17 00:00:00 2001 From: Kelvin Date: Tue, 11 Feb 2025 17:06:57 +0100 Subject: [PATCH 05/29] Hide download for live videos --- app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt | 2 +- .../fragment/mainactivity/main/VideoDetailView.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt b/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt index f1d42838..849d1b8c 100644 --- a/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt +++ b/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt @@ -909,7 +909,7 @@ class UISlideOverlays { val watchLater = StatePlaylists.instance.getWatchLater(); items.add(SlideUpMenuGroup(container.context, container.context.getString(R.string.actions), "actions", (listOf( - if(!isLimited) + if(!isLimited && !video.isLive) SlideUpMenuItem( container.context, R.drawable.ic_download, 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 f69fcd1a..97f2619a 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 @@ -882,7 +882,7 @@ class VideoDetailView : ConstraintLayout { _slideUpOverlay?.hide(); } else null, - if(!isLimitedVersion) + if(!isLimitedVersion && !(video?.isLive ?: false)) RoundButton(context, R.drawable.ic_download, context.getString(R.string.download), TAG_DOWNLOAD) { video?.let { _slideUpOverlay = UISlideOverlays.showDownloadVideoOverlay(it, _overlayContainer, context.contentResolver); From 3b62f999bf094b1b862c5ba0b14c8161d8128162 Mon Sep 17 00:00:00 2001 From: Koen J Date: Tue, 11 Feb 2025 17:41:25 +0100 Subject: [PATCH 06/29] Fixed HttpFileHandler bug causing casting local webm not to work. --- .../platformplayer/api/http/server/handlers/HttpFileHandler.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/futo/platformplayer/api/http/server/handlers/HttpFileHandler.kt b/app/src/main/java/com/futo/platformplayer/api/http/server/handlers/HttpFileHandler.kt index ac72f633..a6ca97ab 100644 --- a/app/src/main/java/com/futo/platformplayer/api/http/server/handlers/HttpFileHandler.kt +++ b/app/src/main/java/com/futo/platformplayer/api/http/server/handlers/HttpFileHandler.kt @@ -73,7 +73,7 @@ class HttpFileHandler(method: String, path: String, private val contentType: Str Logger.v(TAG, "Sent bytes $current-${current + bytesToSend}, totalBytesSent=$totalBytesSent") current += bytesToSend.toLong() - if (current >= end) { + if (current > end) { Logger.i(TAG, "Expected amount of bytes sent") break } From 4dce8d6a803a2e720a28923f8e42cb279f784fa6 Mon Sep 17 00:00:00 2001 From: Kelvin Date: Tue, 11 Feb 2025 20:31:26 +0100 Subject: [PATCH 07/29] Export playlist support --- .../platformplayer/downloads/VideoDownload.kt | 4 +- .../platformplayer/downloads/VideoExport.kt | 4 +- .../mainactivity/main/PlaylistFragment.kt | 10 ++++ .../mainactivity/main/VideoListEditorView.kt | 10 ++++ .../platformplayer/states/StateDownloads.kt | 54 ++++++++++++++++++- .../res/layout/fragment_video_list_editor.xml | 16 ++++++ 6 files changed, 92 insertions(+), 6 deletions(-) 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 cd4ae885..ede24707 100644 --- a/app/src/main/java/com/futo/platformplayer/downloads/VideoDownload.kt +++ b/app/src/main/java/com/futo/platformplayer/downloads/VideoDownload.kt @@ -375,8 +375,8 @@ class VideoDownload { else throw DownloadException("Could not find a valid video or audio source for download") if(asource is JSSource) { - this.hasVideoRequestExecutor = this.hasVideoRequestExecutor || asource.hasRequestExecutor; - this.requiresLiveVideoSource = this.hasVideoRequestExecutor || (asource is JSDashManifestRawSource && asource.hasGenerate); + this.hasAudioRequestExecutor = this.hasAudioRequestExecutor || asource.hasRequestExecutor; + this.requiresLiveAudioSource = this.hasAudioRequestExecutor || (asource is JSDashManifestRawSource && asource.hasGenerate); } if(asource == null) { diff --git a/app/src/main/java/com/futo/platformplayer/downloads/VideoExport.kt b/app/src/main/java/com/futo/platformplayer/downloads/VideoExport.kt index 7c1c4e09..a4615822 100644 --- a/app/src/main/java/com/futo/platformplayer/downloads/VideoExport.kt +++ b/app/src/main/java/com/futo/platformplayer/downloads/VideoExport.kt @@ -39,7 +39,7 @@ class VideoExport { this.subtitleSource = subtitleSource; } - suspend fun export(context: Context, onProgress: ((Double) -> Unit)? = null): DocumentFile = coroutineScope { + suspend fun export(context: Context, onProgress: ((Double) -> Unit)? = null, documentRoot: DocumentFile? = null): DocumentFile = coroutineScope { val v = videoSource; val a = audioSource; val s = subtitleSource; @@ -50,7 +50,7 @@ class VideoExport { if (s != null) sourceCount++; val outputFile: DocumentFile?; - val downloadRoot = StateApp.instance.getExternalDownloadDirectory(context) ?: throw Exception("External download directory is not set"); + val downloadRoot = documentRoot ?: StateApp.instance.getExternalDownloadDirectory(context) ?: throw Exception("External download directory is not set"); if (sourceCount > 1) { val outputFileName = videoLocal.name.sanitizeFileName(true) + ".mp4"// + VideoDownload.videoContainerToExtension(v.container); val f = downloadRoot.createFile("video/mp4", outputFileName) 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 81669c73..b58e3ee2 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 @@ -8,6 +8,7 @@ import android.view.ViewGroup import androidx.core.app.ShareCompat import androidx.lifecycle.lifecycleScope import com.futo.platformplayer.* +import com.futo.platformplayer.activities.IWithResultLauncher 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 @@ -78,6 +79,14 @@ class PlaylistFragment : MainFragment() { val nameInput = SlideUpMenuTextInput(context, context.getString(R.string.name)); val editPlaylistOverlay = SlideUpMenuOverlay(context, overlayContainer, context.getString(R.string.edit_playlist), context.getString(R.string.ok), false, nameInput); + _buttonExport.setOnClickListener { + _playlist?.let { + val context = StateApp.instance.contextOrNull ?: return@let; + if(context is IWithResultLauncher) + StateDownloads.instance.exportPlaylist(context, it.id); + } + }; + _buttonDownload.visibility = View.VISIBLE; editPlaylistOverlay.onOK.subscribe { val text = nameInput.text; @@ -176,6 +185,7 @@ class PlaylistFragment : MainFragment() { setVideos(parameter.videos, true) setMetadata(parameter.videos.size, parameter.videos.sumOf { it.duration }) setButtonDownloadVisible(true) + setButtonExportVisible(false) setButtonEditVisible(true) if (!StatePlaylists.instance.playlistStore.hasItem { it.id == parameter.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 2e355fa4..b458a093 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 @@ -34,6 +34,7 @@ abstract class VideoListEditorView : LinearLayout { protected var overlayContainer: FrameLayout private set; protected var _buttonDownload: ImageButton; + protected var _buttonExport: ImageButton; private var _buttonShare: ImageButton; private var _buttonEdit: ImageButton; @@ -54,6 +55,8 @@ abstract class VideoListEditorView : LinearLayout { _buttonEdit = findViewById(R.id.button_edit); _buttonDownload = findViewById(R.id.button_download); _buttonDownload.visibility = View.GONE; + _buttonExport = findViewById(R.id.button_export); + _buttonExport.visibility = View.GONE; _buttonShare = findViewById(R.id.button_share); val onShare = _onShare; @@ -68,6 +71,7 @@ abstract class VideoListEditorView : LinearLayout { buttonShuffle.setOnClickListener { onShuffleClick(); }; _buttonEdit.setOnClickListener { onEditClick(); }; + setButtonExportVisible(false); setButtonDownloadVisible(canEdit()); videoListEditorView.onVideoOrderChanged.subscribe(::onVideoOrderChanged); @@ -108,6 +112,7 @@ abstract class VideoListEditorView : LinearLayout { _buttonDownload.setBackgroundResource(R.drawable.background_button_round); if(isDownloading) { + setButtonExportVisible(false); _buttonDownload.setImageResource(R.drawable.ic_loader_animated); _buttonDownload.drawable.assume { it.start() }; _buttonDownload.setOnClickListener { @@ -117,6 +122,7 @@ abstract class VideoListEditorView : LinearLayout { } } else if(isDownloaded) { + setButtonExportVisible(true) _buttonDownload.setImageResource(R.drawable.ic_download_off); _buttonDownload.setOnClickListener { UIDialogs.showConfirmationDialog(context, context.getString(R.string.are_you_sure_you_want_to_delete_the_downloaded_videos), { @@ -125,6 +131,7 @@ abstract class VideoListEditorView : LinearLayout { } } else { + setButtonExportVisible(false); _buttonDownload.setImageResource(R.drawable.ic_download); _buttonDownload.setOnClickListener { onDownload(); @@ -171,6 +178,9 @@ abstract class VideoListEditorView : LinearLayout { protected fun setButtonDownloadVisible(isVisible: Boolean) { _buttonDownload.visibility = if (isVisible) View.VISIBLE else View.GONE; } + protected fun setButtonExportVisible(isVisible: Boolean) { + _buttonExport.visibility = if (isVisible) View.VISIBLE else View.GONE; + } protected fun setButtonEditVisible(isVisible: Boolean) { _buttonEdit.visibility = if (isVisible) View.VISIBLE else View.GONE; 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 4bfeae7b..5bba7e77 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateDownloads.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateDownloads.kt @@ -3,9 +3,11 @@ package com.futo.platformplayer.states import android.content.ContentResolver import android.content.Context import android.os.StatFs +import androidx.documentfile.provider.DocumentFile import com.futo.platformplayer.R import com.futo.platformplayer.Settings import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.activities.IWithResultLauncher import com.futo.platformplayer.api.http.ManagedHttpClient import com.futo.platformplayer.api.media.PlatformID import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource @@ -466,6 +468,54 @@ class StateDownloads { return _downloadsDirectory; } + fun exportPlaylist(context: Context, playlistId: String) { + if(context is IWithResultLauncher) + StateApp.instance.requestDirectoryAccess(context, "Export Playlist", "To export playlist to directory", null) { + if (it == null) + return@requestDirectoryAccess; + + val root = DocumentFile.fromTreeUri(context, it!!); + + val localVideos = StateDownloads.instance.getDownloadedVideosPlaylist(playlistId) + + var lastNotifyTime = -1L; + + UIDialogs.showDialogProgress(context) { + StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) { + it.setText("Exporting videos.."); + var i = 0; + for (video in localVideos) { + withContext(Dispatchers.Main) { + it.setText("Exporting videos...(${i}/${localVideos.size})"); + //it.setProgress(i.toDouble() / localVideos.size); + } + + try { + val export = VideoExport(video, video.videoSource.firstOrNull(), video.audioSource.firstOrNull(), video.subtitlesSources.firstOrNull()); + Logger.i(TAG, "Exporting [${export.videoLocal.name}] started"); + + val file = export.export(context, { progress -> + val now = System.currentTimeMillis(); + if (lastNotifyTime == -1L || now - lastNotifyTime > 100) { + it.setProgress(progress); + lastNotifyTime = now; + } + }, root); + } catch(ex: Throwable) { + Logger.e(TAG, "Failed export [${video.name}]: ${ex.message}", ex); + } + i++; + } + withContext(Dispatchers.Main) { + it.setProgress(1f); + it.dismiss(); + UIDialogs.appToast("Finished exporting playlist"); + } + }; + } + } + } + fun export(context: Context, videoLocal: VideoLocal, videoSource: LocalVideoSource?, audioSource: LocalAudioSource?, subtitleSource: LocalSubtitleSource?) { var lastNotifyTime = -1L; @@ -477,13 +527,13 @@ class StateDownloads { try { Logger.i(TAG, "Exporting [${export.videoLocal.name}] started"); - val file = export.export(context) { progress -> + val file = export.export(context, { progress -> val now = System.currentTimeMillis(); if (lastNotifyTime == -1L || now - lastNotifyTime > 100) { it.setProgress(progress); lastNotifyTime = now; } - } + }, null); withContext(Dispatchers.Main) { it.setProgress(100.0f) diff --git a/app/src/main/res/layout/fragment_video_list_editor.xml b/app/src/main/res/layout/fragment_video_list_editor.xml index 0e636c6f..a906421b 100644 --- a/app/src/main/res/layout/fragment_video_list_editor.xml +++ b/app/src/main/res/layout/fragment_video_list_editor.xml @@ -54,6 +54,22 @@ + Date: Wed, 12 Feb 2025 16:31:30 +0100 Subject: [PATCH 08/29] Fix for misisng exports for export playlist --- .../platformplayer/states/StateDownloads.kt | 35 +++++++++++++------ .../platformplayer/stores/v2/ManagedStore.kt | 11 ++++++ 2 files changed, 35 insertions(+), 11 deletions(-) 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 5bba7e77..e82cd0da 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateDownloads.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateDownloads.kt @@ -48,6 +48,17 @@ class StateDownloads { private val _downloadsStat = StatFs(_downloadsDirectory.absolutePath); private val _downloaded = FragmentedStorage.storeJson("downloaded") + .withOnModified({ + synchronized(_downloadedSet) { + if(!_downloadedSet.contains(it.id)) + _downloadedSet.add(it.id); + } + }, { + synchronized(_downloadedSet) { + if(_downloadedSet.contains(it.id)) + _downloadedSet.remove(it.id); + } + }) .load() .apply { afterLoadingDownloaded(this) }; private val _downloading = FragmentedStorage.storeJson("downloading") @@ -87,9 +98,6 @@ class StateDownloads { Logger.i("StateDownloads", "Deleting local video ${id.value}"); val downloaded = getCachedVideo(id); if(downloaded != null) { - synchronized(_downloadedSet) { - _downloadedSet.remove(id); - } _downloaded.delete(downloaded); } onDownloadedChanged.emit(); @@ -263,9 +271,6 @@ class StateDownloads { if(existing.groupID == null) { existing.groupID = VideoDownload.GROUP_WATCHLATER; existing.groupType = VideoDownload.GROUP_WATCHLATER; - synchronized(_downloadedSet) { - _downloadedSet.add(existing.id); - } _downloaded.save(existing); } } @@ -308,9 +313,6 @@ class StateDownloads { if(existing.groupID == null) { existing.groupID = playlist.id; existing.groupType = VideoDownload.GROUP_PLAYLIST; - synchronized(_downloadedSet) { - _downloadedSet.add(existing.id); - } _downloaded.save(existing); } } @@ -476,7 +478,16 @@ class StateDownloads { val root = DocumentFile.fromTreeUri(context, it!!); - val localVideos = StateDownloads.instance.getDownloadedVideosPlaylist(playlistId) + val playlist = StatePlaylists.instance.getPlaylist(playlistId); + var localVideos = StateDownloads.instance.getDownloadedVideosPlaylist(playlistId); + if(playlist != null) { + val missing = playlist.videos + .filter { vid -> !localVideos.any { it.id.value == null || it.id.value == vid.id.value } } + .map { getCachedVideo(it.id) } + .filterNotNull(); + if(missing.size > 0) + localVideos = localVideos + missing; + }; var lastNotifyTime = -1L; @@ -484,6 +495,7 @@ class StateDownloads { StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) { it.setText("Exporting videos.."); var i = 0; + var success = 0; for (video in localVideos) { withContext(Dispatchers.Main) { it.setText("Exporting videos...(${i}/${localVideos.size})"); @@ -501,6 +513,7 @@ class StateDownloads { lastNotifyTime = now; } }, root); + success++; } catch(ex: Throwable) { Logger.e(TAG, "Failed export [${video.name}]: ${ex.message}", ex); } @@ -509,7 +522,7 @@ class StateDownloads { withContext(Dispatchers.Main) { it.setProgress(1f); it.dismiss(); - UIDialogs.appToast("Finished exporting playlist"); + UIDialogs.appToast("Finished exporting playlist (${success} videos${if(i < success) ", ${i} errors" else ""})"); } }; } 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 cb10cd20..90e79ccc 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 @@ -33,6 +33,9 @@ class ManagedStore{ val className: String? get() = _class.classifier?.assume>()?.simpleName; + private var _onModificationCreate: ((T) -> Unit)? = null; + private var _onModificationDelete: ((T) -> Unit)? = null; + val name: String; constructor(name: String, dir: File, clazz: KType, serializer: StoreSerializer, niceName: String? = null) { @@ -62,6 +65,12 @@ class ManagedStore{ return this; } + fun withOnModified(created: (T)->Unit, deleted: (T)->Unit): ManagedStore { + _onModificationCreate = created; + _onModificationDelete = deleted; + return this; + } + fun load(): ManagedStore { synchronized(_files) { _files.clear(); @@ -265,6 +274,7 @@ class ManagedStore{ file = saveNew(obj); if(_reconstructStore != null && (_reconstructStore!!.backupOnCreate || withReconstruction)) saveReconstruction(file, obj); + _onModificationCreate?.invoke(obj) } } } @@ -300,6 +310,7 @@ class ManagedStore{ _files.remove(item); Logger.v(TAG, "Deleting file ${logName(file.id)}"); file.delete(); + _onModificationDelete?.invoke(item) } } } From 83843f192da2238cffee2ff76d90e258db2d4f10 Mon Sep 17 00:00:00 2001 From: Kelvin Date: Wed, 12 Feb 2025 18:43:15 +0100 Subject: [PATCH 09/29] Show total downloaded content duration, Indicator how many subscriptions, save queue as playlist --- .../futo/platformplayer/UISlideOverlays.kt | 30 +++++++++++++++++++ .../mainactivity/main/CreatorsFragment.kt | 9 +++++- .../mainactivity/main/DownloadsFragment.kt | 3 +- .../futo/platformplayer/states/StatePlayer.kt | 7 +++++ .../views/adapters/SubscriptionAdapter.kt | 6 +++- .../views/overlays/QueueEditorOverlay.kt | 21 +++++++++++++ app/src/main/res/layout/fragment_creators.xml | 11 ++++++- app/src/main/res/layout/overlay_queue.xml | 15 ++++++++++ 8 files changed, 98 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt b/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt index 849d1b8c..382f14de 100644 --- a/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt +++ b/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt @@ -79,6 +79,36 @@ class UISlideOverlays { return menu; } + fun showQueueOptionsOverlay(context: Context, container: ViewGroup) { + UISlideOverlays.showOverlay(container, "Queue options", null, { + + }, SlideUpMenuItem(context, R.drawable.ic_playlist, "Save as playlist", "", "Creates a new playlist with queue as videos", null, { + val nameInput = SlideUpMenuTextInput(container.context, container.context.getString(R.string.name)); + val addPlaylistOverlay = SlideUpMenuOverlay(container.context, container, container.context.getString(R.string.create_new_playlist), container.context.getString(R.string.ok), false, nameInput); + + addPlaylistOverlay.onOK.subscribe { + val text = nameInput.text.trim() + if (text.isBlank()) { + return@subscribe; + } + + addPlaylistOverlay.hide(); + nameInput.deactivate(); + nameInput.clear(); + StatePlayer.instance.saveQueueAsPlaylist(text); + UIDialogs.appToast("Playlist [${text}] created"); + }; + + addPlaylistOverlay.onCancel.subscribe { + nameInput.deactivate(); + nameInput.clear(); + }; + + addPlaylistOverlay.show(); + nameInput.activate(); + }, false)); + } + fun showSubscriptionOptionsOverlay(subscription: Subscription, container: ViewGroup): SlideUpMenuOverlay { val items = arrayListOf(); diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CreatorsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CreatorsFragment.kt index 6efc7a3d..54649ebf 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CreatorsFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CreatorsFragment.kt @@ -10,6 +10,7 @@ import android.widget.EditText import android.widget.FrameLayout import android.widget.ImageButton import android.widget.Spinner +import android.widget.TextView import androidx.core.widget.addTextChangedListener import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -26,6 +27,7 @@ class CreatorsFragment : MainFragment() { private var _overlayContainer: FrameLayout? = null; private var _containerSearch: FrameLayout? = null; private var _editSearch: EditText? = null; + private var _textMeta: TextView? = null; private var _buttonClearSearch: ImageButton? = null override fun onCreateMainView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { @@ -34,6 +36,7 @@ class CreatorsFragment : MainFragment() { val editSearch: EditText = view.findViewById(R.id.edit_search); val buttonClearSearch: ImageButton = view.findViewById(R.id.button_clear_search) _editSearch = editSearch + _textMeta = view.findViewById(R.id.text_meta); _buttonClearSearch = buttonClearSearch buttonClearSearch.setOnClickListener { editSearch.text.clear() @@ -41,7 +44,11 @@ class CreatorsFragment : MainFragment() { _buttonClearSearch?.visibility = View.INVISIBLE; } - val adapter = SubscriptionAdapter(inflater, getString(R.string.confirm_delete_subscription)); + val adapter = SubscriptionAdapter(inflater, getString(R.string.confirm_delete_subscription)) { subs -> + _textMeta?.let { + it.text = "${subs.size} creator${if(subs.size > 1) "s" else ""}"; + } + }; adapter.onClick.subscribe { platformUser -> navigate(platformUser) }; adapter.onSettings.subscribe { sub -> _overlayContainer?.let { UISlideOverlays.showSubscriptionOptionsOverlay(sub, it) } } diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/DownloadsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/DownloadsFragment.kt index 7995d543..440aa235 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/DownloadsFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/DownloadsFragment.kt @@ -22,6 +22,7 @@ import com.futo.platformplayer.states.StateDownloads import com.futo.platformplayer.states.StatePlayer import com.futo.platformplayer.states.StatePlaylists import com.futo.platformplayer.toHumanBytesSize +import com.futo.platformplayer.toHumanDuration import com.futo.platformplayer.views.AnyInsertedAdapterView import com.futo.platformplayer.views.AnyInsertedAdapterView.Companion.asAnyWithTop import com.futo.platformplayer.views.adapters.viewholders.VideoDownloadViewHolder @@ -215,7 +216,7 @@ class DownloadsFragment : MainFragment() { _listDownloadedHeader.visibility = GONE; } else { _listDownloadedHeader.visibility = VISIBLE; - _listDownloadedMeta.text = "(${downloaded.size} ${context.getString(R.string.videos).lowercase()})"; + _listDownloadedMeta.text = "(${downloaded.size} ${context.getString(R.string.videos).lowercase()}${if(downloaded.size > 0) ", ${downloaded.sumOf { it.duration }.toHumanDuration(false)}" else ""})"; } lastDownloads = downloaded; 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 286941c4..b8368ea5 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StatePlayer.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StatePlayer.kt @@ -13,6 +13,7 @@ import com.futo.platformplayer.UIDialogs 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.api.media.models.video.SerializedPlatformVideo import com.futo.platformplayer.constructs.Event0 import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.logging.Logger @@ -130,6 +131,12 @@ class StatePlayer { closeMediaSession(); } + fun saveQueueAsPlaylist(name: String){ + val videos = _queue.toList(); + val playlist = Playlist(name, videos.map { SerializedPlatformVideo.fromVideo(it) }); + StatePlaylists.instance.createOrUpdatePlaylist(playlist); + } + //Notifications fun hasMediaSession() : Boolean { return MediaPlaybackService.getService() != null; 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 e3644cc3..ef3f7cb0 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 @@ -16,6 +16,7 @@ class SubscriptionAdapter : RecyclerView.Adapter { private lateinit var _sortedDataset: List; private val _inflater: LayoutInflater; private val _confirmationMessage: String; + private val _onDatasetChanged: ((List)->Unit)?; var onClick = Event1(); var onSettings = Event1(); @@ -30,9 +31,10 @@ class SubscriptionAdapter : RecyclerView.Adapter { updateDataset(); } - constructor(inflater: LayoutInflater, confirmationMessage: String) : super() { + constructor(inflater: LayoutInflater, confirmationMessage: String, onDatasetChanged: ((List)->Unit)? = null) : super() { _inflater = inflater; _confirmationMessage = confirmationMessage; + _onDatasetChanged = onDatasetChanged; StateSubscriptions.instance.onSubscriptionsChanged.subscribe { _, _ -> if(Looper.myLooper() != Looper.getMainLooper()) StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) { updateDataset() } @@ -78,6 +80,8 @@ class SubscriptionAdapter : RecyclerView.Adapter { .filter { (queryLower.isNullOrBlank() || it.channel.name.lowercase().contains(queryLower)) } .toList(); + _onDatasetChanged?.invoke(_sortedDataset); + notifyDataSetChanged(); } diff --git a/app/src/main/java/com/futo/platformplayer/views/overlays/QueueEditorOverlay.kt b/app/src/main/java/com/futo/platformplayer/views/overlays/QueueEditorOverlay.kt index 215e8dcb..edaed188 100644 --- a/app/src/main/java/com/futo/platformplayer/views/overlays/QueueEditorOverlay.kt +++ b/app/src/main/java/com/futo/platformplayer/views/overlays/QueueEditorOverlay.kt @@ -2,16 +2,26 @@ package com.futo.platformplayer.views.overlays import android.content.Context import android.util.AttributeSet +import android.widget.FrameLayout +import android.widget.ImageView import android.widget.LinearLayout import com.futo.platformplayer.states.StatePlayer import com.futo.platformplayer.R +import com.futo.platformplayer.UISlideOverlays import com.futo.platformplayer.constructs.Event0 import com.futo.platformplayer.views.lists.VideoListEditorView +import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuItem +import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuOverlay +import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuTextInput class QueueEditorOverlay : LinearLayout { private val _topbar : OverlayTopbar; private val _editor : VideoListEditorView; + private val _btnSettings: ImageView; + + private val _overlayContainer: FrameLayout; + val onClose = Event0(); @@ -19,6 +29,9 @@ class QueueEditorOverlay : LinearLayout { inflate(context, R.layout.overlay_queue, this) _topbar = findViewById(R.id.topbar); _editor = findViewById(R.id.editor); + _btnSettings = findViewById(R.id.button_settings); + _overlayContainer = findViewById(R.id.overlay_container); + _topbar.onClose.subscribe(this, onClose::emit); _editor.onVideoOrderChanged.subscribe { StatePlayer.instance.setQueueWithExisting(it) } @@ -28,6 +41,10 @@ class QueueEditorOverlay : LinearLayout { } _editor.onVideoClicked.subscribe { v -> StatePlayer.instance.setQueuePosition(v) } + _btnSettings.setOnClickListener { + handleSettings(); + } + _topbar.setInfo(context.getString(R.string.queue), ""); } @@ -40,4 +57,8 @@ class QueueEditorOverlay : LinearLayout { fun cleanup() { _topbar.onClose.remove(this); } + + fun handleSettings() { + UISlideOverlays.showQueueOptionsOverlay(context, _overlayContainer); + } } \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_creators.xml b/app/src/main/res/layout/fragment_creators.xml index 62694f56..a3848565 100644 --- a/app/src/main/res/layout/fragment_creators.xml +++ b/app/src/main/res/layout/fragment_creators.xml @@ -16,7 +16,7 @@ + + diff --git a/app/src/main/res/layout/overlay_queue.xml b/app/src/main/res/layout/overlay_queue.xml index 9dc827fb..4cee7598 100644 --- a/app/src/main/res/layout/overlay_queue.xml +++ b/app/src/main/res/layout/overlay_queue.xml @@ -21,5 +21,20 @@ android:layout_height="0dp" app:layout_constraintTop_toBottomOf="@id/topbar" app:layout_constraintBottom_toBottomOf="parent" /> + + \ No newline at end of file From b9bbfb44c59b4f4606532768f98be4fcf3843fab Mon Sep 17 00:00:00 2001 From: Kelvin Date: Wed, 12 Feb 2025 18:53:30 +0100 Subject: [PATCH 10/29] Update submodules, fix apple podcast dir --- .gitmodules | 2 +- app/src/stable/assets/sources/apple-podcast | 1 - app/src/stable/assets/sources/apple-podcasts | 1 + app/src/stable/assets/sources/bitchute | 2 +- app/src/stable/assets/sources/peertube | 2 +- app/src/stable/assets/sources/rumble | 2 +- app/src/stable/assets/sources/youtube | 2 +- app/src/unstable/assets/sources/apple-podcasts | 2 +- app/src/unstable/assets/sources/bitchute | 2 +- app/src/unstable/assets/sources/peertube | 2 +- app/src/unstable/assets/sources/rumble | 2 +- app/src/unstable/assets/sources/youtube | 2 +- 12 files changed, 11 insertions(+), 11 deletions(-) delete mode 160000 app/src/stable/assets/sources/apple-podcast create mode 160000 app/src/stable/assets/sources/apple-podcasts diff --git a/.gitmodules b/.gitmodules index dad5c74e..5f6ab0dc 100644 --- a/.gitmodules +++ b/.gitmodules @@ -83,7 +83,7 @@ path = app/src/stable/assets/sources/dailymotion url = ../plugins/dailymotion.git [submodule "app/src/stable/assets/sources/apple-podcast"] - path = app/src/stable/assets/sources/apple-podcast + path = app/src/stable/assets/sources/apple-podcasts url = ../plugins/apple-podcasts.git [submodule "app/src/unstable/assets/sources/apple-podcasts"] path = app/src/unstable/assets/sources/apple-podcasts diff --git a/app/src/stable/assets/sources/apple-podcast b/app/src/stable/assets/sources/apple-podcast deleted file mode 160000 index f79c7141..00000000 --- a/app/src/stable/assets/sources/apple-podcast +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f79c7141bcb11464103abc56fd7be492fe8568ab diff --git a/app/src/stable/assets/sources/apple-podcasts b/app/src/stable/assets/sources/apple-podcasts new file mode 160000 index 00000000..090104c7 --- /dev/null +++ b/app/src/stable/assets/sources/apple-podcasts @@ -0,0 +1 @@ +Subproject commit 090104c7fa112d9772f43c7c2620e8c2cf3c9d6a diff --git a/app/src/stable/assets/sources/bitchute b/app/src/stable/assets/sources/bitchute index 8d7c0e25..7f869aa4 160000 --- a/app/src/stable/assets/sources/bitchute +++ b/app/src/stable/assets/sources/bitchute @@ -1 +1 @@ -Subproject commit 8d7c0e252738450f2a8bb2a48e9f8bdc24cfea54 +Subproject commit 7f869aa4b117214095feb367d38414402cd08417 diff --git a/app/src/stable/assets/sources/peertube b/app/src/stable/assets/sources/peertube index cfabdc97..20fd03d9 160000 --- a/app/src/stable/assets/sources/peertube +++ b/app/src/stable/assets/sources/peertube @@ -1 +1 @@ -Subproject commit cfabdc97ab435822c44b0135b3b76519327ba05a +Subproject commit 20fd03d9847b308d81ce474144bee79b04385477 diff --git a/app/src/stable/assets/sources/rumble b/app/src/stable/assets/sources/rumble index 670cbc04..b9e6259f 160000 --- a/app/src/stable/assets/sources/rumble +++ b/app/src/stable/assets/sources/rumble @@ -1 +1 @@ -Subproject commit 670cbc043e8901026c43e1a2e4ac44e12e32143b +Subproject commit b9e6259f4eedd41d1838506d32d240e177c1feff diff --git a/app/src/stable/assets/sources/youtube b/app/src/stable/assets/sources/youtube index 15d3391a..65524663 160000 --- a/app/src/stable/assets/sources/youtube +++ b/app/src/stable/assets/sources/youtube @@ -1 +1 @@ -Subproject commit 15d3391a5d091405b0c9bd92ff87ebcf2f6944eb +Subproject commit 655246634f4aac712c2cc26c8abc1bc29001e3d8 diff --git a/app/src/unstable/assets/sources/apple-podcasts b/app/src/unstable/assets/sources/apple-podcasts index f79c7141..090104c7 160000 --- a/app/src/unstable/assets/sources/apple-podcasts +++ b/app/src/unstable/assets/sources/apple-podcasts @@ -1 +1 @@ -Subproject commit f79c7141bcb11464103abc56fd7be492fe8568ab +Subproject commit 090104c7fa112d9772f43c7c2620e8c2cf3c9d6a diff --git a/app/src/unstable/assets/sources/bitchute b/app/src/unstable/assets/sources/bitchute index 8d7c0e25..7f869aa4 160000 --- a/app/src/unstable/assets/sources/bitchute +++ b/app/src/unstable/assets/sources/bitchute @@ -1 +1 @@ -Subproject commit 8d7c0e252738450f2a8bb2a48e9f8bdc24cfea54 +Subproject commit 7f869aa4b117214095feb367d38414402cd08417 diff --git a/app/src/unstable/assets/sources/peertube b/app/src/unstable/assets/sources/peertube index cfabdc97..20fd03d9 160000 --- a/app/src/unstable/assets/sources/peertube +++ b/app/src/unstable/assets/sources/peertube @@ -1 +1 @@ -Subproject commit cfabdc97ab435822c44b0135b3b76519327ba05a +Subproject commit 20fd03d9847b308d81ce474144bee79b04385477 diff --git a/app/src/unstable/assets/sources/rumble b/app/src/unstable/assets/sources/rumble index 670cbc04..b9e6259f 160000 --- a/app/src/unstable/assets/sources/rumble +++ b/app/src/unstable/assets/sources/rumble @@ -1 +1 @@ -Subproject commit 670cbc043e8901026c43e1a2e4ac44e12e32143b +Subproject commit b9e6259f4eedd41d1838506d32d240e177c1feff diff --git a/app/src/unstable/assets/sources/youtube b/app/src/unstable/assets/sources/youtube index 2c816009..65524663 160000 --- a/app/src/unstable/assets/sources/youtube +++ b/app/src/unstable/assets/sources/youtube @@ -1 +1 @@ -Subproject commit 2c816009f7a09ceb79a707654edbb01e7fb7a3a4 +Subproject commit 655246634f4aac712c2cc26c8abc1bc29001e3d8 From 1dfe18aa6fe8fa9b50af081f59d783e6c67d5a3b Mon Sep 17 00:00:00 2001 From: Kelvin Date: Wed, 12 Feb 2025 18:58:01 +0100 Subject: [PATCH 11/29] Add Apple podcasts --- app/src/stable/res/raw/plugin_config.json | 3 ++- app/src/unstable/res/raw/plugin_config.json | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/stable/res/raw/plugin_config.json b/app/src/stable/res/raw/plugin_config.json index 3b7dacec..d98fc987 100644 --- a/app/src/stable/res/raw/plugin_config.json +++ b/app/src/stable/res/raw/plugin_config.json @@ -12,7 +12,8 @@ "cf8ea74d-ad9b-489e-a083-539b6aa8648c": "sources/bilibili/build/BiliBiliConfig.json", "4e365633-6d3f-4267-8941-fdc36631d813": "sources/spotify/build/SpotifyConfig.json", "9c87e8db-e75d-48f4-afe5-2d203d4b95c5": "sources/dailymotion/build/DailymotionConfig.json", - "e8b1ad5f-0c6d-497d-a5fa-0a785a16d902": "sources/bitchute/BitchuteConfig.json" + "e8b1ad5f-0c6d-497d-a5fa-0a785a16d902": "sources/bitchute/BitchuteConfig.json", + "89ae4889-0420-4d16-ad6c-19c776b28f99": "sources/apple-podcasts/ApplePodcastsConfig.json" }, "SOURCES_EMBEDDED_DEFAULT": [ "35ae969a-a7db-11ed-afa1-0242ac120002" diff --git a/app/src/unstable/res/raw/plugin_config.json b/app/src/unstable/res/raw/plugin_config.json index 4e4cc1dc..cfbf3e87 100644 --- a/app/src/unstable/res/raw/plugin_config.json +++ b/app/src/unstable/res/raw/plugin_config.json @@ -12,7 +12,8 @@ "cf8ea74d-ad9b-489e-a083-539b6aa8648c": "sources/bilibili/build/BiliBiliConfig.json", "4e365633-6d3f-4267-8941-fdc36631d813": "sources/spotify/build/SpotifyConfig.json", "9c87e8db-e75d-48f4-afe5-2d203d4b95c5": "sources/dailymotion/build/DailymotionConfig.json", - "e8b1ad5f-0c6d-497d-a5fa-0a785a16d902": "sources/bitchute/BitchuteConfig.json" + "e8b1ad5f-0c6d-497d-a5fa-0a785a16d902": "sources/bitchute/BitchuteConfig.json", + "89ae4889-0420-4d16-ad6c-19c776b28f99": "sources/apple-podcasts/ApplePodcastsConfig.json" }, "SOURCES_EMBEDDED_DEFAULT": [ "35ae969a-a7db-11ed-afa1-0242ac120002" From 36c51f1a0c1d6a30ef8e708585fe2d2dbf7ecd6e Mon Sep 17 00:00:00 2001 From: Kelvin Date: Wed, 12 Feb 2025 19:06:43 +0100 Subject: [PATCH 12/29] Refs --- app/src/stable/assets/sources/youtube | 2 +- app/src/unstable/assets/sources/youtube | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/stable/assets/sources/youtube b/app/src/stable/assets/sources/youtube index 65524663..8f8774a7 160000 --- a/app/src/stable/assets/sources/youtube +++ b/app/src/stable/assets/sources/youtube @@ -1 +1 @@ -Subproject commit 655246634f4aac712c2cc26c8abc1bc29001e3d8 +Subproject commit 8f8774a782aa49889774920688de371f28317ca6 diff --git a/app/src/unstable/assets/sources/youtube b/app/src/unstable/assets/sources/youtube index 65524663..8f8774a7 160000 --- a/app/src/unstable/assets/sources/youtube +++ b/app/src/unstable/assets/sources/youtube @@ -1 +1 @@ -Subproject commit 655246634f4aac712c2cc26c8abc1bc29001e3d8 +Subproject commit 8f8774a782aa49889774920688de371f28317ca6 From 2f0ba1b1f7ceb0342f7cb30b8dcb6508979c827c Mon Sep 17 00:00:00 2001 From: Kelvin Date: Wed, 12 Feb 2025 19:17:20 +0100 Subject: [PATCH 13/29] Setting to check disabled plugins for updates (off by default) --- app/src/main/java/com/futo/platformplayer/Settings.kt | 3 +++ .../main/java/com/futo/platformplayer/states/StatePlugins.kt | 3 +++ app/src/main/res/values/strings.xml | 2 ++ 3 files changed, 8 insertions(+) diff --git a/app/src/main/java/com/futo/platformplayer/Settings.kt b/app/src/main/java/com/futo/platformplayer/Settings.kt index 0a62e28f..c95947ea 100644 --- a/app/src/main/java/com/futo/platformplayer/Settings.kt +++ b/app/src/main/java/com/futo/platformplayer/Settings.kt @@ -644,6 +644,9 @@ class Settings : FragmentedStorageFileJson() { @Serializable class Plugins { + @FormField(R.string.check_disabled_plugin_updates, FieldForm.TOGGLE, R.string.check_disabled_plugin_updates_description, -1) + var checkDisabledPluginsForUpdates: Boolean = false; + @FormField(R.string.clear_cookies_on_logout, FieldForm.TOGGLE, R.string.clears_cookies_when_you_log_out, 0) var clearCookiesOnLogout: Boolean = true; 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 02154677..3506bc54 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StatePlugins.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StatePlugins.kt @@ -2,6 +2,7 @@ package com.futo.platformplayer.states import android.content.Context import com.futo.platformplayer.R +import com.futo.platformplayer.Settings import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.activities.LoginActivity import com.futo.platformplayer.api.http.ManagedHttpClient @@ -101,6 +102,8 @@ class StatePlugins { if (availableClient !is JSClient) { continue } + if(!Settings.instance.plugins.checkDisabledPluginsForUpdates && StatePlatform.instance.isClientEnabled(availableClient.id)) + continue; val newConfig = checkForUpdates(availableClient.config); if (newConfig != null) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e746d8fc..e7026f7a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -286,6 +286,8 @@ Also removes any data related plugin like login or settings Announcement Notifications + Check disabled plugins for updates + Check disabled plugins for updates Planned Content Notifications Schedules discovered planned content as notifications, resulting in more accurate notifications for this content. Attempt to utilize byte ranges From 44c8800bec12293168afc19f5ba90708ada7c611 Mon Sep 17 00:00:00 2001 From: Kelvin Date: Wed, 12 Feb 2025 19:25:29 +0100 Subject: [PATCH 14/29] plugin disabled update check fix --- .../main/java/com/futo/platformplayer/states/StatePlugins.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 3506bc54..cbb7b4d4 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StatePlugins.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StatePlugins.kt @@ -102,7 +102,7 @@ class StatePlugins { if (availableClient !is JSClient) { continue } - if(!Settings.instance.plugins.checkDisabledPluginsForUpdates && StatePlatform.instance.isClientEnabled(availableClient.id)) + if(!Settings.instance.plugins.checkDisabledPluginsForUpdates && !StatePlatform.instance.isClientEnabled(availableClient.id)) continue; val newConfig = checkForUpdates(availableClient.config); From 157d5b4c3659f6b1cd40f99c03e86248c70c4525 Mon Sep 17 00:00:00 2001 From: Kelvin Date: Wed, 12 Feb 2025 20:03:33 +0100 Subject: [PATCH 15/29] Fix container id conflict --- .../futo/platformplayer/views/overlays/QueueEditorOverlay.kt | 2 +- app/src/main/res/layout/overlay_queue.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/views/overlays/QueueEditorOverlay.kt b/app/src/main/java/com/futo/platformplayer/views/overlays/QueueEditorOverlay.kt index edaed188..a7181e90 100644 --- a/app/src/main/java/com/futo/platformplayer/views/overlays/QueueEditorOverlay.kt +++ b/app/src/main/java/com/futo/platformplayer/views/overlays/QueueEditorOverlay.kt @@ -30,7 +30,7 @@ class QueueEditorOverlay : LinearLayout { _topbar = findViewById(R.id.topbar); _editor = findViewById(R.id.editor); _btnSettings = findViewById(R.id.button_settings); - _overlayContainer = findViewById(R.id.overlay_container); + _overlayContainer = findViewById(R.id.overlay_container_queue); _topbar.onClose.subscribe(this, onClose::emit); diff --git a/app/src/main/res/layout/overlay_queue.xml b/app/src/main/res/layout/overlay_queue.xml index 4cee7598..904372ea 100644 --- a/app/src/main/res/layout/overlay_queue.xml +++ b/app/src/main/res/layout/overlay_queue.xml @@ -33,7 +33,7 @@ app:srcCompat="@drawable/ic_settings" /> From 8b7c9df286d56f923736da30849f1812901ab5b3 Mon Sep 17 00:00:00 2001 From: Kelvin Date: Wed, 12 Feb 2025 20:16:50 +0100 Subject: [PATCH 16/29] Add to queue button on recommendations, no toast on add to watch later if dup --- .../main/java/com/futo/platformplayer/UISlideOverlays.kt | 5 +++-- .../fragment/mainactivity/main/ChannelFragment.kt | 4 ++-- .../fragment/mainactivity/main/ContentFeedView.kt | 4 ++-- .../fragment/mainactivity/main/VideoDetailView.kt | 9 +++++++-- .../com/futo/platformplayer/states/StatePlaylists.kt | 6 +++++- .../views/adapters/feedtypes/PreviewVideoView.kt | 2 +- 6 files changed, 20 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt b/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt index 382f14de..67497b1e 100644 --- a/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt +++ b/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt @@ -1075,8 +1075,9 @@ class UISlideOverlays { StatePlayer.TYPE_WATCHLATER, "${watchLater.size} " + container.context.getString(R.string.videos), tag = "watch later", - call = { StatePlaylists.instance.addToWatchLater(SerializedPlatformVideo.fromVideo(video), true); - UIDialogs.appToast("Added to watch later", false); + call = { + if(StatePlaylists.instance.addToWatchLater(SerializedPlatformVideo.fromVideo(video), true)) + UIDialogs.appToast("Added to watch later", false); }), ) ); 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 8bea629a..817a8ca2 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 @@ -238,8 +238,8 @@ class ChannelFragment : MainFragment() { } adapter.onAddToWatchLaterClicked.subscribe { content -> if (content is IPlatformVideo) { - StatePlaylists.instance.addToWatchLater(SerializedPlatformVideo.fromVideo(content), true) - UIDialogs.toast("Added to watch later\n[${content.name}]") + if(StatePlaylists.instance.addToWatchLater(SerializedPlatformVideo.fromVideo(content), true)) + UIDialogs.toast("Added to watch later\n[${content.name}]") } } adapter.onUrlClicked.subscribe { url -> diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentFeedView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentFeedView.kt index 04a51189..4390a80c 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentFeedView.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentFeedView.kt @@ -82,8 +82,8 @@ abstract class ContentFeedView : FeedView Date: Thu, 13 Feb 2025 21:00:02 +0100 Subject: [PATCH 17/29] Remove accidental always update --- .../fragment/mainactivity/main/SourceDetailFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 1bdedfcd..1b621c03 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 @@ -556,7 +556,7 @@ class SourceDetailFragment : MainFragment() { Logger.i(TAG, "Downloaded source config ($sourceUrl):\n${configJson}"); val config = SourcePluginConfig.fromJson(configJson); - if (config.version <= c.version && config.name != "Youtube") { + if (config.version <= c.version) { Logger.i(TAG, "Plugin is up to date."); withContext(Dispatchers.Main) { UIDialogs.toast(context.getString(R.string.plugin_is_fully_up_to_date)); }; return@launch; From 78f516988074f5a45a1bd3596e42ea165f006a47 Mon Sep 17 00:00:00 2001 From: Kai Date: Thu, 20 Feb 2025 14:43:13 -0600 Subject: [PATCH 18/29] add recommendations assignment in video details class Changelog: added --- app/src/main/assets/scripts/source.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/assets/scripts/source.js b/app/src/main/assets/scripts/source.js index 0c87cac4..b6b4ab6d 100644 --- a/app/src/main/assets/scripts/source.js +++ b/app/src/main/assets/scripts/source.js @@ -263,6 +263,10 @@ class PlatformVideoDetails extends PlatformVideo { this.rating = obj.rating ?? null; //IRating this.subtitles = obj.subtitles ?? []; this.isShort = !!obj.isShort ?? false; + + if (obj.getContentRecommendations) { + this.getContentRecommendations = obj.getContentRecommendations + } } } From b5ac8b3ec6d94a2af55d587a1993a1ea268bc8a0 Mon Sep 17 00:00:00 2001 From: Kai DeLorenzo Date: Thu, 20 Feb 2025 21:59:29 +0000 Subject: [PATCH 19/29] Edit Authentication.md --- docs/Authentication.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Authentication.md b/docs/Authentication.md index f21581a1..f54eced5 100644 --- a/docs/Authentication.md +++ b/docs/Authentication.md @@ -8,7 +8,7 @@ The goal of the authentication system is to provide plugins the ability to make > >You should always only login (and install for that matter) plugins you trust. -How to actually use the authenticated client is described in the Http package documentation (See [Package: Http](_blank)). +How to actually use the authenticated client is described in the Http package documentation (See [Package: Http](docs/packages/packageHttp.md)). This documentation will exclusively focus on configuring authentication and how it behaves. ## How it works @@ -58,5 +58,5 @@ Headers are exclusively applied to the domains they are retrieved from. A plugin By default, when authentication requests are made, the authenticated client will behave similar to that of a normal browser. Meaning that if the server you are communicating with sets new cookies, the client will use those cookies instead. These new cookies are NOT saved to disk, meaning that whenever that plugin reloads the cookies will revert to those assigned at login. This behavior can be modified by using custom http clients as described in the http package documentation. - (See [Package: Http](_blank)) + (See [Package: Http](docs/packages/packageHttp.md)) From 0006da7385378d133d6c0118f5075a6a1c33fe27 Mon Sep 17 00:00:00 2001 From: Koen J Date: Tue, 25 Feb 2025 11:00:54 +0100 Subject: [PATCH 20/29] Implemented sync display names. --- app/build.gradle | 2 +- .../activities/SyncHomeActivity.kt | 3 +- .../mainactivity/main/VideoDetailView.kt | 2 +- .../futo/platformplayer/states/StateSync.kt | 21 +++++++- .../sync/internal/SyncSession.kt | 51 ++++++++++++++++--- .../sync/internal/SyncSocketSession.kt | 11 ++-- 6 files changed, 74 insertions(+), 16 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 866a47dd..8d55d000 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -197,7 +197,7 @@ dependencies { implementation 'org.jsoup:jsoup:1.15.3' implementation 'com.google.android.flexbox:flexbox:3.0.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' - implementation 'com.arthenica:ffmpeg-kit-full:5.1' + implementation 'com.arthenica:ffmpeg-kit-full:6.0-2.LTS' implementation 'org.jetbrains.kotlin:kotlin-reflect:1.9.0' implementation 'com.github.dhaval2404:imagepicker:2.1' implementation 'com.google.zxing:core:3.4.1' diff --git a/app/src/main/java/com/futo/platformplayer/activities/SyncHomeActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/SyncHomeActivity.kt index 2d9e51da..d1cd7706 100644 --- a/app/src/main/java/com/futo/platformplayer/activities/SyncHomeActivity.kt +++ b/app/src/main/java/com/futo/platformplayer/activities/SyncHomeActivity.kt @@ -101,7 +101,8 @@ class SyncHomeActivity : AppCompatActivity() { private fun updateDeviceView(syncDeviceView: SyncDeviceView, publicKey: String, session: SyncSession?): SyncDeviceView { val connected = session?.connected ?: false syncDeviceView.setLinkType(if (connected) LinkType.Local else LinkType.None) - .setName(publicKey) + .setName(session?.displayName ?: StateSync.instance.getCachedName(publicKey) ?: publicKey) + //TODO: also display public key? .setStatus(if (connected) "Connected" else "Disconnected") return syncDeviceView } 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 147eb2c2..b84e8c5e 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 @@ -922,7 +922,7 @@ class VideoDetailView : ConstraintLayout { } else if(devices.size == 1){ val device = devices.first(); Logger.i(TAG, "Send to device? (public key: ${device.remotePublicKey}): " + videoToSend.url) - UIDialogs.showConfirmationDialog(context, "Would you like to open\n[${videoToSend.name}]\non ${device.remotePublicKey}" , { + UIDialogs.showConfirmationDialog(context, "Would you like to open\n[${videoToSend.name}]\non '${device.displayName}'" , { Logger.i(TAG, "Send to device confirmed (public key: ${device.remotePublicKey}): " + videoToSend.url) fragment.lifecycleScope.launch(Dispatchers.IO) { diff --git a/app/src/main/java/com/futo/platformplayer/states/StateSync.kt b/app/src/main/java/com/futo/platformplayer/states/StateSync.kt index 0197b856..96c25f9d 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateSync.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateSync.kt @@ -44,6 +44,7 @@ import kotlin.system.measureTimeMillis class StateSync { private val _authorizedDevices = FragmentedStorage.get("authorized_devices") + private val _nameStorage = FragmentedStorage.get("sync_remembered_name_storage") private val _syncKeyPair = FragmentedStorage.get("sync_key_pair") private val _lastAddressStorage = FragmentedStorage.get("sync_last_address_storage") private val _syncSessionData = FragmentedStorage.get>("syncSessionData") @@ -305,12 +306,22 @@ class StateSync { synchronized(_sessions) { session = _sessions[s.remotePublicKey] if (session == null) { + val remoteDeviceName = synchronized(_nameStorage) { + _nameStorage.get(remotePublicKey) + } + session = SyncSession(remotePublicKey, onAuthorized = { it, isNewlyAuthorized, isNewSession -> if (!isNewSession) { return@SyncSession } - Logger.i(TAG, "${s.remotePublicKey} authorized") + it.remoteDeviceName?.let { remoteDeviceName -> + synchronized(_nameStorage) { + _nameStorage.setAndSave(remotePublicKey, remoteDeviceName) + } + } + + Logger.i(TAG, "${s.remotePublicKey} authorized (name: ${it.displayName})") synchronized(_lastAddressStorage) { _lastAddressStorage.setAndSave(remotePublicKey, s.remoteAddress) } @@ -341,7 +352,7 @@ class StateSync { deviceRemoved.emit(it.remotePublicKey) - }) + }, remoteDeviceName) _sessions[remotePublicKey] = session!! } session!!.addSocketSession(s) @@ -469,6 +480,12 @@ class StateSync { } } + fun getCachedName(publicKey: String): String? { + return synchronized(_nameStorage) { + _nameStorage.get(publicKey) + } + } + suspend fun delete(publicKey: String) { withContext(Dispatchers.IO) { try { diff --git a/app/src/main/java/com/futo/platformplayer/sync/internal/SyncSession.kt b/app/src/main/java/com/futo/platformplayer/sync/internal/SyncSession.kt index 6281ca23..8b5621e0 100644 --- a/app/src/main/java/com/futo/platformplayer/sync/internal/SyncSession.kt +++ b/app/src/main/java/com/futo/platformplayer/sync/internal/SyncSession.kt @@ -6,12 +6,10 @@ import com.futo.platformplayer.api.media.Serializer import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.models.HistoryVideo import com.futo.platformplayer.models.Subscription -import com.futo.platformplayer.models.SubscriptionGroup import com.futo.platformplayer.smartMerge import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.states.StateBackup import com.futo.platformplayer.states.StateHistory -import com.futo.platformplayer.states.StatePlayer import com.futo.platformplayer.states.StatePlaylists import com.futo.platformplayer.states.StateSubscriptionGroups import com.futo.platformplayer.states.StateSubscriptions @@ -30,6 +28,7 @@ import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import java.io.ByteArrayInputStream import java.nio.ByteBuffer +import java.nio.ByteOrder import java.time.Instant import java.time.OffsetDateTime import java.time.ZoneOffset @@ -53,6 +52,9 @@ class SyncSession : IAuthorizable { private val _id = UUID.randomUUID() private var _remoteId: UUID? = null private var _lastAuthorizedRemoteId: UUID? = null + var remoteDeviceName: String? = null + private set + val displayName: String get() = remoteDeviceName ?: remotePublicKey var connected: Boolean = false private set(v) { @@ -62,7 +64,7 @@ class SyncSession : IAuthorizable { } } - constructor(remotePublicKey: String, onAuthorized: (session: SyncSession, isNewlyAuthorized: Boolean, isNewSession: Boolean) -> Unit, onUnauthorized: (session: SyncSession) -> Unit, onConnectedChanged: (session: SyncSession, connected: Boolean) -> Unit, onClose: (session: SyncSession) -> Unit) { + constructor(remotePublicKey: String, onAuthorized: (session: SyncSession, isNewlyAuthorized: Boolean, isNewSession: Boolean) -> Unit, onUnauthorized: (session: SyncSession) -> Unit, onConnectedChanged: (session: SyncSession, connected: Boolean) -> Unit, onClose: (session: SyncSession) -> Unit, remoteDeviceName: String?) { this.remotePublicKey = remotePublicKey _onAuthorized = onAuthorized _onUnauthorized = onUnauthorized @@ -85,7 +87,20 @@ class SyncSession : IAuthorizable { fun authorize(socketSession: SyncSocketSession) { Logger.i(TAG, "Sent AUTHORIZED with session id $_id") - socketSession.send(Opcode.NOTIFY_AUTHORIZED.value, 0u, ByteBuffer.wrap(_id.toString().toByteArray())) + + if (socketSession.remoteVersion >= 3) { + val idStringBytes = _id.toString().toByteArray() + val nameBytes = "${android.os.Build.MANUFACTURER}-${android.os.Build.MODEL}".toByteArray() + val buffer = ByteArray(1 + idStringBytes.size + 1 + nameBytes.size) + socketSession.send(Opcode.NOTIFY_AUTHORIZED.value, 0u, ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).apply { + put(idStringBytes.size.toByte()) + put(idStringBytes) + put(nameBytes.size.toByte()) + put(nameBytes) + }.apply { flip() }) + } else { + socketSession.send(Opcode.NOTIFY_AUTHORIZED.value, 0u, ByteBuffer.wrap(_id.toString().toByteArray())) + } _authorized = true checkAuthorized() } @@ -138,15 +153,37 @@ class SyncSession : IAuthorizable { when (opcode) { Opcode.NOTIFY_AUTHORIZED.value -> { - val str = data.toUtf8String() - _remoteId = if (data.remaining() >= 0) UUID.fromString(str) else UUID.fromString("00000000-0000-0000-0000-000000000000") + if (socketSession.remoteVersion >= 3) { + val idByteCount = data.get().toInt() + if (idByteCount > 64) + throw Exception("Id should always be smaller than 64 bytes") + + val idBytes = ByteArray(idByteCount) + data.get(idBytes) + + val nameByteCount = data.get().toInt() + if (nameByteCount > 64) + throw Exception("Name should always be smaller than 64 bytes") + + val nameBytes = ByteArray(nameByteCount) + data.get(nameBytes) + + _remoteId = UUID.fromString(idBytes.toString(Charsets.UTF_8)) + remoteDeviceName = nameBytes.toString(Charsets.UTF_8) + } else { + val str = data.toUtf8String() + _remoteId = if (data.remaining() >= 0) UUID.fromString(str) else UUID.fromString("00000000-0000-0000-0000-000000000000") + remoteDeviceName = null + } + _remoteAuthorized = true - Logger.i(TAG, "Received AUTHORIZED with session id $_remoteId") + Logger.i(TAG, "Received AUTHORIZED with session id $_remoteId (device name: '${remoteDeviceName ?: "not set"}')") checkAuthorized() return } Opcode.NOTIFY_UNAUTHORIZED.value -> { _remoteId = null + remoteDeviceName = null _lastAuthorizedRemoteId = null _remoteAuthorized = false _onUnauthorized(this) diff --git a/app/src/main/java/com/futo/platformplayer/sync/internal/SyncSocketSession.kt b/app/src/main/java/com/futo/platformplayer/sync/internal/SyncSocketSession.kt index 4a1def91..c997cec4 100644 --- a/app/src/main/java/com/futo/platformplayer/sync/internal/SyncSocketSession.kt +++ b/app/src/main/java/com/futo/platformplayer/sync/internal/SyncSocketSession.kt @@ -46,6 +46,8 @@ class SyncSocketSession { val localPublicKey: String get() = _localPublicKey private val _onData: (session: SyncSocketSession, opcode: UByte, subOpcode: UByte, data: ByteBuffer) -> Unit var authorizable: IAuthorizable? = null + var remoteVersion: Int = -1 + private set val remoteAddress: String @@ -162,11 +164,12 @@ class SyncSocketSession { } private fun performVersionCheck() { - val CURRENT_VERSION = 2 + val CURRENT_VERSION = 3 + val MINIMUM_VERSION = 2 _outputStream.writeInt(CURRENT_VERSION) - val version = _inputStream.readInt() - Logger.i(TAG, "performVersionCheck (version = $version)") - if (version != CURRENT_VERSION) + remoteVersion = _inputStream.readInt() + Logger.i(TAG, "performVersionCheck (version = $remoteVersion)") + if (remoteVersion < MINIMUM_VERSION) throw Exception("Invalid version") } From edc2b3d2956ad0315fc09822be0e0be9c4c003b2 Mon Sep 17 00:00:00 2001 From: Koen J Date: Tue, 25 Feb 2025 15:32:30 +0100 Subject: [PATCH 21/29] Fixed issue where video reload would reset video timestamp. --- .../mainactivity/main/VideoDetailView.kt | 72 +++++++++++++------ .../views/video/FutoVideoPlayerBase.kt | 3 +- 2 files changed, 54 insertions(+), 21 deletions(-) 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 b84e8c5e..afda7722 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 @@ -579,6 +579,14 @@ class VideoDetailView : ConstraintLayout { _minimize_title.setOnClickListener { onMaximize.emit(false) }; _minimize_meta.setOnClickListener { onMaximize.emit(false) }; + _player.onStateChange.subscribe { + if (_player.activelyPlaying) { + Logger.i(TAG, "Play changed, resetting error counter _didTriggerDatasourceErrorCount = 0 (_player.activelyPlaying: ${_player.activelyPlaying})") + _didTriggerDatasourceErrorCount = 0; + _didTriggerDatasourceError = false; + } + } + _player.onPlayChanged.subscribe { if (StateCasting.instance.activeDevice == null) { handlePlayChanged(it); @@ -963,6 +971,7 @@ class VideoDetailView : ConstraintLayout { throw IllegalStateException("Expected media content, found ${video.contentType}"); withContext(Dispatchers.Main) { + _videoResumePositionMilliseconds = _player.position setVideoDetails(video); } } @@ -1265,8 +1274,6 @@ class VideoDetailView : ConstraintLayout { @OptIn(ExperimentalCoroutinesApi::class) fun setVideoDetails(videoDetail: IPlatformVideoDetails, newVideo: Boolean = false) { Logger.i(TAG, "setVideoDetails (${videoDetail.name})") - _didTriggerDatasourceErrroCount = 0; - _didTriggerDatasourceError = false; _autoplayVideo = null Logger.i(TAG, "Autoplay video cleared (setVideoDetails)") @@ -1277,6 +1284,10 @@ class VideoDetailView : ConstraintLayout { _lastVideoSource = null; _lastAudioSource = null; _lastSubtitleSource = null; + + Logger.i(TAG, "_didTriggerDatasourceErrorCount reset to 0 because new video") + _didTriggerDatasourceErrorCount = 0; + _didTriggerDatasourceError = false; } if (videoDetail.datetime != null && videoDetail.datetime!! > OffsetDateTime.now()) @@ -1831,7 +1842,7 @@ class VideoDetailView : ConstraintLayout { } } - private var _didTriggerDatasourceErrroCount = 0; + private var _didTriggerDatasourceErrorCount = 0; private var _didTriggerDatasourceError = false; private fun onDataSourceError(exception: Throwable) { Logger.e(TAG, "onDataSourceError", exception); @@ -1841,32 +1852,53 @@ class VideoDetailView : ConstraintLayout { return; val config = currentVideo.sourceConfig; - if(_didTriggerDatasourceErrroCount <= 3) { + if(_didTriggerDatasourceErrorCount <= 3) { _didTriggerDatasourceError = true; - _didTriggerDatasourceErrroCount++; + _didTriggerDatasourceErrorCount++; + + UIDialogs.toast("Detected video error, attempting automatic reload (${_didTriggerDatasourceErrorCount})"); + Logger.i(TAG, "Block detected, attempting bypass (_didTriggerDatasourceErrorCount = ${_didTriggerDatasourceErrorCount})"); - UIDialogs.toast("Block detected, attempting bypass"); //return; fragment.lifecycleScope.launch(Dispatchers.IO) { - val newDetails = StatePlatform.instance.getContentDetails(currentVideo.url, true).await(); - val previousVideoSource = _lastVideoSource; - val previousAudioSource = _lastAudioSource; + try { + val newDetails = StatePlatform.instance.getContentDetails(currentVideo.url, true).await(); + val previousVideoSource = _lastVideoSource; + val previousAudioSource = _lastAudioSource; - if(newDetails is IPlatformVideoDetails) { - val newVideoSource = if(previousVideoSource != null) - VideoHelper.selectBestVideoSource(newDetails.video, previousVideoSource.height * previousVideoSource.width, FutoVideoPlayerBase.PREFERED_VIDEO_CONTAINERS); - else null; - val newAudioSource = if(previousAudioSource != null) - VideoHelper.selectBestAudioSource(newDetails.video, FutoVideoPlayerBase.PREFERED_AUDIO_CONTAINERS, previousAudioSource.language, previousAudioSource.bitrate.toLong()); - else null; - withContext(Dispatchers.Main) { - video = newDetails; - _player.setSource(newVideoSource, newAudioSource, true, true); + if (newDetails is IPlatformVideoDetails) { + val newVideoSource = if (previousVideoSource != null) + VideoHelper.selectBestVideoSource( + newDetails.video, + previousVideoSource.height * previousVideoSource.width, + FutoVideoPlayerBase.PREFERED_VIDEO_CONTAINERS + ); + else null; + val newAudioSource = if (previousAudioSource != null) + VideoHelper.selectBestAudioSource( + newDetails.video, + FutoVideoPlayerBase.PREFERED_AUDIO_CONTAINERS, + previousAudioSource.language, + previousAudioSource.bitrate.toLong() + ); + else null; + withContext(Dispatchers.Main) { + video = newDetails; + _player.setSource(newVideoSource, newAudioSource, true, true); + } + } + } catch (e: Throwable) { + Logger.e(TAG, "Failed to get video details, attempting retrying without reloading.", e) + fragment.lifecycleScope.launch(Dispatchers.Main) { + video?.let { + _videoResumePositionMilliseconds = _player.position + setVideoDetails(it, false) + } } } } } - else if(_didTriggerDatasourceErrroCount > 3) { + else if(_didTriggerDatasourceErrorCount > 3) { UIDialogs.showDialog(context, R.drawable.ic_error_pred, context.getString(R.string.media_error), context.getString(R.string.the_media_source_encountered_an_unauthorized_error_this_might_be_solved_by_a_plugin_reload_would_you_like_to_reload_experimental), 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 61366bf5..c872ca02 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 @@ -96,6 +96,7 @@ abstract class FutoVideoPlayerBase : RelativeLayout { val exoPlayerStateName: String; var playing: Boolean = false; + val activelyPlaying: Boolean get() = (exoPlayer?.player?.playbackState == Player.STATE_READY) && (exoPlayer?.player?.playWhenReady ?: false) val position: Long get() = exoPlayer?.player?.currentPosition ?: 0; val duration: Long get() = exoPlayer?.player?.duration ?: 0; @@ -829,7 +830,7 @@ abstract class FutoVideoPlayerBase : RelativeLayout { Logger.i(TAG, "onPlayerError error=$error error.errorCode=${error.errorCode} connectivityLoss"); when (error.errorCode) { - PlaybackException.ERROR_CODE_IO_BAD_HTTP_STATUS -> { + PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED, PlaybackException.ERROR_CODE_IO_BAD_HTTP_STATUS -> { Logger.w(TAG, "ERROR_CODE_IO_BAD_HTTP_STATUS ${error.cause?.javaClass?.simpleName}"); if(error.cause is HttpDataSource.InvalidResponseCodeException) { val cause = error.cause as HttpDataSource.InvalidResponseCodeException From 1bbfa7d39ecb3a8712f59e7d840b9f53d5deac67 Mon Sep 17 00:00:00 2001 From: Kelvin Date: Wed, 26 Feb 2025 21:29:06 +0100 Subject: [PATCH 22/29] WIP home filtering --- .../mainactivity/main/HomeFragment.kt | 43 ++++++++++- .../futo/platformplayer/views/ToggleBar.kt | 74 +++++++++++++++++++ app/src/main/res/layout/view_toggle_bar.xml | 16 ++++ app/src/main/res/values/strings.xml | 2 +- 4 files changed, 130 insertions(+), 5 deletions(-) create mode 100644 app/src/main/java/com/futo/platformplayer/views/ToggleBar.kt create mode 100644 app/src/main/res/layout/view_toggle_bar.xml 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 26210bc8..9cdac8f9 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 @@ -23,6 +23,7 @@ import com.futo.platformplayer.states.StateMeta import com.futo.platformplayer.states.StatePlatform import com.futo.platformplayer.views.FeedStyle import com.futo.platformplayer.views.NoResultsView +import com.futo.platformplayer.views.ToggleBar import com.futo.platformplayer.views.adapters.ContentPreviewViewHolder import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader import com.futo.platformplayer.views.adapters.InsertedViewHolder @@ -94,6 +95,8 @@ class HomeFragment : MainFragment() { class HomeView : ContentFeedView { override val feedStyle: FeedStyle get() = Settings.instance.home.getHomeFeedStyle(); + private var _toggleBar: ToggleBar? = null; + private val _taskGetPager: TaskHandler>; override val shouldShowTimeBar: Boolean get() = Settings.instance.home.progressBar @@ -127,6 +130,8 @@ class HomeFragment : MainFragment() { }, fragment); }; + initializeToolbarContent(); + setPreviewsEnabled(Settings.instance.home.previewFeedItems); showAnnouncementView() } @@ -201,13 +206,43 @@ class HomeFragment : MainFragment() { loadResults(); } - override fun filterResults(results: List): List { - return results.filter { !StateMeta.instance.isVideoHidden(it.url) && !StateMeta.instance.isCreatorHidden(it.author.url) }; + private val _filterLock = Object(); + private var _toggleRecent = false; + fun initializeToolbarContent() { + //Not stable enough with current viewport paging, doesn't work with less results, and reloads content instead of just re-filtering existing + /* + _toggleBar = ToggleBar(context).apply { + layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); + } + synchronized(_filterLock) { + _toggleBar?.setToggles( + //TODO: loadResults needs to be replaced with an internal reload of the current content + ToggleBar.Toggle("Recent", _toggleRecent) { _toggleRecent = it; loadResults(false) } + ) + } + + _toolbarContentView.addView(_toggleBar, 0); + */ } - private fun loadResults() { + override fun filterResults(results: List): List { + return results.filter { + if(StateMeta.instance.isVideoHidden(it.url)) + return@filter false; + if(StateMeta.instance.isCreatorHidden(it.author.url)) + return@filter false; + + if(_toggleRecent && (it.datetime?.getNowDiffHours() ?: 0) > 23) { + return@filter false; + } + + return@filter true; + }; + } + + private fun loadResults(withRefetch: Boolean = true) { setLoading(true); - _taskGetPager.run(true); + _taskGetPager.run(withRefetch); } private fun loadedResult(pager : IPager) { if (pager is EmptyPager) { diff --git a/app/src/main/java/com/futo/platformplayer/views/ToggleBar.kt b/app/src/main/java/com/futo/platformplayer/views/ToggleBar.kt new file mode 100644 index 00000000..be3d8df8 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/ToggleBar.kt @@ -0,0 +1,74 @@ +package com.futo.platformplayer.views + +import android.content.Context +import android.util.AttributeSet +import android.view.View +import android.widget.LinearLayout +import androidx.lifecycle.findViewTreeLifecycleOwner +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.RecyclerView +import com.futo.platformplayer.R +import com.futo.platformplayer.Settings +import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.api.media.models.channels.SerializedChannel +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.models.Subscription +import com.futo.platformplayer.models.SubscriptionGroup +import com.futo.platformplayer.states.StateSubscriptionGroups +import com.futo.platformplayer.states.StateSubscriptions +import com.futo.platformplayer.views.AnyAdapterView.Companion.asAny +import com.futo.platformplayer.views.others.ToggleTagView +import com.futo.platformplayer.views.adapters.viewholders.SubscriptionBarViewHolder +import com.futo.platformplayer.views.adapters.viewholders.SubscriptionGroupBarViewHolder +import com.futo.platformplayer.views.subscriptions.SubscriptionExploreButton +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +class ToggleBar : LinearLayout { + private val _tagsContainer: LinearLayout; + + override fun onAttachedToWindow() { + super.onAttachedToWindow(); + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow(); + StateSubscriptionGroups.instance.onGroupsChanged.remove(this); + } + + constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) { + inflate(context, R.layout.view_toggle_bar, this); + + _tagsContainer = findViewById(R.id.container_tags); + } + + fun setToggles(vararg buttons: Toggle) { + _tagsContainer.removeAllViews(); + for(button in buttons) { + _tagsContainer.addView(ToggleTagView(context).apply { + this.setInfo(button.name, button.isActive); + this.onClick.subscribe { button.action(it); }; + }); + } + } + + class Toggle { + val name: String; + val icon: Int; + val action: (Boolean)->Unit; + val isActive: Boolean; + + constructor(name: String, icon: Int, isActive: Boolean = false, action: (Boolean)->Unit) { + this.name = name; + this.icon = icon; + this.action = action; + this.isActive = isActive; + } + constructor(name: String, isActive: Boolean = false, action: (Boolean)->Unit) { + this.name = name; + this.icon = 0; + this.action = action; + this.isActive = isActive; + } + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/view_toggle_bar.xml b/app/src/main/res/layout/view_toggle_bar.xml new file mode 100644 index 00000000..3da2f363 --- /dev/null +++ b/app/src/main/res/layout/view_toggle_bar.xml @@ -0,0 +1,16 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e7026f7a..d4df1905 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -418,7 +418,7 @@ Log Level Logging Sync Grayjay - Sync your settings across multiple devices + Sync your data across multiple devices Manage Polycentric identity Manage your Polycentric identity Manual check From 442272f51788a4172bd0628d6be9f0aabefd77ab Mon Sep 17 00:00:00 2001 From: Koen J Date: Thu, 27 Feb 2025 10:38:03 +0100 Subject: [PATCH 23/29] SettingsActivity can now be landscape. --- app/src/main/AndroidManifest.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index afd659a7..c9917a2d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -156,7 +156,6 @@ android:theme="@style/Theme.FutoVideo.NoActionBar" /> Date: Thu, 27 Feb 2025 14:34:51 +0100 Subject: [PATCH 24/29] submods --- app/src/stable/assets/sources/apple-podcasts | 2 +- app/src/stable/assets/sources/bilibili | 2 +- app/src/stable/assets/sources/bitchute | 2 +- app/src/stable/assets/sources/dailymotion | 2 +- app/src/stable/assets/sources/kick | 2 +- app/src/stable/assets/sources/nebula | 2 +- app/src/stable/assets/sources/odysee | 2 +- app/src/stable/assets/sources/patreon | 2 +- app/src/stable/assets/sources/peertube | 2 +- app/src/stable/assets/sources/rumble | 2 +- app/src/stable/assets/sources/soundcloud | 2 +- app/src/stable/assets/sources/spotify | 2 +- app/src/stable/assets/sources/twitch | 2 +- app/src/stable/assets/sources/youtube | 2 +- app/src/unstable/assets/sources/apple-podcasts | 2 +- app/src/unstable/assets/sources/bilibili | 2 +- app/src/unstable/assets/sources/bitchute | 2 +- app/src/unstable/assets/sources/dailymotion | 2 +- app/src/unstable/assets/sources/kick | 2 +- app/src/unstable/assets/sources/nebula | 2 +- app/src/unstable/assets/sources/odysee | 2 +- app/src/unstable/assets/sources/patreon | 2 +- app/src/unstable/assets/sources/peertube | 2 +- app/src/unstable/assets/sources/rumble | 2 +- app/src/unstable/assets/sources/soundcloud | 2 +- app/src/unstable/assets/sources/spotify | 2 +- app/src/unstable/assets/sources/twitch | 2 +- app/src/unstable/assets/sources/youtube | 2 +- 28 files changed, 28 insertions(+), 28 deletions(-) diff --git a/app/src/stable/assets/sources/apple-podcasts b/app/src/stable/assets/sources/apple-podcasts index 090104c7..07e39f9d 160000 --- a/app/src/stable/assets/sources/apple-podcasts +++ b/app/src/stable/assets/sources/apple-podcasts @@ -1 +1 @@ -Subproject commit 090104c7fa112d9772f43c7c2620e8c2cf3c9d6a +Subproject commit 07e39f9df71b1937adf5bfb718a115fc232aa6f8 diff --git a/app/src/stable/assets/sources/bilibili b/app/src/stable/assets/sources/bilibili index 13b30fd7..ce0571bd 160000 --- a/app/src/stable/assets/sources/bilibili +++ b/app/src/stable/assets/sources/bilibili @@ -1 +1 @@ -Subproject commit 13b30fd76e30a60c114c97b876542f7f106b5881 +Subproject commit ce0571bdeaed4e341351ef477ef4b6599aa4d0fb diff --git a/app/src/stable/assets/sources/bitchute b/app/src/stable/assets/sources/bitchute index 7f869aa4..3fbd872a 160000 --- a/app/src/stable/assets/sources/bitchute +++ b/app/src/stable/assets/sources/bitchute @@ -1 +1 @@ -Subproject commit 7f869aa4b117214095feb367d38414402cd08417 +Subproject commit 3fbd872ad8bd7df62c5fbec7437e1200d82b74e1 diff --git a/app/src/stable/assets/sources/dailymotion b/app/src/stable/assets/sources/dailymotion index d00c7ff8..b34134ca 160000 --- a/app/src/stable/assets/sources/dailymotion +++ b/app/src/stable/assets/sources/dailymotion @@ -1 +1 @@ -Subproject commit d00c7ff8e557d8b5624c162e4e554f65625c5e29 +Subproject commit b34134ca2dbb1662b060b4a67f14e7c5d077889d diff --git a/app/src/stable/assets/sources/kick b/app/src/stable/assets/sources/kick index 8d957b6f..2046944c 160000 --- a/app/src/stable/assets/sources/kick +++ b/app/src/stable/assets/sources/kick @@ -1 +1 @@ -Subproject commit 8d957b6fc4f354f4ab68d3cb2d1a7fa19323edeb +Subproject commit 2046944c18f48c15dfbea82f3f89d7ba6dce5e14 diff --git a/app/src/stable/assets/sources/nebula b/app/src/stable/assets/sources/nebula index 9e6dcf09..f30a3bfc 160000 --- a/app/src/stable/assets/sources/nebula +++ b/app/src/stable/assets/sources/nebula @@ -1 +1 @@ -Subproject commit 9e6dcf093538511eac56dc44a32b99139f1f1005 +Subproject commit f30a3bfc0f6a894d816ab7fa732b8f63eb54b84e diff --git a/app/src/stable/assets/sources/odysee b/app/src/stable/assets/sources/odysee index 04b4d8ed..f2f83344 160000 --- a/app/src/stable/assets/sources/odysee +++ b/app/src/stable/assets/sources/odysee @@ -1 +1 @@ -Subproject commit 04b4d8ed3163b7146bb58c418c201899e04e34cb +Subproject commit f2f83344ebc905b36c0689bfef407bb95e6d9af0 diff --git a/app/src/stable/assets/sources/patreon b/app/src/stable/assets/sources/patreon index 9c835e07..e5dce87c 160000 --- a/app/src/stable/assets/sources/patreon +++ b/app/src/stable/assets/sources/patreon @@ -1 +1 @@ -Subproject commit 9c835e075c66ea014e544d9fe35fbb317d72a196 +Subproject commit e5dce87c9d8ae7571df40a6fa252404e18b963f1 diff --git a/app/src/stable/assets/sources/peertube b/app/src/stable/assets/sources/peertube index 20fd03d9..2bcab14d 160000 --- a/app/src/stable/assets/sources/peertube +++ b/app/src/stable/assets/sources/peertube @@ -1 +1 @@ -Subproject commit 20fd03d9847b308d81ce474144bee79b04385477 +Subproject commit 2bcab14d01a564aa8ab9218de54042fc68b9ee76 diff --git a/app/src/stable/assets/sources/rumble b/app/src/stable/assets/sources/rumble index b9e6259f..a32dbb62 160000 --- a/app/src/stable/assets/sources/rumble +++ b/app/src/stable/assets/sources/rumble @@ -1 +1 @@ -Subproject commit b9e6259f4eedd41d1838506d32d240e177c1feff +Subproject commit a32dbb626aacfc6264e505cd5c7f34dd8a60edfc diff --git a/app/src/stable/assets/sources/soundcloud b/app/src/stable/assets/sources/soundcloud index a72aeb85..ae47f2ea 160000 --- a/app/src/stable/assets/sources/soundcloud +++ b/app/src/stable/assets/sources/soundcloud @@ -1 +1 @@ -Subproject commit a72aeb85d0fc0c17382cb1a7066fe4ec8b63691c +Subproject commit ae47f2eaacaf2879405435358965c47eb3d48096 diff --git a/app/src/stable/assets/sources/spotify b/app/src/stable/assets/sources/spotify index eb231ade..0d05e35c 160000 --- a/app/src/stable/assets/sources/spotify +++ b/app/src/stable/assets/sources/spotify @@ -1 +1 @@ -Subproject commit eb231adeae7acd0ed8b14e2ebc2b93424ac6811c +Subproject commit 0d05e35cfc81acfa78594c91c381b79694aaf86d diff --git a/app/src/stable/assets/sources/twitch b/app/src/stable/assets/sources/twitch index 1b2833cd..a75e8460 160000 --- a/app/src/stable/assets/sources/twitch +++ b/app/src/stable/assets/sources/twitch @@ -1 +1 @@ -Subproject commit 1b2833cdf22afec8b1177bf8bc3e5f83bc014e37 +Subproject commit a75e846045a7882002dd7a6bfa83550f52d9dbab diff --git a/app/src/stable/assets/sources/youtube b/app/src/stable/assets/sources/youtube index 8f8774a7..857c147b 160000 --- a/app/src/stable/assets/sources/youtube +++ b/app/src/stable/assets/sources/youtube @@ -1 +1 @@ -Subproject commit 8f8774a782aa49889774920688de371f28317ca6 +Subproject commit 857c147b3a3d3e9d0a79c47f1bd5813e08ed2daf diff --git a/app/src/unstable/assets/sources/apple-podcasts b/app/src/unstable/assets/sources/apple-podcasts index 090104c7..07e39f9d 160000 --- a/app/src/unstable/assets/sources/apple-podcasts +++ b/app/src/unstable/assets/sources/apple-podcasts @@ -1 +1 @@ -Subproject commit 090104c7fa112d9772f43c7c2620e8c2cf3c9d6a +Subproject commit 07e39f9df71b1937adf5bfb718a115fc232aa6f8 diff --git a/app/src/unstable/assets/sources/bilibili b/app/src/unstable/assets/sources/bilibili index 13b30fd7..ce0571bd 160000 --- a/app/src/unstable/assets/sources/bilibili +++ b/app/src/unstable/assets/sources/bilibili @@ -1 +1 @@ -Subproject commit 13b30fd76e30a60c114c97b876542f7f106b5881 +Subproject commit ce0571bdeaed4e341351ef477ef4b6599aa4d0fb diff --git a/app/src/unstable/assets/sources/bitchute b/app/src/unstable/assets/sources/bitchute index 7f869aa4..3fbd872a 160000 --- a/app/src/unstable/assets/sources/bitchute +++ b/app/src/unstable/assets/sources/bitchute @@ -1 +1 @@ -Subproject commit 7f869aa4b117214095feb367d38414402cd08417 +Subproject commit 3fbd872ad8bd7df62c5fbec7437e1200d82b74e1 diff --git a/app/src/unstable/assets/sources/dailymotion b/app/src/unstable/assets/sources/dailymotion index d00c7ff8..b34134ca 160000 --- a/app/src/unstable/assets/sources/dailymotion +++ b/app/src/unstable/assets/sources/dailymotion @@ -1 +1 @@ -Subproject commit d00c7ff8e557d8b5624c162e4e554f65625c5e29 +Subproject commit b34134ca2dbb1662b060b4a67f14e7c5d077889d diff --git a/app/src/unstable/assets/sources/kick b/app/src/unstable/assets/sources/kick index 8d957b6f..2046944c 160000 --- a/app/src/unstable/assets/sources/kick +++ b/app/src/unstable/assets/sources/kick @@ -1 +1 @@ -Subproject commit 8d957b6fc4f354f4ab68d3cb2d1a7fa19323edeb +Subproject commit 2046944c18f48c15dfbea82f3f89d7ba6dce5e14 diff --git a/app/src/unstable/assets/sources/nebula b/app/src/unstable/assets/sources/nebula index 9e6dcf09..f30a3bfc 160000 --- a/app/src/unstable/assets/sources/nebula +++ b/app/src/unstable/assets/sources/nebula @@ -1 +1 @@ -Subproject commit 9e6dcf093538511eac56dc44a32b99139f1f1005 +Subproject commit f30a3bfc0f6a894d816ab7fa732b8f63eb54b84e diff --git a/app/src/unstable/assets/sources/odysee b/app/src/unstable/assets/sources/odysee index 04b4d8ed..f2f83344 160000 --- a/app/src/unstable/assets/sources/odysee +++ b/app/src/unstable/assets/sources/odysee @@ -1 +1 @@ -Subproject commit 04b4d8ed3163b7146bb58c418c201899e04e34cb +Subproject commit f2f83344ebc905b36c0689bfef407bb95e6d9af0 diff --git a/app/src/unstable/assets/sources/patreon b/app/src/unstable/assets/sources/patreon index 9c835e07..e5dce87c 160000 --- a/app/src/unstable/assets/sources/patreon +++ b/app/src/unstable/assets/sources/patreon @@ -1 +1 @@ -Subproject commit 9c835e075c66ea014e544d9fe35fbb317d72a196 +Subproject commit e5dce87c9d8ae7571df40a6fa252404e18b963f1 diff --git a/app/src/unstable/assets/sources/peertube b/app/src/unstable/assets/sources/peertube index 20fd03d9..2bcab14d 160000 --- a/app/src/unstable/assets/sources/peertube +++ b/app/src/unstable/assets/sources/peertube @@ -1 +1 @@ -Subproject commit 20fd03d9847b308d81ce474144bee79b04385477 +Subproject commit 2bcab14d01a564aa8ab9218de54042fc68b9ee76 diff --git a/app/src/unstable/assets/sources/rumble b/app/src/unstable/assets/sources/rumble index b9e6259f..a32dbb62 160000 --- a/app/src/unstable/assets/sources/rumble +++ b/app/src/unstable/assets/sources/rumble @@ -1 +1 @@ -Subproject commit b9e6259f4eedd41d1838506d32d240e177c1feff +Subproject commit a32dbb626aacfc6264e505cd5c7f34dd8a60edfc diff --git a/app/src/unstable/assets/sources/soundcloud b/app/src/unstable/assets/sources/soundcloud index a72aeb85..ae47f2ea 160000 --- a/app/src/unstable/assets/sources/soundcloud +++ b/app/src/unstable/assets/sources/soundcloud @@ -1 +1 @@ -Subproject commit a72aeb85d0fc0c17382cb1a7066fe4ec8b63691c +Subproject commit ae47f2eaacaf2879405435358965c47eb3d48096 diff --git a/app/src/unstable/assets/sources/spotify b/app/src/unstable/assets/sources/spotify index eb231ade..0d05e35c 160000 --- a/app/src/unstable/assets/sources/spotify +++ b/app/src/unstable/assets/sources/spotify @@ -1 +1 @@ -Subproject commit eb231adeae7acd0ed8b14e2ebc2b93424ac6811c +Subproject commit 0d05e35cfc81acfa78594c91c381b79694aaf86d diff --git a/app/src/unstable/assets/sources/twitch b/app/src/unstable/assets/sources/twitch index 1b2833cd..a75e8460 160000 --- a/app/src/unstable/assets/sources/twitch +++ b/app/src/unstable/assets/sources/twitch @@ -1 +1 @@ -Subproject commit 1b2833cdf22afec8b1177bf8bc3e5f83bc014e37 +Subproject commit a75e846045a7882002dd7a6bfa83550f52d9dbab diff --git a/app/src/unstable/assets/sources/youtube b/app/src/unstable/assets/sources/youtube index 8f8774a7..857c147b 160000 --- a/app/src/unstable/assets/sources/youtube +++ b/app/src/unstable/assets/sources/youtube @@ -1 +1 @@ -Subproject commit 8f8774a782aa49889774920688de371f28317ca6 +Subproject commit 857c147b3a3d3e9d0a79c47f1bd5813e08ed2daf From bbeb9b83a0ba4538107758ab993566aa625749dc Mon Sep 17 00:00:00 2001 From: Koen J Date: Wed, 5 Mar 2025 11:58:09 +0100 Subject: [PATCH 25/29] Removed dynamic Polycentric calls. --- .../views/adapters/PlaylistView.kt | 43 ----------- .../views/adapters/SubscriptionViewHolder.kt | 41 ---------- .../adapters/feedtypes/PreviewPostView.kt | 43 ----------- .../adapters/feedtypes/PreviewVideoView.kt | 71 ----------------- .../viewholders/CreatorBarViewHolder.kt | 76 ------------------- .../adapters/viewholders/CreatorViewHolder.kt | 39 ---------- .../viewholders/SubscriptionBarViewHolder.kt | 38 ---------- .../main/res/layout/list_locked_thumbnail.xml | 17 +---- .../main/res/layout/list_playlist_feed.xml | 17 +---- app/src/main/res/layout/list_post_preview.xml | 18 +---- .../main/res/layout/list_post_thumbnail.xml | 19 +---- .../main/res/layout/list_video_thumbnail.xml | 17 +---- .../layout/list_video_thumbnail_nested.xml | 19 +---- 13 files changed, 13 insertions(+), 445 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/PlaylistView.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/PlaylistView.kt index b606bf26..c8d86b14 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/PlaylistView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/PlaylistView.kt @@ -29,21 +29,12 @@ open class PlaylistView : LinearLayout { protected val _imageThumbnail: ImageView protected val _imageChannel: ImageView? protected val _creatorThumbnail: CreatorThumbnail? - protected val _imageNeopassChannel: ImageView?; protected val _platformIndicator: PlatformIndicator; protected val _textPlaylistName: TextView protected val _textVideoCount: TextView protected val _textVideoCountLabel: TextView; protected val _textPlaylistItems: TextView protected val _textChannelName: TextView - protected var _neopassAnimator: ObjectAnimator? = null; - - private val _taskLoadValidClaims = TaskHandler(StateApp.instance.scopeGetter, - { PolycentricCache.instance.getValidClaimsAsync(it).await() }) - .success { it -> updateClaimsLayout(it, animate = true) } - .exception { - Logger.w(TAG, "Failed to load claims.", it); - }; val onPlaylistClicked = Event1(); val onChannelClicked = Event1(); @@ -66,7 +57,6 @@ open class PlaylistView : LinearLayout { _textVideoCountLabel = findViewById(R.id.text_video_count_label); _textChannelName = findViewById(R.id.text_channel_name); _textPlaylistItems = findViewById(R.id.text_playlist_items); - _imageNeopassChannel = findViewById(R.id.image_neopass_channel); setOnClickListener { onOpenClicked() }; _imageChannel?.setOnClickListener { currentPlaylist?.let { onChannelClicked.emit(it.author) } }; @@ -88,20 +78,6 @@ open class PlaylistView : LinearLayout { open fun bind(content: IPlatformContent) { - _taskLoadValidClaims.cancel(); - - if (content.author.id.claimType > 0) { - val cachedClaims = PolycentricCache.instance.getCachedValidClaims(content.author.id); - if (cachedClaims != null) { - updateClaimsLayout(cachedClaims, animate = false); - } else { - updateClaimsLayout(null, animate = false); - _taskLoadValidClaims.run(content.author.id); - } - } else { - updateClaimsLayout(null, animate = false); - } - isClickable = true; _imageChannel?.let { @@ -155,25 +131,6 @@ open class PlaylistView : LinearLayout { } } - private fun updateClaimsLayout(claims: PolycentricCache.CachedOwnedClaims?, animate: Boolean) { - _neopassAnimator?.cancel(); - _neopassAnimator = null; - - val firstClaim = claims?.ownedClaims?.firstOrNull(); - val harborAvailable = firstClaim != null - if (harborAvailable) { - _imageNeopassChannel?.visibility = View.VISIBLE - if (animate) { - _neopassAnimator = ObjectAnimator.ofFloat(_imageNeopassChannel, "alpha", 0.0f, 1.0f).setDuration(500) - _neopassAnimator?.start() - } - } else { - _imageNeopassChannel?.visibility = View.GONE - } - - _creatorThumbnail?.setHarborAvailable(harborAvailable, animate, firstClaim?.system?.toProto()) - } - companion object { private val TAG = "VideoPreviewViewHolder" } diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/SubscriptionViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/SubscriptionViewHolder.kt index 603f79d3..05a3f5e7 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/SubscriptionViewHolder.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/SubscriptionViewHolder.kt @@ -32,14 +32,6 @@ class SubscriptionViewHolder : ViewHolder { private val _platformIndicator : PlatformIndicator; private val _textMeta: TextView; - private val _taskLoadProfile = TaskHandler( - StateApp.instance.scopeGetter, - { PolycentricCache.instance.getProfileAsync(it) }) - .success { it -> onProfileLoaded(null, it, true) } - .exception { - Logger.w(TAG, "Failed to load profile.", it); - }; - var subscription: Subscription? = null private set; @@ -74,45 +66,12 @@ class SubscriptionViewHolder : ViewHolder { } fun bind(sub: Subscription) { - _taskLoadProfile.cancel(); - this.subscription = sub; _creatorThumbnail.setThumbnail(sub.channel.thumbnail, false); _textName.text = sub.channel.name; bindViewMetrics(sub); _platformIndicator.setPlatformFromClientID(sub.channel.id.pluginId); - - val cachedProfile = PolycentricCache.instance.getCachedProfile(sub.channel.url, true); - if (cachedProfile != null) { - onProfileLoaded(sub, cachedProfile, false); - if (cachedProfile.expired) { - _taskLoadProfile.run(sub.channel.id); - } - } else { - _taskLoadProfile.run(sub.channel.id); - } - } - - private fun onProfileLoaded(sub: Subscription?, cachedPolycentricProfile: PolycentricCache.CachedPolycentricProfile?, animate: Boolean) { - val dp_46 = 46.dp(itemView.context.resources); - val profile = cachedPolycentricProfile?.profile; - val avatar = profile?.systemState?.avatar?.selectBestImage(dp_46 * dp_46) - ?.let { it.toURLInfoSystemLinkUrl(profile.system.toProto(), it.process, profile.systemState.servers.toList()) }; - - if (avatar != null) { - _creatorThumbnail.setThumbnail(avatar, animate); - } else { - _creatorThumbnail.setThumbnail(this.subscription?.channel?.thumbnail, animate); - _creatorThumbnail.setHarborAvailable(profile != null, animate, profile?.system?.toProto()); - } - - if (profile != null) { - _textName.text = profile.systemState.username; - } - - if(sub != null) - bindViewMetrics(sub) } fun bindViewMetrics(sub: Subscription?) { diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewPostView.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewPostView.kt index 5a476421..cbf0b8a6 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewPostView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewPostView.kt @@ -44,7 +44,6 @@ class PreviewPostView : LinearLayout { private val _imageAuthorThumbnail: ImageView; private val _textAuthorName: TextView; - private val _imageNeopassChannel: ImageView; private val _textMetadata: TextView; private val _textTitle: TextView; private val _textDescription: TextView; @@ -64,15 +63,6 @@ class PreviewPostView : LinearLayout { private val _layoutComments: LinearLayout?; private val _textComments: TextView?; - private var _neopassAnimator: ObjectAnimator? = null; - - private val _taskLoadValidClaims = TaskHandler(StateApp.instance.scopeGetter, - { PolycentricCache.instance.getValidClaimsAsync(it).await() }) - .success { it -> updateClaimsLayout(it, animate = true) } - .exception { - Logger.w(TAG, "Failed to load claims.", it); - }; - val content: IPlatformContent? get() = _content; val onContentClicked = Event1(); @@ -83,7 +73,6 @@ class PreviewPostView : LinearLayout { _imageAuthorThumbnail = findViewById(R.id.image_author_thumbnail); _textAuthorName = findViewById(R.id.text_author_name); - _imageNeopassChannel = findViewById(R.id.image_neopass_channel); _textMetadata = findViewById(R.id.text_metadata); _textTitle = findViewById(R.id.text_title); _textDescription = findViewById(R.id.text_description); @@ -130,21 +119,8 @@ class PreviewPostView : LinearLayout { } fun bind(content: IPlatformContent) { - _taskLoadValidClaims.cancel(); _content = content; - if (content.author.id.claimType > 0) { - val cachedClaims = PolycentricCache.instance.getCachedValidClaims(content.author.id); - if (cachedClaims != null) { - updateClaimsLayout(cachedClaims, animate = false); - } else { - updateClaimsLayout(null, animate = false); - _taskLoadValidClaims.run(content.author.id); - } - } else { - updateClaimsLayout(null, animate = false); - } - _textAuthorName.text = content.author.name; _textMetadata.text = content.datetime?.toHumanNowDiffString()?.let { "$it ago" } ?: ""; @@ -292,25 +268,6 @@ class PreviewPostView : LinearLayout { }; } - private fun updateClaimsLayout(claims: PolycentricCache.CachedOwnedClaims?, animate: Boolean) { - _neopassAnimator?.cancel(); - _neopassAnimator = null; - - val harborAvailable = claims != null && !claims.ownedClaims.isNullOrEmpty(); - if (harborAvailable) { - _imageNeopassChannel.visibility = View.VISIBLE - if (animate) { - _neopassAnimator = ObjectAnimator.ofFloat(_imageNeopassChannel, "alpha", 0.0f, 1.0f).setDuration(500) - _neopassAnimator?.start() - } - } else { - _imageNeopassChannel.visibility = View.GONE - } - - //TODO: Necessary if we decide to use creator thumbnail with neopass indicator instead - //_creatorThumbnail?.setHarborAvailable(harborAvailable, animate) - } - companion object { val TAG = "PreviewPostView"; } 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 33066060..f2123832 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 @@ -47,7 +47,6 @@ open class PreviewVideoView : LinearLayout { protected val _imageVideo: ImageView protected val _imageChannel: ImageView? protected val _creatorThumbnail: CreatorThumbnail? - protected val _imageNeopassChannel: ImageView?; protected val _platformIndicator: PlatformIndicator; protected val _textVideoName: TextView protected val _textChannelName: TextView @@ -57,7 +56,6 @@ open class PreviewVideoView : LinearLayout { protected var _playerVideoThumbnail: FutoThumbnailPlayer? = null; protected val _containerLive: LinearLayout; protected val _playerContainer: FrameLayout; - protected var _neopassAnimator: ObjectAnimator? = null; protected val _layoutDownloaded: FrameLayout; protected val _button_add_to_queue : View; @@ -65,15 +63,6 @@ open class PreviewVideoView : LinearLayout { protected val _button_add_to : View; protected val _exoPlayer: PlayerManager?; - - private val _taskLoadProfile = TaskHandler( - StateApp.instance.scopeGetter, - { PolycentricCache.instance.getProfileAsync(it) }) - .success { it -> onProfileLoaded(it, true) } - .exception { - Logger.w(TAG, "Failed to load profile.", it); - }; - private val _timeBar: ProgressBar?; val onVideoClicked = Event2(); @@ -108,7 +97,6 @@ open class PreviewVideoView : LinearLayout { _button_add_to_queue = findViewById(R.id.button_add_to_queue); _button_add_to_watch_later = findViewById(R.id.button_add_to_watch_later); _button_add_to = findViewById(R.id.button_add_to); - _imageNeopassChannel = findViewById(R.id.image_neopass_channel); _layoutDownloaded = findViewById(R.id.layout_downloaded); _timeBar = findViewById(R.id.time_bar) @@ -160,15 +148,12 @@ open class PreviewVideoView : LinearLayout { open fun bind(content: IPlatformContent) { - _taskLoadProfile.cancel(); - isClickable = true; val isPlanned = (content.datetime?.getNowDiffSeconds() ?: 0) < 0; stopPreview(); - _imageNeopassChannel?.visibility = View.GONE; _creatorThumbnail?.setThumbnail(content.author.thumbnail, false); val thumbnail = content.author.thumbnail @@ -186,16 +171,6 @@ open class PreviewVideoView : LinearLayout { _textChannelName.text = content.author.name - val cachedProfile = PolycentricCache.instance.getCachedProfile(content.author.url, true); - if (cachedProfile != null) { - onProfileLoaded(cachedProfile, false); - if (cachedProfile.expired) { - _taskLoadProfile.run(content.author.id); - } - } else { - _taskLoadProfile.run(content.author.id); - } - _imageChannel?.clipToOutline = true; _textVideoName.text = content.name; @@ -335,52 +310,6 @@ open class PreviewVideoView : LinearLayout { _playerVideoThumbnail?.setMuteChangedListener(callback); } - private fun onProfileLoaded(cachedPolycentricProfile: PolycentricCache.CachedPolycentricProfile?, animate: Boolean) { - _neopassAnimator?.cancel(); - _neopassAnimator = null; - - val profile = cachedPolycentricProfile?.profile; - if (_creatorThumbnail != null) { - val dp_32 = 32.dp(context.resources); - val avatar = profile?.systemState?.avatar?.selectBestImage(dp_32 * dp_32) - ?.let { it.toURLInfoSystemLinkUrl(profile.system.toProto(), it.process, profile.systemState.servers.toList()) }; - - if (avatar != null) { - _creatorThumbnail.setThumbnail(avatar, animate); - } else { - _creatorThumbnail.setThumbnail(content?.author?.thumbnail, animate); - _creatorThumbnail.setHarborAvailable(profile != null, animate, profile?.system?.toProto()); - } - } else if (_imageChannel != null) { - val dp_28 = 28.dp(context.resources); - val avatar = profile?.systemState?.avatar?.selectBestImage(dp_28 * dp_28) - ?.let { it.toURLInfoSystemLinkUrl(profile.system.toProto(), it.process, profile.systemState.servers.toList()) }; - - if (avatar != null) { - _imageChannel.let { - Glide.with(_imageChannel) - .load(avatar) - .placeholder(R.drawable.placeholder_channel_thumbnail) - .into(_imageChannel); - } - - _imageNeopassChannel?.visibility = View.VISIBLE - if (animate) { - _neopassAnimator = ObjectAnimator.ofFloat(_imageNeopassChannel, "alpha", 0.0f, 1.0f).setDuration(500) - _neopassAnimator?.start() - } else { - _imageNeopassChannel?.alpha = 1.0f; - } - } else { - _imageNeopassChannel?.visibility = View.GONE - } - } - - if (profile != null) { - _textChannelName.text = profile.systemState.username - } - } - companion object { private val TAG = "VideoPreviewViewHolder" } diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/CreatorBarViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/CreatorBarViewHolder.kt index 14322506..897718bf 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/CreatorBarViewHolder.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/CreatorBarViewHolder.kt @@ -27,14 +27,6 @@ class CreatorBarViewHolder(private val _viewGroup: ViewGroup) : AnyAdapter.AnyVi val onClick = Event1(); - private val _taskLoadProfile = TaskHandler( - StateApp.instance.scopeGetter, - { PolycentricCache.instance.getProfileAsync(it) }) - .success { onProfileLoaded(it, true) } - .exception { - Logger.w(TAG, "Failed to load profile.", it); - }; - init { _creatorThumbnail = _view.findViewById(R.id.creator_thumbnail); _name = _view.findViewById(R.id.text_channel_name); @@ -45,40 +37,10 @@ class CreatorBarViewHolder(private val _viewGroup: ViewGroup) : AnyAdapter.AnyVi } override fun bind(value: IPlatformChannel) { - _taskLoadProfile.cancel(); - _channel = value; _creatorThumbnail.setThumbnail(value.thumbnail, false); _name.text = value.name; - - val cachedProfile = PolycentricCache.instance.getCachedProfile(value.url, true); - if (cachedProfile != null) { - onProfileLoaded(cachedProfile, false); - if (cachedProfile.expired) { - _taskLoadProfile.run(value.id); - } - } else { - _taskLoadProfile.run(value.id); - } - } - - private fun onProfileLoaded(cachedPolycentricProfile: PolycentricCache.CachedPolycentricProfile?, animate: Boolean) { - val dp_55 = 55.dp(itemView.context.resources) - val profile = cachedPolycentricProfile?.profile; - val avatar = profile?.systemState?.avatar?.selectBestImage(dp_55 * dp_55) - ?.let { it.toURLInfoSystemLinkUrl(profile.system.toProto(), it.process, profile.systemState.servers.toList()) }; - - if (avatar != null) { - _creatorThumbnail.setThumbnail(avatar, animate); - } else { - _creatorThumbnail.setThumbnail(_channel?.thumbnail, animate); - _creatorThumbnail.setHarborAvailable(profile != null, animate, profile?.system?.toProto()); - } - - if (profile != null) { - _name.text = profile.systemState.username; - } } companion object { @@ -94,14 +56,6 @@ class SelectableCreatorBarViewHolder(private val _viewGroup: ViewGroup) : AnyAda val onClick = Event1(); - private val _taskLoadProfile = TaskHandler( - StateApp.instance.scopeGetter, - { PolycentricCache.instance.getProfileAsync(it) }) - .success { onProfileLoaded(it, true) } - .exception { - Logger.w(TAG, "Failed to load profile.", it); - }; - init { _creatorThumbnail = _view.findViewById(R.id.creator_thumbnail); _name = _view.findViewById(R.id.text_channel_name); @@ -112,8 +66,6 @@ class SelectableCreatorBarViewHolder(private val _viewGroup: ViewGroup) : AnyAda } override fun bind(value: Selectable) { - _taskLoadProfile.cancel(); - _channel = value; if(value.active) @@ -123,34 +75,6 @@ class SelectableCreatorBarViewHolder(private val _viewGroup: ViewGroup) : AnyAda _creatorThumbnail.setThumbnail(value.channel.thumbnail, false); _name.text = value.channel.name; - - val cachedProfile = PolycentricCache.instance.getCachedProfile(value.channel.url, true); - if (cachedProfile != null) { - onProfileLoaded(cachedProfile, false); - if (cachedProfile.expired) { - _taskLoadProfile.run(value.channel.id); - } - } else { - _taskLoadProfile.run(value.channel.id); - } - } - - private fun onProfileLoaded(cachedPolycentricProfile: PolycentricCache.CachedPolycentricProfile?, animate: Boolean) { - val dp_55 = 55.dp(itemView.context.resources) - val profile = cachedPolycentricProfile?.profile; - val avatar = profile?.systemState?.avatar?.selectBestImage(dp_55 * dp_55) - ?.let { it.toURLInfoSystemLinkUrl(profile.system.toProto(), it.process, profile.systemState.servers.toList()) }; - - if (avatar != null) { - _creatorThumbnail.setThumbnail(avatar, animate); - } else { - _creatorThumbnail.setThumbnail(_channel?.channel?.thumbnail, animate); - _creatorThumbnail.setHarborAvailable(profile != null, animate, profile?.system?.toProto()); - } - - if (profile != null) { - _name.text = profile.systemState.username; - } } companion object { 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 5c57ffa9..b5784d4c 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 @@ -34,14 +34,6 @@ class CreatorViewHolder(private val _viewGroup: ViewGroup, private val _tiny: Bo val onClick = Event1(); - private val _taskLoadProfile = TaskHandler( - StateApp.instance.scopeGetter, - { PolycentricCache.instance.getProfileAsync(it) }) - .success { it -> onProfileLoaded(it, true) } - .exception { - Logger.w(TAG, "Failed to load profile.", it); - }; - init { _textName = _view.findViewById(R.id.text_channel_name); _creatorThumbnail = _view.findViewById(R.id.creator_thumbnail); @@ -61,21 +53,9 @@ class CreatorViewHolder(private val _viewGroup: ViewGroup, private val _tiny: Bo } override fun bind(value: PlatformAuthorLink) { - _taskLoadProfile.cancel(); - _creatorThumbnail.setThumbnail(value.thumbnail, false); _textName.text = value.name; - val cachedProfile = PolycentricCache.instance.getCachedProfile(value.url, true); - if (cachedProfile != null) { - onProfileLoaded(cachedProfile, false); - if (cachedProfile.expired) { - _taskLoadProfile.run(value.id); - } - } else { - _taskLoadProfile.run(value.id); - } - if(value.subscribers == null || (value.subscribers ?: 0) <= 0L) _textMetadata.visibility = View.GONE; else { @@ -87,25 +67,6 @@ class CreatorViewHolder(private val _viewGroup: ViewGroup, private val _tiny: Bo _authorLink = value; } - private fun onProfileLoaded(cachedPolycentricProfile: PolycentricCache.CachedPolycentricProfile?, animate: Boolean) { - val dp_61 = 61.dp(itemView.context.resources); - - val profile = cachedPolycentricProfile?.profile; - val avatar = profile?.systemState?.avatar?.selectBestImage(dp_61 * dp_61) - ?.let { it.toURLInfoSystemLinkUrl(profile.system.toProto(), it.process, profile.systemState.servers.toList()) }; - - if (avatar != null) { - _creatorThumbnail.setThumbnail(avatar, animate); - } else { - _creatorThumbnail.setThumbnail(_authorLink?.thumbnail, animate); - _creatorThumbnail.setHarborAvailable(profile != null, animate, profile?.system?.toProto()); - } - - if (profile != null) { - _textName.text = profile.systemState.username; - } - } - companion object { private const val TAG = "CreatorViewHolder"; } 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 2b6350c4..da491cf6 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 @@ -27,14 +27,6 @@ class SubscriptionBarViewHolder(private val _viewGroup: ViewGroup) : AnyAdapter. private var _subscription: Subscription? = null; private var _channel: SerializedChannel? = null; - private val _taskLoadProfile = TaskHandler( - StateApp.instance.scopeGetter, - { PolycentricCache.instance.getProfileAsync(it) }) - .success { onProfileLoaded(it, true) } - .exception { - Logger.w(TAG, "Failed to load profile.", it); - }; - val onClick = Event1(); init { @@ -47,44 +39,14 @@ class SubscriptionBarViewHolder(private val _viewGroup: ViewGroup) : AnyAdapter. } override fun bind(value: Subscription) { - _taskLoadProfile.cancel(); - _channel = value.channel; _creatorThumbnail.setThumbnail(value.channel.thumbnail, false); _name.text = value.channel.name; - val cachedProfile = PolycentricCache.instance.getCachedProfile(value.channel.url, true); - if (cachedProfile != null) { - onProfileLoaded(cachedProfile, false); - if (cachedProfile.expired) { - _taskLoadProfile.run(value.channel.id); - } - } else { - _taskLoadProfile.run(value.channel.id); - } - _subscription = value; } - private fun onProfileLoaded(cachedPolycentricProfile: PolycentricCache.CachedPolycentricProfile?, animate: Boolean) { - val dp_55 = 55.dp(itemView.context.resources) - val profile = cachedPolycentricProfile?.profile; - val avatar = profile?.systemState?.avatar?.selectBestImage(dp_55 * dp_55) - ?.let { it.toURLInfoSystemLinkUrl(profile.system.toProto(), it.process, profile.systemState.servers.toList()) }; - - if (avatar != null) { - _creatorThumbnail.setThumbnail(avatar, animate); - } else { - _creatorThumbnail.setThumbnail(_channel?.thumbnail, animate); - _creatorThumbnail.setHarborAvailable(profile != null, animate, profile?.system?.toProto()); - } - - if (profile != null) { - _name.text = profile.systemState.username; - } - } - companion object { private const val TAG = "SubscriptionBarViewHolder"; } diff --git a/app/src/main/res/layout/list_locked_thumbnail.xml b/app/src/main/res/layout/list_locked_thumbnail.xml index 462d65d4..f00e28f3 100644 --- a/app/src/main/res/layout/list_locked_thumbnail.xml +++ b/app/src/main/res/layout/list_locked_thumbnail.xml @@ -254,7 +254,7 @@ - - - - + app:layout_constraintTop_toBottomOf="@id/text_playlist_name" /> - - - - - - - - - - Date: Wed, 5 Mar 2025 17:04:48 +0100 Subject: [PATCH 26/29] Implemented new ApiMethods calls. --- .../platformplayer/Extensions_Polycentric.kt | 40 +- .../PolycentricCreateProfileActivity.kt | 6 +- .../PolycentricImportProfileActivity.kt | 4 +- .../activities/PolycentricProfileActivity.kt | 6 +- .../platformplayer/dialogs/CommentDialog.kt | 2 +- .../channel/tab/ChannelAboutFragment.kt | 6 +- .../channel/tab/ChannelContentsFragment.kt | 2 +- .../channel/tab/ChannelListFragment.kt | 2 +- .../tab/ChannelMonetizationFragment.kt | 2 +- .../channel/tab/IChannelTabFragment.kt | 2 +- .../mainactivity/main/ChannelFragment.kt | 54 +-- .../mainactivity/main/CommentsFragment.kt | 2 +- .../mainactivity/main/PostDetailFragment.kt | 30 +- .../mainactivity/main/VideoDetailView.kt | 62 +-- .../topbar/NavigationTopBarFragment.kt | 2 +- .../images/PolycentricModelLoader.java | 17 +- .../polycentric/PolycentricCache.kt | 353 ------------------ .../futo/platformplayer/states/StateCache.kt | 10 +- .../futo/platformplayer/states/StateMeta.kt | 2 +- .../platformplayer/states/StatePolycentric.kt | 56 +-- .../states/StateSubscriptionGroups.kt | 41 +- .../states/StateSubscriptions.kt | 7 - .../stores/CachedPolycentricProfileStorage.kt | 31 -- .../platformplayer/views/MonetizationView.kt | 5 +- .../futo/platformplayer/views/SupportView.kt | 2 +- .../views/adapters/ChannelViewPagerAdapter.kt | 2 +- .../views/adapters/CommentViewHolder.kt | 19 +- .../CommentWithReferenceViewHolder.kt | 2 +- .../views/adapters/PlaylistView.kt | 1 - .../views/adapters/SubscriptionViewHolder.kt | 1 - .../adapters/feedtypes/PreviewPostView.kt | 1 - .../adapters/feedtypes/PreviewVideoView.kt | 1 - .../viewholders/CreatorBarViewHolder.kt | 1 - .../adapters/viewholders/CreatorViewHolder.kt | 1 - .../viewholders/SubscriptionBarViewHolder.kt | 1 - .../SubscriptionGroupListViewHolder.kt | 1 - .../views/others/CreatorThumbnail.kt | 4 +- .../views/overlays/SupportOverlay.kt | 2 +- .../views/overlays/WebviewOverlay.kt | 2 - .../views/segments/CommentsList.kt | 2 +- dep/polycentricandroid | 2 +- 41 files changed, 124 insertions(+), 665 deletions(-) delete mode 100644 app/src/main/java/com/futo/platformplayer/polycentric/PolycentricCache.kt delete mode 100644 app/src/main/java/com/futo/platformplayer/stores/CachedPolycentricProfileStorage.kt diff --git a/app/src/main/java/com/futo/platformplayer/Extensions_Polycentric.kt b/app/src/main/java/com/futo/platformplayer/Extensions_Polycentric.kt index 74f1372e..139407ed 100644 --- a/app/src/main/java/com/futo/platformplayer/Extensions_Polycentric.kt +++ b/app/src/main/java/com/futo/platformplayer/Extensions_Polycentric.kt @@ -1,13 +1,13 @@ package com.futo.platformplayer import com.futo.platformplayer.logging.Logger -import com.futo.platformplayer.polycentric.PolycentricCache import com.futo.platformplayer.states.AnnouncementType import com.futo.platformplayer.states.StateAnnouncement import com.futo.platformplayer.states.StatePlatform import com.futo.polycentric.core.ProcessHandle import com.futo.polycentric.core.Store import com.futo.polycentric.core.SystemState +import com.futo.polycentric.core.base64UrlToByteArray import userpackage.Protocol import kotlin.math.abs import kotlin.math.min @@ -40,33 +40,25 @@ fun Protocol.ImageBundle?.selectHighestResolutionImage(): Protocol.ImageManifest return imageManifestsList.filter { it.byteCount < maximumFileSize }.maxByOrNull { abs(it.width * it.height) } } +fun String.getDataLinkFromUrl(): Protocol.URLInfoDataLink? { + val urlData = if (this.startsWith("polycentric://")) { + this.substring("polycentric://".length) + } else this; + + val urlBytes = urlData.base64UrlToByteArray(); + val urlInfo = Protocol.URLInfo.parseFrom(urlBytes); + if (urlInfo.urlType != 4L) { + return null + } + + val dataLink = Protocol.URLInfoDataLink.parseFrom(urlInfo.body); + return dataLink +} + fun Protocol.Claim.resolveChannelUrl(): String? { return StatePlatform.instance.resolveChannelUrlByClaimTemplates(this.claimType.toInt(), this.claimFieldsList.associate { Pair(it.key.toInt(), it.value) }) } fun Protocol.Claim.resolveChannelUrls(): List { return StatePlatform.instance.resolveChannelUrlsByClaimTemplates(this.claimType.toInt(), this.claimFieldsList.associate { Pair(it.key.toInt(), it.value) }) -} - -suspend fun ProcessHandle.fullyBackfillServersAnnounceExceptions() { - val systemState = SystemState.fromStorageTypeSystemState(Store.instance.getSystemState(system)) - if (!systemState.servers.contains(PolycentricCache.SERVER)) { - Logger.w("Backfill", "Polycentric prod server not added, adding it.") - addServer(PolycentricCache.SERVER) - } - - val exceptions = fullyBackfillServers() - for (pair in exceptions) { - val server = pair.key - val exception = pair.value - - StateAnnouncement.instance.registerAnnouncement( - "backfill-failed", - "Backfill failed", - "Failed to backfill server $server. $exception", - AnnouncementType.SESSION_RECURRING - ); - - Logger.e("Backfill", "Failed to backfill server $server.", exception) - } } \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/activities/PolycentricCreateProfileActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/PolycentricCreateProfileActivity.kt index d5fce50b..f7432c05 100644 --- a/app/src/main/java/com/futo/platformplayer/activities/PolycentricCreateProfileActivity.kt +++ b/app/src/main/java/com/futo/platformplayer/activities/PolycentricCreateProfileActivity.kt @@ -11,16 +11,16 @@ import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.lifecycleScope import com.futo.platformplayer.R import com.futo.platformplayer.UIDialogs -import com.futo.platformplayer.fullyBackfillServersAnnounceExceptions import com.futo.platformplayer.logging.Logger -import com.futo.platformplayer.polycentric.PolycentricCache import com.futo.platformplayer.polycentric.PolycentricStorage import com.futo.platformplayer.setNavigationBarColorAndIcons import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.states.StatePolycentric import com.futo.platformplayer.views.LoaderView +import com.futo.polycentric.core.ApiMethods import com.futo.polycentric.core.ProcessHandle import com.futo.polycentric.core.Store +import com.futo.polycentric.core.fullyBackfillServersAnnounceExceptions import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -87,7 +87,7 @@ class PolycentricCreateProfileActivity : AppCompatActivity() { Logger.e(TAG, "Failed to save process secret to secret storage.", e) } - processHandle.addServer(PolycentricCache.SERVER); + processHandle.addServer(ApiMethods.SERVER); processHandle.setUsername(username); StatePolycentric.instance.setProcessHandle(processHandle); } catch (e: Throwable) { 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 825463b3..ab6d70a3 100644 --- a/app/src/main/java/com/futo/platformplayer/activities/PolycentricImportProfileActivity.kt +++ b/app/src/main/java/com/futo/platformplayer/activities/PolycentricImportProfileActivity.kt @@ -12,12 +12,12 @@ import androidx.lifecycle.lifecycleScope import com.futo.platformplayer.R import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.logging.Logger -import com.futo.platformplayer.polycentric.PolycentricCache import com.futo.platformplayer.polycentric.PolycentricStorage import com.futo.platformplayer.setNavigationBarColorAndIcons import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.states.StatePolycentric import com.futo.platformplayer.views.overlays.LoaderOverlay +import com.futo.polycentric.core.ApiMethods import com.futo.polycentric.core.KeyPair import com.futo.polycentric.core.Process import com.futo.polycentric.core.ProcessSecret @@ -145,7 +145,7 @@ class PolycentricImportProfileActivity : AppCompatActivity() { } StatePolycentric.instance.setProcessHandle(processHandle); - processHandle.fullyBackfillClient(PolycentricCache.SERVER); + processHandle.fullyBackfillClient(ApiMethods.SERVER); withContext(Dispatchers.Main) { startActivity(Intent(this@PolycentricImportProfileActivity, PolycentricProfileActivity::class.java)); finish(); 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 e296a118..3493363e 100644 --- a/app/src/main/java/com/futo/platformplayer/activities/PolycentricProfileActivity.kt +++ b/app/src/main/java/com/futo/platformplayer/activities/PolycentricProfileActivity.kt @@ -21,10 +21,8 @@ import com.bumptech.glide.Glide import com.futo.platformplayer.R import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.dp -import com.futo.platformplayer.fullyBackfillServersAnnounceExceptions import com.futo.platformplayer.images.GlideHelper.Companion.crossfade import com.futo.platformplayer.logging.Logger -import com.futo.platformplayer.polycentric.PolycentricCache import com.futo.platformplayer.polycentric.PolycentricStorage import com.futo.platformplayer.selectBestImage import com.futo.platformplayer.setNavigationBarColorAndIcons @@ -32,8 +30,10 @@ import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.states.StatePolycentric import com.futo.platformplayer.views.buttons.BigButton import com.futo.platformplayer.views.overlays.LoaderOverlay +import com.futo.polycentric.core.ApiMethods import com.futo.polycentric.core.Store import com.futo.polycentric.core.SystemState +import com.futo.polycentric.core.fullyBackfillServersAnnounceExceptions import com.futo.polycentric.core.systemToURLInfoSystemLinkUrl import com.futo.polycentric.core.toBase64Url import com.futo.polycentric.core.toURLInfoSystemLinkUrl @@ -145,7 +145,7 @@ class PolycentricProfileActivity : AppCompatActivity() { lifecycleScope.launch(Dispatchers.IO) { try { - processHandle.fullyBackfillClient(PolycentricCache.SERVER) + processHandle.fullyBackfillClient(ApiMethods.SERVER) withContext(Dispatchers.Main) { updateUI(); diff --git a/app/src/main/java/com/futo/platformplayer/dialogs/CommentDialog.kt b/app/src/main/java/com/futo/platformplayer/dialogs/CommentDialog.kt index 9d0282f0..794c8537 100644 --- a/app/src/main/java/com/futo/platformplayer/dialogs/CommentDialog.kt +++ b/app/src/main/java/com/futo/platformplayer/dialogs/CommentDialog.kt @@ -22,7 +22,6 @@ import com.futo.platformplayer.api.media.models.comments.PolycentricPlatformComm import com.futo.platformplayer.api.media.models.ratings.RatingLikeDislikes import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.dp -import com.futo.platformplayer.fullyBackfillServersAnnounceExceptions import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.selectBestImage import com.futo.platformplayer.states.StateApp @@ -30,6 +29,7 @@ import com.futo.platformplayer.states.StatePolycentric import com.futo.polycentric.core.ClaimType import com.futo.polycentric.core.Store import com.futo.polycentric.core.SystemState +import com.futo.polycentric.core.fullyBackfillServersAnnounceExceptions import com.futo.polycentric.core.systemToURLInfoSystemLinkUrl import com.futo.polycentric.core.toURLInfoSystemLinkUrl import com.google.android.material.button.MaterialButton 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 8a412f75..777443c5 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 @@ -13,7 +13,6 @@ 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 @@ -21,6 +20,7 @@ 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.PolycentricProfile import com.futo.polycentric.core.toName import com.futo.polycentric.core.toURLInfoSystemLinkUrl @@ -134,9 +134,7 @@ class ChannelAboutFragment : Fragment, IChannelTabFragment { } } if(!map.containsKey("Harbor")) - this.context?.let { - map.set("Harbor", polycentricProfile.getHarborUrl(it)); - } + map.set("Harbor", polycentricProfile.getHarborUrl()); if (map.isNotEmpty()) setLinks(map, if (polycentricProfile.systemState.username.isNotBlank()) polycentricProfile.systemState.username else _lastChannel?.name ?: "") 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 a7e313e3..b26c9b35 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 @@ -29,7 +29,6 @@ import com.futo.platformplayer.engine.exceptions.PluginException import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException import com.futo.platformplayer.exceptions.ChannelException 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 @@ -39,6 +38,7 @@ 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.feedtypes.PreviewContentListAdapter +import com.futo.polycentric.core.PolycentricProfile import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlin.math.max 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 807fbd90..dc32acaa 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 @@ -16,12 +16,12 @@ import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.constructs.TaskHandler 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.PolycentricProfile class ChannelListFragment : Fragment, IChannelTabFragment { private var _channels: ArrayList = arrayListOf(); 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 53268d16..fd401042 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 @@ -8,8 +8,8 @@ import android.widget.TextView import androidx.fragment.app.Fragment import com.futo.platformplayer.R import com.futo.platformplayer.api.media.models.channels.IPlatformChannel -import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile import com.futo.platformplayer.views.SupportView +import com.futo.polycentric.core.PolycentricProfile class ChannelMonetizationFragment : Fragment, IChannelTabFragment { diff --git a/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/IChannelTabFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/IChannelTabFragment.kt index 2b615d25..3821b34c 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/IChannelTabFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/IChannelTabFragment.kt @@ -1,7 +1,7 @@ package com.futo.platformplayer.fragment.channel.tab import com.futo.platformplayer.api.media.models.channels.IPlatformChannel -import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile +import com.futo.polycentric.core.PolycentricProfile interface IChannelTabFragment { fun setChannel(channel: IPlatformChannel) 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 817a8ca2..6be65482 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 @@ -42,7 +42,6 @@ import com.futo.platformplayer.images.GlideHelper.Companion.crossfade import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.models.SearchType import com.futo.platformplayer.models.Subscription -import com.futo.platformplayer.polycentric.PolycentricCache import com.futo.platformplayer.selectBestImage import com.futo.platformplayer.selectHighestResolutionImage import com.futo.platformplayer.states.StatePlatform @@ -55,29 +54,14 @@ 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.OwnedClaim -import com.futo.polycentric.core.PublicKey -import com.futo.polycentric.core.Store -import com.futo.polycentric.core.SystemState -import com.futo.polycentric.core.systemToURLInfoSystemLinkUrl +import com.futo.polycentric.core.ApiMethods +import com.futo.polycentric.core.PolycentricProfile import com.futo.polycentric.core.toURLInfoSystemLinkUrl import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayoutMediator import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import kotlinx.serialization.Serializable - -@Serializable -data class PolycentricProfile( - val system: PublicKey, val systemState: SystemState, val ownedClaims: List -) { - fun getHarborUrl(context: Context): String{ - val systemState = SystemState.fromStorageTypeSystemState(Store.instance.getSystemState(system)); - val url = system.systemToURLInfoSystemLinkUrl(systemState.servers.asIterable()); - return "https://harbor.social/" + url.substring("polycentric://".length); - } -} class ChannelFragment : MainFragment() { override val isMainView: Boolean = true @@ -144,15 +128,14 @@ class ChannelFragment : MainFragment() { private val _onPageChangeCallback = object : ViewPager2.OnPageChangeCallback() {} - private val _taskLoadPolycentricProfile: TaskHandler + private val _taskLoadPolycentricProfile: TaskHandler private val _taskGetChannel: TaskHandler init { inflater.inflate(R.layout.fragment_channel, this) - _taskLoadPolycentricProfile = - TaskHandler({ fragment.lifecycleScope }, + _taskLoadPolycentricProfile = TaskHandler({ fragment.lifecycleScope }, { id -> - return@TaskHandler PolycentricCache.instance.getProfileAsync(id) + return@TaskHandler ApiMethods.getPolycentricProfileByClaim(ApiMethods.SERVER, ApiMethods.FUTO_TRUST_ROOT, id.claimFieldType.toLong(), id.claimType.toLong(), id.value!!) }).success { setPolycentricProfile(it, animate = true) }.exception { Logger.w(TAG, "Failed to load polycentric profile.", it) } @@ -328,7 +311,7 @@ class ChannelFragment : MainFragment() { _creatorThumbnail.setThumbnail(parameter.thumbnail, true) Glide.with(_imageBanner).clear(_imageBanner) - loadPolycentricProfile(parameter.id, parameter.url) + loadPolycentricProfile(parameter.id) } _url = parameter.url @@ -342,7 +325,7 @@ class ChannelFragment : MainFragment() { _creatorThumbnail.setThumbnail(parameter.channel.thumbnail, true) Glide.with(_imageBanner).clear(_imageBanner) - loadPolycentricProfile(parameter.channel.id, parameter.channel.url) + loadPolycentricProfile(parameter.channel.id) } _url = parameter.channel.url @@ -359,16 +342,8 @@ class ChannelFragment : MainFragment() { _tabs.selectTab(_tabs.getTabAt(selectedTabIndex)) } - private fun loadPolycentricProfile(id: PlatformID, url: String) { - val cachedPolycentricProfile = PolycentricCache.instance.getCachedProfile(url, true) - if (cachedPolycentricProfile != null) { - setPolycentricProfile(cachedPolycentricProfile, animate = true) - if (cachedPolycentricProfile.expired) { - _taskLoadPolycentricProfile.run(id) - } - } else { - _taskLoadPolycentricProfile.run(id) - } + private fun loadPolycentricProfile(id: PlatformID) { + _taskLoadPolycentricProfile.run(id) } private fun setLoading(isLoading: Boolean) { @@ -533,20 +508,13 @@ class ChannelFragment : MainFragment() { private fun setPolycentricProfileOr(url: String, or: () -> Unit) { setPolycentricProfile(null, animate = false) - - val cachedProfile = channel?.let { PolycentricCache.instance.getCachedProfile(url) } - if (cachedProfile != null) { - setPolycentricProfile(cachedProfile, animate = false) - } else { - or() - } + or() } private fun setPolycentricProfile( - cachedPolycentricProfile: PolycentricCache.CachedPolycentricProfile?, animate: Boolean + profile: PolycentricProfile?, animate: Boolean ) { val dp35 = 35.dp(resources) - val profile = cachedPolycentricProfile?.profile val avatar = profile?.systemState?.avatar?.selectBestImage(dp35 * dp35)?.let { it.toURLInfoSystemLinkUrl( profile.system.toProto(), it.process, profile.systemState.servers.toList() diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CommentsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CommentsFragment.kt index dce725f6..0dae227d 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CommentsFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CommentsFragment.kt @@ -23,7 +23,6 @@ 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.video.IPlatformVideoDetails import com.futo.platformplayer.constructs.TaskHandler -import com.futo.platformplayer.fullyBackfillServersAnnounceExceptions import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.states.StatePlatform @@ -32,6 +31,7 @@ import com.futo.platformplayer.views.adapters.CommentWithReferenceViewHolder import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader import com.futo.platformplayer.views.overlays.RepliesOverlay import com.futo.polycentric.core.PublicKey +import com.futo.polycentric.core.fullyBackfillServersAnnounceExceptions import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import java.net.UnknownHostException 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 86555b11..d4dc1672 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 @@ -33,10 +33,8 @@ import com.futo.platformplayer.api.media.models.ratings.RatingLikes import com.futo.platformplayer.constructs.TaskHandler import com.futo.platformplayer.dp import com.futo.platformplayer.fixHtmlWhitespace -import com.futo.platformplayer.fullyBackfillServersAnnounceExceptions import com.futo.platformplayer.images.GlideHelper.Companion.crossfade import com.futo.platformplayer.logging.Logger -import com.futo.platformplayer.polycentric.PolycentricCache import com.futo.platformplayer.setPlatformPlayerLinkMovementMethod import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.states.StatePlatform @@ -47,7 +45,6 @@ import com.futo.platformplayer.views.adapters.ChannelTab 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 @@ -57,6 +54,8 @@ 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.PolycentricProfile +import com.futo.polycentric.core.fullyBackfillServersAnnounceExceptions import com.google.android.flexbox.FlexboxLayout import com.google.android.material.imageview.ShapeableImageView import com.google.android.material.shape.CornerFamily @@ -112,7 +111,7 @@ class PostDetailFragment : MainFragment { private var _isLoading = false; private var _post: IPlatformPostDetails? = null; private var _postOverview: IPlatformPost? = null; - private var _polycentricProfile: PolycentricCache.CachedPolycentricProfile? = null; + private var _polycentricProfile: PolycentricProfile? = null; private var _version = 0; private var _isRepliesVisible: Boolean = false; private var _repliesAnimator: ViewPropertyAnimator? = null; @@ -169,7 +168,7 @@ class PostDetailFragment : MainFragment { UIDialogs.showGeneralRetryErrorDialog(context, context.getString(R.string.failed_to_load_post), it, ::fetchPost, null, _fragment); } else TaskHandler(IPlatformPostDetails::class.java) { _fragment.lifecycleScope }; - private val _taskLoadPolycentricProfile = TaskHandler(StateApp.instance.scopeGetter, { PolycentricCache.instance.getProfileAsync(it) }) + private val _taskLoadPolycentricProfile = TaskHandler(StateApp.instance.scopeGetter, { ApiMethods.getPolycentricProfileByClaim(ApiMethods.SERVER, ApiMethods.FUTO_TRUST_ROOT, it.claimFieldType.toLong(), it.claimType.toLong(), it.value!!) }) .success { it -> setPolycentricProfile(it, animate = true) } .exception { Logger.w(TAG, "Failed to load claims.", it); @@ -274,7 +273,7 @@ class PostDetailFragment : MainFragment { }; _buttonStore.setOnClickListener { - _polycentricProfile?.profile?.systemState?.store?.let { + _polycentricProfile?.systemState?.store?.let { try { val uri = Uri.parse(it); val intent = Intent(Intent.ACTION_VIEW); @@ -334,7 +333,7 @@ class PostDetailFragment : MainFragment { } try { - val queryReferencesResponse = ApiMethods.getQueryReferences(PolycentricCache.SERVER, ref, null,null, + val queryReferencesResponse = ApiMethods.getQueryReferences(ApiMethods.SERVER, ref, null,null, arrayListOf( Protocol.QueryReferencesRequestCountLWWElementReferences.newBuilder().setFromType( ContentType.OPINION.value).setValue( @@ -604,16 +603,8 @@ class PostDetailFragment : MainFragment { private fun fetchPolycentricProfile() { val author = _post?.author ?: _postOverview?.author ?: return; - val cachedPolycentricProfile = PolycentricCache.instance.getCachedProfile(author.url, true); - if (cachedPolycentricProfile != null) { - setPolycentricProfile(cachedPolycentricProfile, animate = false); - if (cachedPolycentricProfile.expired) { - _taskLoadPolycentricProfile.run(author.id); - } - } else { setPolycentricProfile(null, animate = false); _taskLoadPolycentricProfile.run(author.id); - } } private fun setChannelMeta(value: IPlatformPost?) { @@ -639,17 +630,18 @@ class PostDetailFragment : MainFragment { _repliesOverlay.cleanup(); } - private fun setPolycentricProfile(cachedPolycentricProfile: PolycentricCache.CachedPolycentricProfile?, animate: Boolean) { - _polycentricProfile = cachedPolycentricProfile; + private fun setPolycentricProfile(polycentricProfile: PolycentricProfile?, animate: Boolean) { + _polycentricProfile = polycentricProfile; - if (cachedPolycentricProfile?.profile == null) { + val pp = _polycentricProfile; + if (pp == null) { _layoutMonetization.visibility = View.GONE; _creatorThumbnail.setHarborAvailable(false, animate, null); return; } _layoutMonetization.visibility = View.VISIBLE; - _creatorThumbnail.setHarborAvailable(true, animate, cachedPolycentricProfile.profile.system.toProto()); + _creatorThumbnail.setHarborAvailable(true, animate, pp.system.toProto()); } private fun fetchPost() { 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 afda7722..8d930838 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 @@ -94,12 +94,10 @@ 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.selectBestImage import com.futo.platformplayer.states.AnnouncementType @@ -158,6 +156,8 @@ 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.PolycentricProfile +import com.futo.polycentric.core.fullyBackfillServersAnnounceExceptions import com.futo.polycentric.core.toURLInfoSystemLinkUrl import com.google.protobuf.ByteString import kotlinx.coroutines.Dispatchers @@ -294,7 +294,7 @@ class VideoDetailView : ConstraintLayout { private set; private var _historicalPosition: Long = 0; private var _commentsCount = 0; - private var _polycentricProfile: PolycentricCache.CachedPolycentricProfile? = null; + private var _polycentricProfile: PolycentricProfile? = null; private var _slideUpOverlay: SlideUpMenuOverlay? = null; private var _autoplayVideo: IPlatformVideo? = null @@ -409,12 +409,12 @@ class VideoDetailView : ConstraintLayout { }; _monetization.onSupportTap.subscribe { - _container_content_support.setPolycentricProfile(_polycentricProfile?.profile); + _container_content_support.setPolycentricProfile(_polycentricProfile); switchContentView(_container_content_support); }; _monetization.onStoreTap.subscribe { - _polycentricProfile?.profile?.systemState?.store?.let { + _polycentricProfile?.systemState?.store?.let { try { val uri = Uri.parse(it); val intent = Intent(Intent.ACTION_VIEW); @@ -1236,16 +1236,8 @@ class VideoDetailView : ConstraintLayout { _creatorThumbnail.setThumbnail(video.author.thumbnail, false); _channelName.text = video.author.name; - val cachedPolycentricProfile = PolycentricCache.instance.getCachedProfile(video.author.url, true); - if (cachedPolycentricProfile != null) { - setPolycentricProfile(cachedPolycentricProfile, animate = false); - if (cachedPolycentricProfile.expired) { - _taskLoadPolycentricProfile.run(video.author.id); - } - } else { - setPolycentricProfile(null, animate = false); - _taskLoadPolycentricProfile.run(video.author.id); - } + setPolycentricProfile(null, animate = false); + _taskLoadPolycentricProfile.run(video.author.id); _player.clear(); @@ -1405,11 +1397,8 @@ class VideoDetailView : ConstraintLayout { setTabIndex(2, true) } else { when (Settings.instance.comments.defaultCommentSection) { - 0 -> if (Settings.instance.other.polycentricEnabled) setTabIndex( - 0, - true - ) else setTabIndex(1, true); - 1 -> setTabIndex(1, true); + 0 -> if (Settings.instance.other.polycentricEnabled) setTabIndex(0, true) else setTabIndex(1, true) + 1 -> setTabIndex(1, true) 2 -> setTabIndex(StateMeta.instance.getLastCommentSection(), true) } } @@ -1447,16 +1436,8 @@ class VideoDetailView : ConstraintLayout { _buttonSubscribe.setSubscribeChannel(video.author.url); setDescription(video.description.fixHtmlLinks()); _creatorThumbnail.setThumbnail(video.author.thumbnail, false); - - - val cachedPolycentricProfile = - PolycentricCache.instance.getCachedProfile(video.author.url, true); - if (cachedPolycentricProfile != null) { - setPolycentricProfile(cachedPolycentricProfile, animate = false); - } else { - setPolycentricProfile(null, animate = false); - _taskLoadPolycentricProfile.run(video.author.id); - } + setPolycentricProfile(null, animate = false); + _taskLoadPolycentricProfile.run(video.author.id); _platform.setPlatformFromClientID(video.id.pluginId); val subTitleSegments: ArrayList = ArrayList(); @@ -1485,7 +1466,7 @@ class VideoDetailView : ConstraintLayout { fragment.lifecycleScope.launch(Dispatchers.IO) { try { val queryReferencesResponse = ApiMethods.getQueryReferences( - PolycentricCache.SERVER, ref, null, null, + ApiMethods.SERVER, ref, null, null, arrayListOf( Protocol.QueryReferencesRequestCountLWWElementReferences.newBuilder() .setFromType(ContentType.OPINION.value).setValue( @@ -1501,10 +1482,8 @@ class VideoDetailView : ConstraintLayout { val likes = queryReferencesResponse.countsList[0]; val dislikes = queryReferencesResponse.countsList[1]; - val hasLiked = - StatePolycentric.instance.hasLiked(ref.toByteArray())/* || extraBytesRef?.let { StatePolycentric.instance.hasLiked(it) } ?: false*/; - val hasDisliked = - StatePolycentric.instance.hasDisliked(ref.toByteArray())/* || extraBytesRef?.let { StatePolycentric.instance.hasDisliked(it) } ?: false*/; + val hasLiked = StatePolycentric.instance.hasLiked(ref.toByteArray())/* || extraBytesRef?.let { StatePolycentric.instance.hasLiked(it) } ?: false*/; + val hasDisliked = StatePolycentric.instance.hasDisliked(ref.toByteArray())/* || extraBytesRef?.let { StatePolycentric.instance.hasDisliked(it) } ?: false*/; withContext(Dispatchers.Main) { _rating.visibility = View.VISIBLE; @@ -2805,13 +2784,12 @@ class VideoDetailView : ConstraintLayout { } } - private fun setPolycentricProfile(cachedPolycentricProfile: PolycentricCache.CachedPolycentricProfile?, animate: Boolean) { - _polycentricProfile = cachedPolycentricProfile; + private fun setPolycentricProfile(profile: PolycentricProfile?, animate: Boolean) { + _polycentricProfile = profile val dp_35 = 35.dp(context.resources) - val profile = cachedPolycentricProfile?.profile; val avatar = profile?.systemState?.avatar?.selectBestImage(dp_35 * dp_35) - ?.let { it.toURLInfoSystemLinkUrl(profile.system.toProto(), it.process, profile.systemState.servers.toList()) }; + ?.let { it.toURLInfoSystemLinkUrl(profile.system.toProto(), it.process, profile.systemState.servers.toList()) } if (avatar != null) { _creatorThumbnail.setThumbnail(avatar, animate); @@ -2820,12 +2798,12 @@ class VideoDetailView : ConstraintLayout { _creatorThumbnail.setHarborAvailable(profile != null, animate, profile?.system?.toProto()); } - val username = cachedPolycentricProfile?.profile?.systemState?.username + val username = profile?.systemState?.username if (username != null) { _channelName.text = username } - _monetization.setPolycentricProfile(cachedPolycentricProfile); + _monetization.setPolycentricProfile(profile); } fun setProgressBarOverlayed(isOverlayed: Boolean?) { @@ -3013,7 +2991,7 @@ class VideoDetailView : ConstraintLayout { Logger.w(TAG, "Failed to load recommendations.", it); }; - private val _taskLoadPolycentricProfile = TaskHandler(StateApp.instance.scopeGetter, { PolycentricCache.instance.getProfileAsync(it) }) + private val _taskLoadPolycentricProfile = TaskHandler(StateApp.instance.scopeGetter, { ApiMethods.getPolycentricProfileByClaim(ApiMethods.SERVER, ApiMethods.FUTO_TRUST_ROOT, it.claimFieldType.toLong(), it.claimType.toLong(), it.value!!) }) .success { it -> setPolycentricProfile(it, animate = true) } .exception { Logger.w(TAG, "Failed to load claims.", it); diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/NavigationTopBarFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/NavigationTopBarFragment.kt index 4348edac..3309c851 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/NavigationTopBarFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/NavigationTopBarFragment.kt @@ -14,9 +14,9 @@ import com.futo.platformplayer.R import com.futo.platformplayer.api.media.IPlatformClient import com.futo.platformplayer.api.media.models.PlatformAuthorLink import com.futo.platformplayer.api.media.models.channels.IPlatformChannel -import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile import com.futo.platformplayer.models.Playlist import com.futo.platformplayer.views.casting.CastButton +import com.futo.polycentric.core.PolycentricProfile class NavigationTopBarFragment : TopFragment() { private var _buttonBack: ImageButton? = null; diff --git a/app/src/main/java/com/futo/platformplayer/images/PolycentricModelLoader.java b/app/src/main/java/com/futo/platformplayer/images/PolycentricModelLoader.java index ded61ffc..c0155a33 100644 --- a/app/src/main/java/com/futo/platformplayer/images/PolycentricModelLoader.java +++ b/app/src/main/java/com/futo/platformplayer/images/PolycentricModelLoader.java @@ -1,5 +1,7 @@ package com.futo.platformplayer.images; +import static com.futo.platformplayer.Extensions_PolycentricKt.getDataLinkFromUrl; + import android.util.Log; import androidx.annotation.NonNull; @@ -12,10 +14,14 @@ import com.bumptech.glide.load.model.ModelLoader; import com.bumptech.glide.load.model.ModelLoaderFactory; import com.bumptech.glide.load.model.MultiModelLoaderFactory; import com.bumptech.glide.signature.ObjectKey; -import com.futo.platformplayer.polycentric.PolycentricCache; +import com.futo.polycentric.core.ApiMethods; import kotlin.Unit; +import kotlinx.coroutines.CoroutineScopeKt; import kotlinx.coroutines.Deferred; +import kotlinx.coroutines.Dispatchers; +import userpackage.Protocol; + import java.lang.Exception; import java.nio.ByteBuffer; import java.util.concurrent.CancellationException; @@ -60,7 +66,14 @@ public class PolycentricModelLoader implements ModelLoader { @Override public void loadData(@NonNull Priority priority, @NonNull DataFetcher.DataCallback callback) { Log.i("PolycentricModelLoader", this._model); - _deferred = PolycentricCache.getInstance().getDataAsync(_model); + + Protocol.URLInfoDataLink dataLink = getDataLinkFromUrl(_model); + if (dataLink == null) { + callback.onLoadFailed(new Exception("Data link cannot be null")); + return; + } + + _deferred = ApiMethods.Companion.getDataFromServerAndReassemble(CoroutineScopeKt.CoroutineScope(Dispatchers.getIO()), dataLink); _deferred.invokeOnCompletion(throwable -> { if (throwable != null) { Log.e("PolycentricModelLoader", "getDataAsync failed throwable: " + throwable.toString()); diff --git a/app/src/main/java/com/futo/platformplayer/polycentric/PolycentricCache.kt b/app/src/main/java/com/futo/platformplayer/polycentric/PolycentricCache.kt deleted file mode 100644 index abe4ca8e..00000000 --- a/app/src/main/java/com/futo/platformplayer/polycentric/PolycentricCache.kt +++ /dev/null @@ -1,353 +0,0 @@ -package com.futo.platformplayer.polycentric - -import com.futo.platformplayer.api.media.PlatformID -import com.futo.platformplayer.constructs.BatchedTaskHandler -import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile -import com.futo.platformplayer.getNowDiffSeconds -import com.futo.platformplayer.logging.Logger -import com.futo.platformplayer.resolveChannelUrls -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.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 - -class PolycentricCache { - data class CachedOwnedClaims(val ownedClaims: List?, val creationTime: OffsetDateTime = OffsetDateTime.now()) { - val expired get() = creationTime.getNowDiffSeconds() > CACHE_EXPIRATION_SECONDS - } - @Serializable - data class CachedPolycentricProfile(val profile: PolycentricProfile?, @Serializable(with = OffsetDateTimeSerializer::class) val creationTime: OffsetDateTime = OffsetDateTime.now()) { - val expired get() = creationTime.getNowDiffSeconds() > CACHE_EXPIRATION_SECONDS - } - - private val _cache = hashMapOf() - private val _profileCache = hashMapOf() - private val _profileUrlCache: CachedPolycentricProfileStorage; - private val _scope = CoroutineScope(Dispatchers.IO); - init { - Logger.i(TAG, "Initializing Polycentric cache"); - val time = measureTimeMillis { - _profileUrlCache = FragmentedStorage.get("profileUrlCache") - } - Logger.i(TAG, "Initialized Polycentric cache (${_profileUrlCache.map.size}, ${time}ms)"); - } - - private val _taskGetProfile = BatchedTaskHandler(_scope, - { system -> - val signedEventsList = ApiMethods.getQueryLatest( - SERVER, - system.toProto(), - listOf( - ContentType.BANNER.value, - ContentType.AVATAR.value, - ContentType.USERNAME.value, - ContentType.DESCRIPTION.value, - ContentType.STORE.value, - ContentType.SERVER.value, - ContentType.STORE_DATA.value, - ContentType.PROMOTION_BANNER.value, - ContentType.PROMOTION.value, - ContentType.MEMBERSHIP_URLS.value, - ContentType.DONATION_DESTINATIONS.value - ) - ).eventsList.map { e -> SignedEvent.fromProto(e) }; - - val signedProfileEvents = signedEventsList.groupBy { e -> e.event.contentType } - .map { (_, events) -> events.maxBy { it.event.unixMilliseconds ?: 0 } }; - - val storageSystemState = StorageTypeSystemState.create() - for (signedEvent in signedProfileEvents) { - storageSystemState.update(signedEvent.event) - } - - val signedClaimEvents = ApiMethods.getQueryIndex( - SERVER, - system.toProto(), - ContentType.CLAIM.value, - limit = 200 - ).eventsList.map { e -> SignedEvent.fromProto(e) }; - - val ownedClaims: ArrayList = arrayListOf() - for (signedEvent in signedClaimEvents) { - if (signedEvent.event.contentType != ContentType.CLAIM.value) { - continue; - } - - val response = ApiMethods.getQueryReferences( - SERVER, - Protocol.Reference.newBuilder() - .setReference(signedEvent.toPointer().toProto().toByteString()) - .setReferenceType(2) - .build(), - null, - Protocol.QueryReferencesRequestEvents.newBuilder() - .setFromType(ContentType.VOUCH.value) - .build() - ); - - val ownedClaim = response.itemsList.map { SignedEvent.fromProto(it.event) }.getClaimIfValid(signedEvent); - if (ownedClaim != null) { - ownedClaims.add(ownedClaim); - } - } - - Logger.i(TAG, "Retrieved profile (ownedClaims = $ownedClaims)"); - val systemState = SystemState.fromStorageTypeSystemState(storageSystemState); - return@BatchedTaskHandler CachedPolycentricProfile(PolycentricProfile(system, systemState, ownedClaims)); - }, - { system -> return@BatchedTaskHandler getCachedProfile(system); }, - { system, result -> - synchronized(_cache) { - _profileCache[system] = result; - - if (result.profile != null) { - for (claim in result.profile.ownedClaims) { - val urls = claim.claim.resolveChannelUrls(); - for (url in urls) - _profileUrlCache.map[url] = result; - } - } - - _profileUrlCache.save(); - } - }); - - private val _batchTaskGetClaims = BatchedTaskHandler(_scope, - { id -> - val resolved = if (id.claimFieldType == -1) ApiMethods.getResolveClaim(SERVER, system, id.claimType.toLong(), id.value!!) - else ApiMethods.getResolveClaim(SERVER, system, id.claimType.toLong(), id.claimFieldType.toLong(), id.value!!); - Logger.v(TAG, "getResolveClaim(url = $SERVER, system = $system, id = $id, claimType = ${id.claimType}, matchAnyField = ${id.value})"); - val protoEvents = resolved.matchesList.flatMap { arrayListOf(it.claim).apply { addAll(it.proofChainList) } } - val resolvedEvents = protoEvents.map { i -> SignedEvent.fromProto(i) }; - return@BatchedTaskHandler CachedOwnedClaims(resolvedEvents.getValidClaims()); - }, - { id -> return@BatchedTaskHandler getCachedValidClaims(id); }, - { id, result -> - synchronized(_cache) { - _cache[id] = result; - } - }); - - private val _batchTaskGetData = BatchedTaskHandler(_scope, - { - val dataLink = getDataLinkFromUrl(it) ?: throw Exception("Only URLInfoDataLink is supported"); - return@BatchedTaskHandler ApiMethods.getDataFromServerAndReassemble(dataLink); - }, - { return@BatchedTaskHandler null }, - { _, _ -> }); - - fun getCachedValidClaims(id: PlatformID, ignoreExpired: Boolean = false): CachedOwnedClaims? { - if (!StatePolycentric.instance.enabled || id.claimType <= 0) { - return CachedOwnedClaims(null); - } - - synchronized(_cache) { - val cached = _cache[id] - if (cached == null) { - return null - } - - if (!ignoreExpired && cached.expired) { - return null; - } - - return cached; - } - } - - //TODO: Review all return null in this file, perhaps it should be CachedX(null) instead - fun getValidClaimsAsync(id: PlatformID): Deferred { - if (!StatePolycentric.instance.enabled || id.value == null || id.claimType <= 0) { - return _scope.async { CachedOwnedClaims(null) }; - } - - Logger.v(TAG, "getValidClaims (id: $id)") - val def = _batchTaskGetClaims.execute(id); - def.invokeOnCompletion { - if (it == null) { - return@invokeOnCompletion - } - - handleException(it, handleNetworkException = { /* Do nothing (do not cache) */ }, handleOtherException = { - //Cache failed result - synchronized(_cache) { - _cache[id] = CachedOwnedClaims(null); - } - }) - }; - return def; - } - - fun getDataAsync(url: String): Deferred { - StatePolycentric.instance.ensureEnabled() - return _batchTaskGetData.execute(url); - } - - fun getCachedProfile(url: String, ignoreExpired: Boolean = false): CachedPolycentricProfile? { - if (!StatePolycentric.instance.enabled) { - return CachedPolycentricProfile(null) - } - - synchronized (_profileCache) { - val cached = _profileUrlCache.get(url) ?: return null; - if (!ignoreExpired && cached.expired) { - return null; - } - - return cached; - } - } - - fun getCachedProfile(system: PublicKey, ignoreExpired: Boolean = false): CachedPolycentricProfile? { - if (!StatePolycentric.instance.enabled) { - return CachedPolycentricProfile(null) - } - - synchronized(_profileCache) { - val cached = _profileCache[system] ?: return null; - if (!ignoreExpired && cached.expired) { - return null; - } - - return cached; - } - } - - suspend fun getProfileAsync(id: PlatformID, urlNullCache: String? = null): CachedPolycentricProfile? { - if (!StatePolycentric.instance.enabled || id.claimType <= 0) { - return CachedPolycentricProfile(null); - } - - val cachedClaims = getCachedValidClaims(id); - if (cachedClaims != null) { - if (!cachedClaims.ownedClaims.isNullOrEmpty()) { - Logger.v(TAG, "getProfileAsync (id: $id) != null (with cached valid claims)") - return getProfileAsync(cachedClaims.ownedClaims.first().system).await(); - } else { - return null; - } - } else { - Logger.v(TAG, "getProfileAsync (id: $id) no cached valid claims, will be retrieved") - - val claims = getValidClaimsAsync(id).await() - if (!claims.ownedClaims.isNullOrEmpty()) { - Logger.v(TAG, "getProfileAsync (id: $id) != null (with retrieved valid claims)") - return getProfileAsync(claims.ownedClaims.first().system).await() - } else { - synchronized (_cache) { - if (urlNullCache != null) { - _profileUrlCache.setAndSave(urlNullCache, CachedPolycentricProfile(null)) - } - } - return null; - } - } - } - - fun getProfileAsync(system: PublicKey): Deferred { - if (!StatePolycentric.instance.enabled) { - return _scope.async { CachedPolycentricProfile(null) }; - } - - Logger.i(TAG, "getProfileAsync (system: ${system})") - val def = _taskGetProfile.execute(system); - def.invokeOnCompletion { - if (it == null) { - return@invokeOnCompletion - } - - handleException(it, handleNetworkException = { /* Do nothing (do not cache) */ }, handleOtherException = { - //Cache failed result - synchronized(_cache) { - val cachedProfile = CachedPolycentricProfile(null); - _profileCache[system] = cachedProfile; - } - }) - }; - return def; - } - - private fun handleException(e: Throwable, handleNetworkException: () -> Unit, handleOtherException: () -> Unit) { - val isNetworkException = when(e) { - is java.net.UnknownHostException, - is java.net.SocketTimeoutException, - is java.net.ConnectException -> true - else -> when(e.cause) { - is java.net.UnknownHostException, - is java.net.SocketTimeoutException, - is java.net.ConnectException -> true - else -> false - } - } - if (isNetworkException) { - handleNetworkException() - } else { - handleOtherException() - } - } - - companion object { - private val system = Protocol.PublicKey.newBuilder() - .setKeyType(1) - .setKey(ByteString.copyFrom("gX0eCWctTm6WHVGot4sMAh7NDAIwWsIM5tRsOz9dX04=".base64ToByteArray())) //Production key - //.setKey(ByteString.copyFrom("LeQkzn1j625YZcZHayfCmTX+6ptrzsA+CdAyq+BcEdQ".base64ToByteArray())) //Test key koen-futo - .build(); - - private const val TAG = "PolycentricCache" - const val SERVER = "https://srv1-prod.polycentric.io" - private var _instance: PolycentricCache? = null; - private val CACHE_EXPIRATION_SECONDS = 60 * 5; - - @JvmStatic - val instance: PolycentricCache - get(){ - if(_instance == null) - _instance = PolycentricCache(); - return _instance!!; - }; - - fun finish() { - _instance?.let { - _instance = null; - it._scope.cancel("PolycentricCache finished"); - } - } - - fun getDataLinkFromUrl(it: String): Protocol.URLInfoDataLink? { - val urlData = if (it.startsWith("polycentric://")) { - it.substring("polycentric://".length) - } else it; - - val urlBytes = urlData.base64UrlToByteArray(); - val urlInfo = Protocol.URLInfo.parseFrom(urlBytes); - if (urlInfo.urlType != 4L) { - return null - } - - val dataLink = Protocol.URLInfoDataLink.parseFrom(urlInfo.body); - return dataLink - } - } -} \ No newline at end of file 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 7d91c9ce..8157ed2a 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateCache.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateCache.kt @@ -6,7 +6,6 @@ import com.futo.platformplayer.api.media.structures.DedupContentPager import com.futo.platformplayer.api.media.structures.IPager import com.futo.platformplayer.api.media.structures.MultiChronoContentPager import com.futo.platformplayer.logging.Logger -import com.futo.platformplayer.polycentric.PolycentricCache import com.futo.platformplayer.resolveChannelUrl import com.futo.platformplayer.serializers.PlatformContentSerializer import com.futo.platformplayer.stores.db.ManagedDBStore @@ -50,14 +49,7 @@ class StateCache { val subs = StateSubscriptions.instance.getSubscriptions(); Logger.i(TAG, "Subscriptions CachePager polycentric urls"); val allUrls = subs - .map { - val otherUrls = PolycentricCache.instance.getCachedProfile(it.channel.url)?.profile?.ownedClaims?.mapNotNull { c -> c.claim.resolveChannelUrl() } ?: listOf(); - if(!otherUrls.contains(it.channel.url)) - return@map listOf(listOf(it.channel.url), otherUrls).flatten(); - else - return@map otherUrls; - } - .flatten() + .map { it.channel.url } .distinct() .filter { StatePlatform.instance.hasEnabledChannelClient(it) }; diff --git a/app/src/main/java/com/futo/platformplayer/states/StateMeta.kt b/app/src/main/java/com/futo/platformplayer/states/StateMeta.kt index b20866bf..bd45b205 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateMeta.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateMeta.kt @@ -14,7 +14,7 @@ class StateMeta { return when(lastCommentSection.value){ "Polycentric" -> 0; "Platform" -> 1; - else -> 1 + else -> 0 } } fun setLastCommentSection(value: Int) { diff --git a/app/src/main/java/com/futo/platformplayer/states/StatePolycentric.kt b/app/src/main/java/com/futo/platformplayer/states/StatePolycentric.kt index e98aff0c..9d6f7437 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StatePolycentric.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StatePolycentric.kt @@ -21,9 +21,7 @@ import com.futo.platformplayer.api.media.structures.IPager import com.futo.platformplayer.api.media.structures.MultiChronoContentPager import com.futo.platformplayer.awaitFirstDeferred import com.futo.platformplayer.dp -import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile import com.futo.platformplayer.logging.Logger -import com.futo.platformplayer.polycentric.PolycentricCache import com.futo.platformplayer.polycentric.PolycentricStorage import com.futo.platformplayer.resolveChannelUrl import com.futo.platformplayer.selectBestImage @@ -33,6 +31,7 @@ import com.futo.polycentric.core.ApiMethods import com.futo.polycentric.core.ClaimType import com.futo.polycentric.core.ContentType import com.futo.polycentric.core.Opinion +import com.futo.polycentric.core.PolycentricProfile import com.futo.polycentric.core.ProcessHandle import com.futo.polycentric.core.PublicKey import com.futo.polycentric.core.SignedEvent @@ -234,34 +233,7 @@ class StatePolycentric { if (!enabled) { return Pair(false, listOf(url)); } - var polycentricProfile: PolycentricProfile? = null; - try { - val polycentricCached = PolycentricCache.instance.getCachedProfile(url, cacheOnly) - polycentricProfile = polycentricCached?.profile; - if (polycentricCached == null && channelId != null) { - Logger.i("StateSubscriptions", "Get polycentric profile not cached"); - if(!cacheOnly) { - polycentricProfile = runBlocking { PolycentricCache.instance.getProfileAsync(channelId, if(doCacheNull) url else null) }?.profile; - didUpdate = true; - } - } else { - Logger.i("StateSubscriptions", "Get polycentric profile cached"); - } - } - catch(ex: Throwable) { - Logger.w(StateSubscriptions.TAG, "Polycentric getCachedProfile failed for subscriptions", ex); - //TODO: Some way to communicate polycentric failing without blocking here - } - if(polycentricProfile != null) { - val urls = polycentricProfile.ownedClaims.groupBy { it.claim.claimType } - .mapNotNull { it.value.firstOrNull()?.claim?.resolveChannelUrl() }.toMutableList(); - if(urls.any { it.equals(url, true) }) - return Pair(didUpdate, urls); - else - return Pair(didUpdate, listOf(url) + urls); - } - else - return Pair(didUpdate, listOf(url)); + return Pair(didUpdate, listOf(url)); } fun getChannelContent(scope: CoroutineScope, profile: PolycentricProfile, isSubscriptionOptimized: Boolean = false, channelConcurrency: Int = -1): IPager? { @@ -325,7 +297,7 @@ class StatePolycentric { id = PlatformID("polycentric", author, null, ClaimType.POLYCENTRIC.value.toInt()), name = systemState.username, url = author, - thumbnail = systemState.avatar?.selectBestImage(dp_25 * dp_25)?.let { img -> img.toURLInfoSystemLinkUrl(system.toProto(), img.process, listOf(PolycentricCache.SERVER)) }, + thumbnail = systemState.avatar?.selectBestImage(dp_25 * dp_25)?.let { img -> img.toURLInfoSystemLinkUrl(system.toProto(), img.process, listOf(ApiMethods.SERVER)) }, subscribers = null ), msg = if (post.content.count() > PolycentricPlatformComment.MAX_COMMENT_SIZE) post.content.substring(0, PolycentricPlatformComment.MAX_COMMENT_SIZE) else post.content, @@ -349,7 +321,7 @@ class StatePolycentric { suspend fun getLikesDislikesReplies(reference: Protocol.Reference): LikesDislikesReplies { ensureEnabled() - val response = ApiMethods.getQueryReferences(PolycentricCache.SERVER, reference, null, + val response = ApiMethods.getQueryReferences(ApiMethods.SERVER, reference, null, null, listOf( Protocol.QueryReferencesRequestCountLWWElementReferences.newBuilder() @@ -382,7 +354,7 @@ class StatePolycentric { } val pointer = Protocol.Pointer.parseFrom(reference.reference) - val events = ApiMethods.getEvents(PolycentricCache.SERVER, pointer.system, Protocol.RangesForSystem.newBuilder() + val events = ApiMethods.getEvents(ApiMethods.SERVER, pointer.system, Protocol.RangesForSystem.newBuilder() .addRangesForProcesses(Protocol.RangesForProcess.newBuilder() .setProcess(pointer.process) .addRanges(Protocol.Range.newBuilder() @@ -400,11 +372,11 @@ class StatePolycentric { } val post = Protocol.Post.parseFrom(ev.content); - val systemLinkUrl = ev.system.systemToURLInfoSystemLinkUrl(listOf(PolycentricCache.SERVER)); + val systemLinkUrl = ev.system.systemToURLInfoSystemLinkUrl(listOf(ApiMethods.SERVER)); val dp_25 = 25.dp(StateApp.instance.context.resources) val profileEvents = ApiMethods.getQueryLatest( - PolycentricCache.SERVER, + ApiMethods.SERVER, ev.system.toProto(), listOf( ContentType.AVATAR.value, @@ -433,7 +405,7 @@ class StatePolycentric { id = PlatformID("polycentric", systemLinkUrl, null, ClaimType.POLYCENTRIC.value.toInt()), name = nameEvent?.event?.lwwElement?.value?.decodeToString() ?: "Unknown", url = systemLinkUrl, - thumbnail = imageBundle?.selectBestImage(dp_25 * dp_25)?.let { img -> img.toURLInfoSystemLinkUrl(ev.system.toProto(), img.process, listOf(PolycentricCache.SERVER)) }, + thumbnail = imageBundle?.selectBestImage(dp_25 * dp_25)?.let { img -> img.toURLInfoSystemLinkUrl(ev.system.toProto(), img.process, listOf(ApiMethods.SERVER)) }, subscribers = null ), msg = if (post.content.count() > PolycentricPlatformComment.MAX_COMMENT_SIZE) post.content.substring(0, PolycentricPlatformComment.MAX_COMMENT_SIZE) else post.content, @@ -445,12 +417,12 @@ class StatePolycentric { ) } - suspend fun getCommentPager(contextUrl: String, reference: Protocol.Reference, extraByteReferences: List? = null): IPager { + suspend fun getCommentPager(contextUrl: String, reference: Reference, extraByteReferences: List? = null): IPager { if (!enabled) { return EmptyPager() } - val response = ApiMethods.getQueryReferences(PolycentricCache.SERVER, reference, null, + val response = ApiMethods.getQueryReferences(ApiMethods.SERVER, reference, null, Protocol.QueryReferencesRequestEvents.newBuilder() .setFromType(ContentType.POST.value) .addAllCountLwwElementReferences(arrayListOf( @@ -486,7 +458,7 @@ class StatePolycentric { } override suspend fun nextPageAsync() { - val nextPageResponse = ApiMethods.getQueryReferences(PolycentricCache.SERVER, reference, _cursor, + val nextPageResponse = ApiMethods.getQueryReferences(ApiMethods.SERVER, reference, _cursor, Protocol.QueryReferencesRequestEvents.newBuilder() .setFromType(ContentType.POST.value) .addAllCountLwwElementReferences(arrayListOf( @@ -534,7 +506,7 @@ class StatePolycentric { return@mapNotNull LazyComment(scope.async(_commentPoolDispatcher){ Logger.i(TAG, "Fetching comment data for [" + ev.system.key.toBase64() + "]"); val profileEvents = ApiMethods.getQueryLatest( - PolycentricCache.SERVER, + ApiMethods.SERVER, ev.system.toProto(), listOf( ContentType.AVATAR.value, @@ -558,7 +530,7 @@ class StatePolycentric { val unixMilliseconds = ev.unixMilliseconds //TODO: Don't use single hardcoded sderver here - val systemLinkUrl = ev.system.systemToURLInfoSystemLinkUrl(listOf(PolycentricCache.SERVER)); + val systemLinkUrl = ev.system.systemToURLInfoSystemLinkUrl(listOf(ApiMethods.SERVER)); val dp_25 = 25.dp(StateApp.instance.context.resources) return@async PolycentricPlatformComment( contextUrl = contextUrl, @@ -566,7 +538,7 @@ class StatePolycentric { id = PlatformID("polycentric", systemLinkUrl, null, ClaimType.POLYCENTRIC.value.toInt()), name = nameEvent?.event?.lwwElement?.value?.decodeToString() ?: "Unknown", url = systemLinkUrl, - thumbnail = imageBundle?.selectBestImage(dp_25 * dp_25)?.let { img -> img.toURLInfoSystemLinkUrl(ev.system.toProto(), img.process, listOf(PolycentricCache.SERVER)) }, + thumbnail = imageBundle?.selectBestImage(dp_25 * dp_25)?.let { img -> img.toURLInfoSystemLinkUrl(ev.system.toProto(), img.process, listOf(ApiMethods.SERVER)) }, subscribers = null ), msg = if (post.content.count() > PolycentricPlatformComment.MAX_COMMENT_SIZE) post.content.substring(0, PolycentricPlatformComment.MAX_COMMENT_SIZE) else post.content, diff --git a/app/src/main/java/com/futo/platformplayer/states/StateSubscriptionGroups.kt b/app/src/main/java/com/futo/platformplayer/states/StateSubscriptionGroups.kt index 7da01216..f979251d 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateSubscriptionGroups.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateSubscriptionGroups.kt @@ -1,54 +1,17 @@ package com.futo.platformplayer.states -import com.futo.platformplayer.Settings -import com.futo.platformplayer.UIDialogs -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.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.ReusablePager.Companion.asReusable import com.futo.platformplayer.constructs.Event0 -import com.futo.platformplayer.constructs.Event1 -import com.futo.platformplayer.constructs.Event2 -import com.futo.platformplayer.engine.exceptions.PluginException -import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException -import com.futo.platformplayer.engine.exceptions.ScriptCriticalException -import com.futo.platformplayer.exceptions.ChannelException -import com.futo.platformplayer.findNonRuntimeException -import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile -import com.futo.platformplayer.getNowDiffDays import com.futo.platformplayer.logging.Logger -import com.futo.platformplayer.models.Subscription import com.futo.platformplayer.models.SubscriptionGroup -import com.futo.platformplayer.polycentric.PolycentricCache -import com.futo.platformplayer.resolveChannelUrl -import com.futo.platformplayer.states.StateHistory.Companion import com.futo.platformplayer.stores.FragmentedStorage import com.futo.platformplayer.stores.StringDateMapStorage -import com.futo.platformplayer.stores.SubscriptionStorage -import com.futo.platformplayer.stores.v2.ReconstructStore -import com.futo.platformplayer.stores.v2.ManagedStore -import com.futo.platformplayer.subscription.SubscriptionFetchAlgorithm -import com.futo.platformplayer.subscription.SubscriptionFetchAlgorithms import com.futo.platformplayer.sync.internal.GJSyncOpcodes import com.futo.platformplayer.sync.models.SyncSubscriptionGroupsPackage -import com.futo.platformplayer.sync.models.SyncSubscriptionsPackage -import kotlinx.coroutines.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import java.time.OffsetDateTime -import java.util.concurrent.ExecutionException -import java.util.concurrent.ForkJoinPool -import java.util.concurrent.ForkJoinTask -import kotlin.collections.ArrayList -import kotlin.coroutines.resumeWithException -import kotlin.coroutines.suspendCoroutine -import kotlin.streams.asSequence -import kotlin.streams.toList -import kotlin.system.measureTimeMillis /*** * Used to maintain subscription groups diff --git a/app/src/main/java/com/futo/platformplayer/states/StateSubscriptions.kt b/app/src/main/java/com/futo/platformplayer/states/StateSubscriptions.kt index 52fb9f2e..65892a1e 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateSubscriptions.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateSubscriptions.kt @@ -15,7 +15,6 @@ import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.models.ImportCache import com.futo.platformplayer.models.Subscription import com.futo.platformplayer.models.SubscriptionGroup -import com.futo.platformplayer.polycentric.PolycentricCache import com.futo.platformplayer.resolveChannelUrl import com.futo.platformplayer.stores.FragmentedStorage import com.futo.platformplayer.stores.StringDateMapStorage @@ -335,12 +334,6 @@ class StateSubscriptions { return true; } - //TODO: This causes issues, because what if the profile is not cached yet when the susbcribe button is loaded for example? - val cachedProfile = PolycentricCache.instance.getCachedProfile(urls.first(), true)?.profile; - if (cachedProfile != null) { - return cachedProfile.ownedClaims.any { c -> _subscriptions.hasItem { s -> c.claim.resolveChannelUrl() == s.channel.url } }; - } - return false; } } diff --git a/app/src/main/java/com/futo/platformplayer/stores/CachedPolycentricProfileStorage.kt b/app/src/main/java/com/futo/platformplayer/stores/CachedPolycentricProfileStorage.kt deleted file mode 100644 index b9c036ac..00000000 --- a/app/src/main/java/com/futo/platformplayer/stores/CachedPolycentricProfileStorage.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.futo.platformplayer.stores - -import com.futo.platformplayer.polycentric.PolycentricCache -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json - -@kotlinx.serialization.Serializable -class CachedPolycentricProfileStorage : FragmentedStorageFileJson() { - var map: HashMap = hashMapOf(); - - override fun encode(): String { - val encoded = Json.encodeToString(this); - return encoded; - } - - fun get(key: String) : PolycentricCache.CachedPolycentricProfile? { - return map[key]; - } - - fun setAndSave(key: String, value: PolycentricCache.CachedPolycentricProfile) : PolycentricCache.CachedPolycentricProfile { - map[key] = value; - save(); - return value; - } - - fun setAndSaveBlocking(key: String, value: PolycentricCache.CachedPolycentricProfile) : PolycentricCache.CachedPolycentricProfile { - map[key] = value; - saveBlocking(); - return value; - } -} \ 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 62da748b..b109acb5 100644 --- a/app/src/main/java/com/futo/platformplayer/views/MonetizationView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/MonetizationView.kt @@ -16,11 +16,11 @@ import com.futo.platformplayer.constructs.Event0 import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.constructs.TaskHandler import com.futo.platformplayer.logging.Logger -import com.futo.platformplayer.polycentric.PolycentricCache import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.views.AnyAdapterView.Companion.asAny import com.futo.platformplayer.views.adapters.viewholders.StoreItemViewHolder import com.futo.platformplayer.views.platform.PlatformIndicator +import com.futo.polycentric.core.PolycentricProfile import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json @@ -125,8 +125,7 @@ class MonetizationView : LinearLayout { } } - fun setPolycentricProfile(cachedPolycentricProfile: PolycentricCache.CachedPolycentricProfile?) { - val profile = cachedPolycentricProfile?.profile; + fun setPolycentricProfile(profile: PolycentricProfile?) { if (profile != null) { if (profile.systemState.store.isNotEmpty()) { _buttonStore.visibility = View.VISIBLE; 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 ad3017e7..c85d3450 100644 --- a/app/src/main/java/com/futo/platformplayer/views/SupportView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/SupportView.kt @@ -14,10 +14,10 @@ import androidx.core.view.isVisible import androidx.core.view.size import com.bumptech.glide.Glide import com.futo.platformplayer.R -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.views.buttons.BigButton +import com.futo.polycentric.core.PolycentricProfile import com.futo.polycentric.core.toURLInfoSystemLinkUrl import com.google.android.material.imageview.ShapeableImageView diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/ChannelViewPagerAdapter.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/ChannelViewPagerAdapter.kt index 665829db..3bd06903 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/ChannelViewPagerAdapter.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/ChannelViewPagerAdapter.kt @@ -17,7 +17,7 @@ import com.futo.platformplayer.fragment.channel.tab.ChannelListFragment import com.futo.platformplayer.fragment.channel.tab.ChannelMonetizationFragment import com.futo.platformplayer.fragment.channel.tab.ChannelPlaylistsFragment import com.futo.platformplayer.fragment.channel.tab.IChannelTabFragment -import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile +import com.futo.polycentric.core.PolycentricProfile import com.google.android.material.tabs.TabLayout diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/CommentViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/CommentViewHolder.kt index fe9c6079..74f7d53c 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/CommentViewHolder.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/CommentViewHolder.kt @@ -18,8 +18,6 @@ import com.futo.platformplayer.api.media.models.ratings.RatingLikeDislikes import com.futo.platformplayer.api.media.models.ratings.RatingLikes import com.futo.platformplayer.constructs.Event1 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 @@ -29,6 +27,7 @@ import com.futo.platformplayer.views.LoaderView import com.futo.platformplayer.views.others.CreatorThumbnail import com.futo.platformplayer.views.pills.PillButton import com.futo.platformplayer.views.pills.PillRatingLikesDislikes +import com.futo.polycentric.core.ApiMethods import com.futo.polycentric.core.Opinion import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -81,24 +80,18 @@ class CommentViewHolder : ViewHolder { throw Exception("Not implemented for non polycentric comments") } - if (args.hasLiked) { - args.processHandle.opinion(c.reference, Opinion.like); + val newOpinion: Opinion = if (args.hasLiked) { + Opinion.like } else if (args.hasDisliked) { - args.processHandle.opinion(c.reference, Opinion.dislike); + Opinion.dislike } else { - args.processHandle.opinion(c.reference, Opinion.neutral); + Opinion.neutral } _layoutComment.alpha = if (args.dislikes > 2 && args.dislikes.toFloat() / (args.likes + args.dislikes).toFloat() >= 0.7f) 0.5f else 1.0f; StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) { - try { - Logger.i(TAG, "Started backfill"); - args.processHandle.fullyBackfillServersAnnounceExceptions(); - Logger.i(TAG, "Finished backfill"); - } catch (e: Throwable) { - Logger.e(TAG, "Failed to backfill servers.", e) - } + ApiMethods.setOpinion(args.processHandle, c.reference, newOpinion) } StatePolycentric.instance.updateLikeMap(c.reference, args.hasLiked, args.hasDisliked) 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 db66a1a9..c4b9a51e 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 @@ -16,7 +16,6 @@ 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 @@ -26,6 +25,7 @@ import com.futo.platformplayer.views.others.CreatorThumbnail import com.futo.platformplayer.views.pills.PillButton import com.futo.platformplayer.views.pills.PillRatingLikesDislikes import com.futo.polycentric.core.Opinion +import com.futo.polycentric.core.fullyBackfillServersAnnounceExceptions import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import java.util.IdentityHashMap diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/PlaylistView.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/PlaylistView.kt index c8d86b14..c9cb8b73 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/PlaylistView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/PlaylistView.kt @@ -16,7 +16,6 @@ import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.constructs.TaskHandler import com.futo.platformplayer.images.GlideHelper.Companion.crossfade import com.futo.platformplayer.logging.Logger -import com.futo.platformplayer.polycentric.PolycentricCache import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.views.FeedStyle import com.futo.platformplayer.views.others.CreatorThumbnail diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/SubscriptionViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/SubscriptionViewHolder.kt index 05a3f5e7..a9e7110a 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/SubscriptionViewHolder.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/SubscriptionViewHolder.kt @@ -15,7 +15,6 @@ 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.states.StateApp import com.futo.platformplayer.toHumanTimeIndicator diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewPostView.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewPostView.kt index cbf0b8a6..1d90e09c 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewPostView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/feedtypes/PreviewPostView.kt @@ -30,7 +30,6 @@ import com.futo.platformplayer.dp import com.futo.platformplayer.fixHtmlWhitespace import com.futo.platformplayer.images.GlideHelper.Companion.crossfade import com.futo.platformplayer.logging.Logger -import com.futo.platformplayer.polycentric.PolycentricCache import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.toHumanNowDiffString import com.futo.platformplayer.views.FeedStyle 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 f2123832..75d332e5 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 @@ -24,7 +24,6 @@ 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 diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/CreatorBarViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/CreatorBarViewHolder.kt index 897718bf..9a171df6 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/CreatorBarViewHolder.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/CreatorBarViewHolder.kt @@ -11,7 +11,6 @@ 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.polycentric.PolycentricCache import com.futo.platformplayer.selectBestImage import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.views.adapters.AnyAdapter 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 b5784d4c..93576fec 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 @@ -12,7 +12,6 @@ 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.polycentric.PolycentricCache import com.futo.platformplayer.selectBestImage import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.toHumanNumber 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 da491cf6..e56fcf3a 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 @@ -12,7 +12,6 @@ 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.states.StateApp import com.futo.platformplayer.views.adapters.AnyAdapter diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/SubscriptionGroupListViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/SubscriptionGroupListViewHolder.kt index 19fe8a30..4f601d26 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/SubscriptionGroupListViewHolder.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/SubscriptionGroupListViewHolder.kt @@ -19,7 +19,6 @@ import com.futo.platformplayer.dp import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.models.Subscription import com.futo.platformplayer.models.SubscriptionGroup -import com.futo.platformplayer.polycentric.PolycentricCache import com.futo.platformplayer.selectBestImage import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.views.adapters.AnyAdapter diff --git a/app/src/main/java/com/futo/platformplayer/views/others/CreatorThumbnail.kt b/app/src/main/java/com/futo/platformplayer/views/others/CreatorThumbnail.kt index 2f592123..472a516f 100644 --- a/app/src/main/java/com/futo/platformplayer/views/others/CreatorThumbnail.kt +++ b/app/src/main/java/com/futo/platformplayer/views/others/CreatorThumbnail.kt @@ -11,8 +11,8 @@ import androidx.constraintlayout.widget.ConstraintLayout import com.bumptech.glide.Glide import com.futo.platformplayer.R import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.getDataLinkFromUrl import com.futo.platformplayer.images.GlideHelper.Companion.crossfade -import com.futo.platformplayer.polycentric.PolycentricCache import com.futo.platformplayer.views.IdenticonView import userpackage.Protocol @@ -68,7 +68,7 @@ class CreatorThumbnail : ConstraintLayout { if (url.startsWith("polycentric://")) { try { - val dataLink = PolycentricCache.getDataLinkFromUrl(url) + val dataLink = url.getDataLinkFromUrl() setHarborAvailable(true, animate, dataLink?.system); } catch (e: Throwable) { setHarborAvailable(false, animate, null); 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 dfc59e05..e451806c 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 @@ -5,8 +5,8 @@ import android.util.AttributeSet import android.widget.LinearLayout import com.futo.platformplayer.R import com.futo.platformplayer.constructs.Event0 -import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile import com.futo.platformplayer.views.SupportView +import com.futo.polycentric.core.PolycentricProfile class SupportOverlay : LinearLayout { val onClose = Event0(); diff --git a/app/src/main/java/com/futo/platformplayer/views/overlays/WebviewOverlay.kt b/app/src/main/java/com/futo/platformplayer/views/overlays/WebviewOverlay.kt index 06deadb8..27befb1e 100644 --- a/app/src/main/java/com/futo/platformplayer/views/overlays/WebviewOverlay.kt +++ b/app/src/main/java/com/futo/platformplayer/views/overlays/WebviewOverlay.kt @@ -6,9 +6,7 @@ import android.webkit.WebView import android.widget.LinearLayout import com.futo.platformplayer.R import com.futo.platformplayer.constructs.Event0 -import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile import com.futo.platformplayer.logging.Logger -import com.futo.platformplayer.views.SupportView class WebviewOverlay : LinearLayout { val onClose = Event0(); diff --git a/app/src/main/java/com/futo/platformplayer/views/segments/CommentsList.kt b/app/src/main/java/com/futo/platformplayer/views/segments/CommentsList.kt index 965d3014..a1ccd142 100644 --- a/app/src/main/java/com/futo/platformplayer/views/segments/CommentsList.kt +++ b/app/src/main/java/com/futo/platformplayer/views/segments/CommentsList.kt @@ -22,12 +22,12 @@ import com.futo.platformplayer.api.media.structures.IPager import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.constructs.TaskHandler import com.futo.platformplayer.engine.exceptions.ScriptUnavailableException -import com.futo.platformplayer.fullyBackfillServersAnnounceExceptions import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.states.StatePolycentric import com.futo.platformplayer.views.adapters.CommentViewHolder import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader +import com.futo.polycentric.core.fullyBackfillServersAnnounceExceptions import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import java.net.UnknownHostException diff --git a/dep/polycentricandroid b/dep/polycentricandroid index 44edd69e..f87f00ab 160000 --- a/dep/polycentricandroid +++ b/dep/polycentricandroid @@ -1 +1 @@ -Subproject commit 44edd69ece9cac4a6dd95a84ca91299e44f3650a +Subproject commit f87f00ab9e1262e300246b8963591bdf3a8fada7 From 7c256782118997ce4d4c9476b30a40da67005160 Mon Sep 17 00:00:00 2001 From: Kelvin Date: Mon, 10 Mar 2025 22:12:17 +0100 Subject: [PATCH 27/29] Subgroup sub image url, ImageVariable default error icon on fail to load --- .../mainactivity/main/SubscriptionGroupFragment.kt | 2 ++ .../com/futo/platformplayer/models/ImageVariable.kt | 12 ++++++++++-- app/src/stable/assets/sources/spotify | 2 +- app/src/stable/assets/sources/youtube | 2 +- app/src/unstable/assets/sources/spotify | 2 +- app/src/unstable/assets/sources/youtube | 2 +- dep/polycentricandroid | 2 +- 7 files changed, 17 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SubscriptionGroupFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SubscriptionGroupFragment.kt index 9fd4d7d6..a2875647 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SubscriptionGroupFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SubscriptionGroupFragment.kt @@ -256,6 +256,8 @@ class SubscriptionGroupFragment : MainFragment() { val sub = StateSubscriptions.instance.getSubscription(sub) ?: StateSubscriptions.instance.getSubscriptionOther(sub); if(sub != null && sub.channel.thumbnail != null) { g.image = ImageVariable.fromUrl(sub.channel.thumbnail!!); + if(g.image != null) + g.image!!.subscriptionUrl = sub.channel.url; g.image?.setImageView(_imageGroup); g.image?.setImageView(_imageGroupBackground); break; diff --git a/app/src/main/java/com/futo/platformplayer/models/ImageVariable.kt b/app/src/main/java/com/futo/platformplayer/models/ImageVariable.kt index 1de1f917..00594df7 100644 --- a/app/src/main/java/com/futo/platformplayer/models/ImageVariable.kt +++ b/app/src/main/java/com/futo/platformplayer/models/ImageVariable.kt @@ -7,6 +7,7 @@ import android.widget.ImageView import com.bumptech.glide.Glide import com.futo.platformplayer.PresetImages import com.futo.platformplayer.R +import com.futo.platformplayer.logging.Logger import kotlinx.serialization.Contextual import kotlinx.serialization.Transient import java.io.File @@ -18,7 +19,8 @@ data class ImageVariable( @Transient @Contextual private val bitmap: Bitmap? = null, - val presetName: String? = null) { + val presetName: String? = null, + var subscriptionUrl: String? = null) { @SuppressLint("DiscouragedApi") fun setImageView(imageView: ImageView, fallbackResId: Int = -1) { @@ -63,7 +65,13 @@ data class ImageVariable( return ImageVariable(null, null, null, str); } fun fromFile(file: File): ImageVariable { - return ImageVariable.fromBitmap(BitmapFactory.decodeFile(file.absolutePath)); + try { + return ImageVariable.fromBitmap(BitmapFactory.decodeFile(file.absolutePath)); + } + catch(ex: Throwable) { + Logger.e("ImageVariable", "Unsupported image format? " + ex.message, ex); + return fromResource(R.drawable.ic_error_pred); + } } } } \ No newline at end of file diff --git a/app/src/stable/assets/sources/spotify b/app/src/stable/assets/sources/spotify index 0d05e35c..9c36c457 160000 --- a/app/src/stable/assets/sources/spotify +++ b/app/src/stable/assets/sources/spotify @@ -1 +1 @@ -Subproject commit 0d05e35cfc81acfa78594c91c381b79694aaf86d +Subproject commit 9c36c457463fc2a452f76eb74465e9e234cdaf69 diff --git a/app/src/stable/assets/sources/youtube b/app/src/stable/assets/sources/youtube index 857c147b..6e0fe924 160000 --- a/app/src/stable/assets/sources/youtube +++ b/app/src/stable/assets/sources/youtube @@ -1 +1 @@ -Subproject commit 857c147b3a3d3e9d0a79c47f1bd5813e08ed2daf +Subproject commit 6e0fe9245143336a31954d678cdd22cbc3e0e115 diff --git a/app/src/unstable/assets/sources/spotify b/app/src/unstable/assets/sources/spotify index 0d05e35c..9c36c457 160000 --- a/app/src/unstable/assets/sources/spotify +++ b/app/src/unstable/assets/sources/spotify @@ -1 +1 @@ -Subproject commit 0d05e35cfc81acfa78594c91c381b79694aaf86d +Subproject commit 9c36c457463fc2a452f76eb74465e9e234cdaf69 diff --git a/app/src/unstable/assets/sources/youtube b/app/src/unstable/assets/sources/youtube index 857c147b..6e0fe924 160000 --- a/app/src/unstable/assets/sources/youtube +++ b/app/src/unstable/assets/sources/youtube @@ -1 +1 @@ -Subproject commit 857c147b3a3d3e9d0a79c47f1bd5813e08ed2daf +Subproject commit 6e0fe9245143336a31954d678cdd22cbc3e0e115 diff --git a/dep/polycentricandroid b/dep/polycentricandroid index 44edd69e..f87f00ab 160000 --- a/dep/polycentricandroid +++ b/dep/polycentricandroid @@ -1 +1 @@ -Subproject commit 44edd69ece9cac4a6dd95a84ca91299e44f3650a +Subproject commit f87f00ab9e1262e300246b8963591bdf3a8fada7 From 9165a9f7cbc3af0466ee6c67a00be9715cb32fb0 Mon Sep 17 00:00:00 2001 From: Kai DeLorenzo Date: Mon, 17 Mar 2025 23:24:15 +0000 Subject: [PATCH 28/29] Add : support for login button selector --- .../java/com/futo/platformplayer/activities/LoginActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 36041919..6ea7bd67 100644 --- a/app/src/main/java/com/futo/platformplayer/activities/LoginActivity.kt +++ b/app/src/main/java/com/futo/platformplayer/activities/LoginActivity.kt @@ -113,7 +113,7 @@ class LoginActivity : AppCompatActivity() { companion object { private val TAG = "LoginActivity"; - private val REGEX_LOGIN_BUTTON = Regex("[a-zA-Z\\-\\.#_ ]*"); + private val REGEX_LOGIN_BUTTON = Regex("[a-zA-Z\\-\\.#:_ ]*"); private var _callback: ((SourceAuth?) -> Unit)? = null; From 54d58df4b6080396162f438c12a82961b9c3087d Mon Sep 17 00:00:00 2001 From: Kelvin K Date: Fri, 21 Mar 2025 02:23:55 +0100 Subject: [PATCH 29/29] Sync watch later on initial connection, Original audio boolean support, priority audio support, setting to prefer original audio --- .../java/com/futo/platformplayer/Settings.kt | 4 +++- .../futo/platformplayer/UISlideOverlays.kt | 2 +- .../models/streams/sources/AudioUrlSource.kt | 7 +++++-- .../streams/sources/HLSVariantUrlSource.kt | 1 + .../models/streams/sources/IAudioSource.kt | 1 + .../streams/sources/LocalAudioSource.kt | 1 + .../js/models/sources/JSAudioUrlSource.kt | 3 +++ .../sources/JSDashManifestRawAudioSource.kt | 2 ++ .../sources/JSHLSManifestAudioSource.kt | 2 ++ .../platformplayer/helpers/VideoHelper.kt | 10 ++++++++-- .../com/futo/platformplayer/parsers/HLS.kt | 4 ++-- .../platformplayer/states/StatePlaylists.kt | 19 +++++++++++-------- .../sync/internal/SyncSession.kt | 2 ++ app/src/main/res/values/strings.xml | 2 ++ 14 files changed, 44 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/Settings.kt b/app/src/main/java/com/futo/platformplayer/Settings.kt index c95947ea..c421c9a3 100644 --- a/app/src/main/java/com/futo/platformplayer/Settings.kt +++ b/app/src/main/java/com/futo/platformplayer/Settings.kt @@ -356,7 +356,7 @@ class Settings : FragmentedStorageFileJson() { var playback = PlaybackSettings(); @Serializable class PlaybackSettings { - @FormField(R.string.primary_language, FieldForm.DROPDOWN, -1, -1) + @FormField(R.string.primary_language, FieldForm.DROPDOWN, -1, -2) @DropdownFieldOptionsId(R.array.audio_languages) var primaryLanguage: Int = 0; @@ -380,6 +380,8 @@ class Settings : FragmentedStorageFileJson() { else -> null } } + @FormField(R.string.prefer_original_audio, FieldForm.TOGGLE, R.string.prefer_original_audio_description, -1) + var preferOriginalAudio: Boolean = true; //= context.resources.getStringArray(R.array.audio_languages)[primaryLanguage]; diff --git a/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt b/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt index 67497b1e..a1aa71b7 100644 --- a/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt +++ b/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt @@ -402,7 +402,7 @@ class UISlideOverlays { UIDialogs.toast(container.context, "Variant video HLS playlist download started") slideUpMenuOverlay.hide() } else if (source is IHLSManifestAudioSource) { - StateDownloads.instance.download(video, null, HLSVariantAudioUrlSource("variant", 0, "application/vnd.apple.mpegurl", "", "", null, false, sourceUrl), null) + StateDownloads.instance.download(video, null, HLSVariantAudioUrlSource("variant", 0, "application/vnd.apple.mpegurl", "", "", null, false, false, sourceUrl), null) UIDialogs.toast(container.context, "Variant audio HLS playlist download started") slideUpMenuOverlay.hide() } else { diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/AudioUrlSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/AudioUrlSource.kt index 67548b89..a4d2cb55 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/AudioUrlSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/AudioUrlSource.kt @@ -13,7 +13,8 @@ class AudioUrlSource( override val codec: String = "", override val language: String = Language.UNKNOWN, override val duration: Long? = null, - override var priority: Boolean = false + override var priority: Boolean = false, + override var original: Boolean = false ) : IAudioUrlSource, IStreamMetaDataSource{ override var streamMetaData: StreamMetaData? = null; @@ -36,7 +37,9 @@ class AudioUrlSource( source.container, source.codec, source.language, - source.duration + source.duration, + source.priority, + source.original ); ret.streamMetaData = streamData; diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/HLSVariantUrlSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/HLSVariantUrlSource.kt index 36df5fb2..854cf9b8 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/HLSVariantUrlSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/HLSVariantUrlSource.kt @@ -27,6 +27,7 @@ class HLSVariantAudioUrlSource( override val language: String, override val duration: Long?, override val priority: Boolean, + override val original: Boolean, val url: String ) : IAudioUrlSource { override fun getAudioUrl(): String { diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/IAudioSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/IAudioSource.kt index eca17e47..f2c95b08 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/IAudioSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/IAudioSource.kt @@ -8,4 +8,5 @@ interface IAudioSource { val language : String; val duration : Long?; val priority: Boolean; + val original: Boolean; } \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/LocalAudioSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/LocalAudioSource.kt index 254b9731..1f616307 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/LocalAudioSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/streams/sources/LocalAudioSource.kt @@ -15,6 +15,7 @@ class LocalAudioSource : IAudioSource, IStreamMetaDataSource { override val duration: Long? = null; override var priority: Boolean = false; + override val original: Boolean = false; val filePath : String; val fileSize: Long; diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSAudioUrlSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSAudioUrlSource.kt index 09de1f35..0edc4f73 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSAudioUrlSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSAudioUrlSource.kt @@ -21,6 +21,8 @@ open class JSAudioUrlSource : IAudioUrlSource, JSSource { override var priority: Boolean = false; + override var original: Boolean = false; + constructor(plugin: JSClient, obj: V8ValueObject) : super(TYPE_AUDIOURL, plugin, obj) { val contextName = "AudioUrlSource"; val config = plugin.config; @@ -35,6 +37,7 @@ open class JSAudioUrlSource : IAudioUrlSource, JSSource { name = _obj.getOrDefault(config, "name", contextName, "${container} ${bitrate}") ?: "${container} ${bitrate}"; priority = if(_obj.has("priority")) obj.getOrThrow(config, "priority", contextName) else false; + original = if(_obj.has("original")) obj.getOrThrow(config, "original", contextName) else false; } override fun getAudioUrl() : String { diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawAudioSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawAudioSource.kt index 93ed6a01..ae35207b 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawAudioSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawAudioSource.kt @@ -23,6 +23,7 @@ class JSDashManifestRawAudioSource : JSSource, IAudioSource, IJSDashManifestRawS override val bitrate: Int; override val duration: Long; override val priority: Boolean; + override var original: Boolean = false; override val language: String; @@ -45,6 +46,7 @@ class JSDashManifestRawAudioSource : JSSource, IAudioSource, IJSDashManifestRawS duration = _obj.getOrDefault(config, "duration", contextName, 0) ?: 0; priority = _obj.getOrDefault(config, "priority", contextName, false) ?: false; language = _obj.getOrDefault(config, "language", contextName, Language.UNKNOWN) ?: Language.UNKNOWN; + original = if(_obj.has("original")) obj.getOrThrow(config, "original", contextName) else false; hasGenerate = _obj.has("generate"); } diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSHLSManifestAudioSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSHLSManifestAudioSource.kt index 41948802..9e328df3 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSHLSManifestAudioSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSHLSManifestAudioSource.kt @@ -21,6 +21,7 @@ class JSHLSManifestAudioSource : IHLSManifestAudioSource, JSSource { override val language: String; override var priority: Boolean = false; + override var original: Boolean = false; constructor(plugin: JSClient, obj: V8ValueObject) : super(TYPE_HLS, plugin, obj) { val contextName = "HLSAudioSource"; @@ -32,6 +33,7 @@ class JSHLSManifestAudioSource : IHLSManifestAudioSource, JSSource { language = _obj.getOrThrow(config, "language", contextName); priority = obj.getOrNull(config, "priority", contextName) ?: false; + original = if(_obj.has("original")) obj.getOrThrow(config, "original", contextName) else false; } 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 87e8f051..522647de 100644 --- a/app/src/main/java/com/futo/platformplayer/helpers/VideoHelper.kt +++ b/app/src/main/java/com/futo/platformplayer/helpers/VideoHelper.kt @@ -9,6 +9,7 @@ import androidx.media3.datasource.ResolvingDataSource import androidx.media3.exoplayer.dash.DashMediaSource import androidx.media3.exoplayer.dash.manifest.DashManifestParser import androidx.media3.exoplayer.source.MediaSource +import com.futo.platformplayer.Settings 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.IAudioSource @@ -85,12 +86,17 @@ class VideoHelper { return selectBestAudioSource((desc as VideoUnMuxedSourceDescriptor).audioSources.toList(), prefContainers, prefLanguage, targetBitrate); } - fun selectBestAudioSource(altSources : Iterable, prefContainers : Array, preferredLanguage: String? = null, targetBitrate: Long? = null) : IAudioSource? { + fun selectBestAudioSource(sources : Iterable, prefContainers : Array, preferredLanguage: String? = null, targetBitrate: Long? = null) : IAudioSource? { + val hasPriority = sources.any { it.priority }; + var altSources = if(hasPriority) sources.filter { it.priority } else sources; + val hasOriginal = altSources.any { it.original }; + if(hasOriginal && Settings.instance.playback.preferOriginalAudio) + altSources = altSources.filter { it.original }; val languageToFilter = if(preferredLanguage != null && altSources.any { it.language == preferredLanguage }) { preferredLanguage } else { if(altSources.any { it.language == Language.ENGLISH }) - Language.ENGLISH + Language.ENGLISH; else Language.UNKNOWN; } 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 9d1a3faa..7d57a151 100644 --- a/app/src/main/java/com/futo/platformplayer/parsers/HLS.kt +++ b/app/src/main/java/com/futo/platformplayer/parsers/HLS.kt @@ -119,7 +119,7 @@ class HLS { return if (source is IHLSManifestSource) { listOf() } else if (source is IHLSManifestAudioSource) { - listOf(HLSVariantAudioUrlSource("variant", 0, "application/vnd.apple.mpegurl", "", "", null, false, url)) + listOf(HLSVariantAudioUrlSource("variant", 0, "application/vnd.apple.mpegurl", "", "", null, false, false, url)) } else { throw NotImplementedError() } @@ -340,7 +340,7 @@ class HLS { val suffix = listOf(it.language, it.groupID).mapNotNull { x -> x?.ifEmpty { null } }.joinToString(", ") return@mapNotNull when (it.type) { - "AUDIO" -> HLSVariantAudioUrlSource(it.name?.ifEmpty { "Audio (${suffix})" } ?: "Audio (${suffix})", 0, "application/vnd.apple.mpegurl", "", it.language ?: "", null, false, it.uri) + "AUDIO" -> HLSVariantAudioUrlSource(it.name?.ifEmpty { "Audio (${suffix})" } ?: "Audio (${suffix})", 0, "application/vnd.apple.mpegurl", "", it.language ?: "", null, false, false, it.uri) else -> null } } 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 4a41760a..e2054c90 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StatePlaylists.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StatePlaylists.kt @@ -184,7 +184,7 @@ class StatePlaylists { wasNew = true; _watchlistStore.saveAsync(video); if(orderPosition == -1) - _watchlistOrderStore.set(*(listOf(video.url) + _watchlistOrderStore.values) .toTypedArray()); + _watchlistOrderStore.set(*(listOf(video.url) + _watchlistOrderStore.values).toTypedArray()); else { val existing = _watchlistOrderStore.getAllValues().toMutableList(); existing.add(orderPosition, video.url); @@ -230,17 +230,20 @@ class StatePlaylists { } } + public fun getWatchLaterSyncPacket(orderOnly: Boolean = false): SyncWatchLaterPackage{ + return SyncWatchLaterPackage( + if (orderOnly) listOf() else getWatchLater(), + if (orderOnly) mapOf() else _watchLaterAdds.all(), + if (orderOnly) mapOf() else _watchLaterRemovals.all(), + getWatchLaterLastReorderTime().toEpochSecond(), + _watchlistOrderStore.values.toList() + ) + } private fun broadcastWatchLater(orderOnly: Boolean = false) { StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) { try { StateSync.instance.broadcastJsonData( - GJSyncOpcodes.syncWatchLater, SyncWatchLaterPackage( - if (orderOnly) listOf() else getWatchLater(), - if (orderOnly) mapOf() else _watchLaterAdds.all(), - if (orderOnly) mapOf() else _watchLaterRemovals.all(), - getWatchLaterLastReorderTime().toEpochSecond(), - _watchlistOrderStore.values.toList() - ) + GJSyncOpcodes.syncWatchLater, getWatchLaterSyncPacket(orderOnly) ); } catch (e: Throwable) { Logger.w(TAG, "Failed to broadcast watch later", e) diff --git a/app/src/main/java/com/futo/platformplayer/sync/internal/SyncSession.kt b/app/src/main/java/com/futo/platformplayer/sync/internal/SyncSession.kt index 8b5621e0..e4273d63 100644 --- a/app/src/main/java/com/futo/platformplayer/sync/internal/SyncSession.kt +++ b/app/src/main/java/com/futo/platformplayer/sync/internal/SyncSession.kt @@ -232,6 +232,8 @@ class SyncSession : IAuthorizable { sendData(GJSyncOpcodes.syncSubscriptionGroups, StateSubscriptionGroups.instance.getSyncSubscriptionGroupsPackageString()); sendData(GJSyncOpcodes.syncPlaylists, StatePlaylists.instance.getSyncPlaylistsPackageString()) + sendData(GJSyncOpcodes.syncWatchLater, Json.encodeToString(StatePlaylists.instance.getWatchLaterSyncPacket(false))); + val recentHistory = StateHistory.instance.getRecentHistory(syncSessionData.lastHistory); if(recentHistory.size > 0) sendJsonData(GJSyncOpcodes.syncHistory, recentHistory); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d4df1905..8f5605c0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -447,6 +447,8 @@ Preferred Preview Quality Default quality while previewing a video in a feed Primary Language + Prefer Original Audio + Use original audio instead of preferred language when it is known Default Comment Section Hide Recommendations Fully hide the recommendations tab.