From 90dca2537a9b1999ce325cd3526ca91f2593f135 Mon Sep 17 00:00:00 2001 From: Kelvin Date: Tue, 29 Jul 2025 01:39:32 +0200 Subject: [PATCH] getUserHistory support --- app/src/main/assets/scripts/source.js | 3 + .../api/media/IPlatformClient.kt | 4 + .../api/media/PlatformClientCapabilities.kt | 3 +- .../api/media/models/video/IPlatformVideo.kt | 4 + .../models/video/SerializedPlatformVideo.kt | 6 +- .../video/SerializedPlatformVideoDetails.kt | 5 +- .../api/media/platforms/js/JSClient.kt | 10 +- .../platforms/js/SourcePluginDescriptor.kt | 29 +++++- .../api/media/platforms/js/models/JSVideo.kt | 14 +++ .../local/models/LocalVideoDetails.kt | 7 +- .../developer/DeveloperEndpoints.kt | 6 +- .../platformplayer/downloads/VideoLocal.kt | 4 + .../engine/dev/V8RemoteObject.kt | 2 +- .../mainactivity/main/TutorialFragment.kt | 4 + .../futo/platformplayer/states/StateApp.kt | 14 +++ .../platformplayer/states/StateHistory.kt | 95 ++++++++++++++++++- .../platformplayer/states/StatePlatform.kt | 10 ++ app/src/main/res/values/strings.xml | 4 + 18 files changed, 210 insertions(+), 14 deletions(-) diff --git a/app/src/main/assets/scripts/source.js b/app/src/main/assets/scripts/source.js index 7f348347..85195f47 100644 --- a/app/src/main/assets/scripts/source.js +++ b/app/src/main/assets/scripts/source.js @@ -251,6 +251,9 @@ class PlatformVideo extends PlatformContent { this.duration = obj.duration ?? -1; //Long this.viewCount = obj.viewCount ?? -1; //Long + this.playbackTime = obj.playbackTime ?? -1; + this.playbackDate = obj.playbackDate ?? undefined; + this.isLive = obj.isLive ?? false; //Boolean this.isShort = !!obj.isShort ?? false; } diff --git a/app/src/main/java/com/futo/platformplayer/api/media/IPlatformClient.kt b/app/src/main/java/com/futo/platformplayer/api/media/IPlatformClient.kt index 56d8fbd2..894dfca4 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/IPlatformClient.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/IPlatformClient.kt @@ -182,6 +182,10 @@ interface IPlatformClient { * Retrieves the subscriptions of the currently logged in user */ fun getUserSubscriptions(): Array; + /** + * Retrieves the history of the currently logged in user + */ + fun getUserHistory(): IPager; fun isClaimTypeSupported(claimType: Int): Boolean; diff --git a/app/src/main/java/com/futo/platformplayer/api/media/PlatformClientCapabilities.kt b/app/src/main/java/com/futo/platformplayer/api/media/PlatformClientCapabilities.kt index cb62b66c..aa65a18b 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/PlatformClientCapabilities.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/PlatformClientCapabilities.kt @@ -20,7 +20,8 @@ data class PlatformClientCapabilities( val hasGetContentChapters: Boolean = false, val hasPeekChannelContents: Boolean = false, val hasGetChannelPlaylists: Boolean = false, - val hasGetContentRecommendations: Boolean = false + val hasGetContentRecommendations: Boolean = false, + val hasGetUserHistory: Boolean = false ) { } \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/video/IPlatformVideo.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/video/IPlatformVideo.kt index bd4a80ec..de635396 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/models/video/IPlatformVideo.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/video/IPlatformVideo.kt @@ -2,6 +2,7 @@ package com.futo.platformplayer.api.media.models.video import com.futo.platformplayer.api.media.models.Thumbnails import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import java.time.OffsetDateTime /** * A search result representing a video (overview data) @@ -12,6 +13,9 @@ interface IPlatformVideo : IPlatformContent { val duration: Long; val viewCount: Long; + val playbackTime: Long; + val playbackDate: OffsetDateTime?; + val isLive : Boolean; val isShort: Boolean; diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/video/SerializedPlatformVideo.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/video/SerializedPlatformVideo.kt index c9e02d92..eadffc8d 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/models/video/SerializedPlatformVideo.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/video/SerializedPlatformVideo.kt @@ -6,8 +6,6 @@ import com.futo.platformplayer.api.media.models.PlatformAuthorLink import com.futo.platformplayer.api.media.models.Thumbnails import com.futo.platformplayer.api.media.models.contents.ContentType import com.futo.platformplayer.serializers.OffsetDateTimeNullableSerializer -import com.futo.polycentric.core.combineHashCodes -import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonNames @@ -33,6 +31,10 @@ open class SerializedPlatformVideo( override val isLive: Boolean = false; + override var playbackTime: Long = -1; + @kotlinx.serialization.Serializable(with = OffsetDateTimeNullableSerializer::class) + override var playbackDate: OffsetDateTime? = null; + override fun toJson() : String { return Json.encodeToString(this); } diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/video/SerializedPlatformVideoDetails.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/video/SerializedPlatformVideoDetails.kt index 5354352f..b7e343ca 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/models/video/SerializedPlatformVideoDetails.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/video/SerializedPlatformVideoDetails.kt @@ -13,7 +13,6 @@ import com.futo.platformplayer.api.media.models.ratings.IRating import com.futo.platformplayer.api.media.models.streams.sources.* import com.futo.platformplayer.api.media.structures.IPager import com.futo.platformplayer.serializers.OffsetDateTimeNullableSerializer -import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import java.time.OffsetDateTime @@ -43,6 +42,10 @@ open class SerializedPlatformVideoDetails( ) : IPlatformVideo, IPlatformVideoDetails { final override val contentType: ContentType get() = ContentType.MEDIA; + override var playbackTime: Long = -1; + @kotlinx.serialization.Serializable(with = OffsetDateTimeNullableSerializer::class) + override var playbackDate: OffsetDateTime? = null; + override val isLive: Boolean get() = false; override val dash: IDashManifestSource? get() = null; diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt index fd3c9dde..2233048f 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt @@ -272,7 +272,8 @@ open class JSClient : IPlatformClient { hasGetContentChapters = plugin.executeBoolean("!!source.getContentChapters") ?: false, hasPeekChannelContents = plugin.executeBoolean("!!source.peekChannelContents") ?: false, hasGetChannelPlaylists = plugin.executeBoolean("!!source.getChannelPlaylists") ?: false, - hasGetContentRecommendations = plugin.executeBoolean("!!source.getContentRecommendations") ?: false + hasGetContentRecommendations = plugin.executeBoolean("!!source.getContentRecommendations") ?: false, + hasGetUserHistory = plugin.executeBoolean("!!source.getUserHistory") ?: false ); try { @@ -712,6 +713,13 @@ open class JSClient : IPlatformClient { .toTypedArray(); } + @JSOptional + @JSDocs(23, "source.getUserHistory()", "Gets the history of the current user") + override fun getUserHistory(): IPager { + ensureEnabled(); + return JSContentPager(config, this, plugin.executeTyped("source.getUserHistory()")); + } + fun validate() { try { plugin.start(); diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginDescriptor.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginDescriptor.kt index 971c7baa..55921618 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginDescriptor.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginDescriptor.kt @@ -5,10 +5,16 @@ import com.futo.platformplayer.constructs.Event0 import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.states.AnnouncementType import com.futo.platformplayer.states.StateAnnouncement +import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.states.StateHistory +import com.futo.platformplayer.states.StatePlatform import com.futo.platformplayer.views.fields.DropdownFieldOptions import com.futo.platformplayer.views.fields.FieldForm import com.futo.platformplayer.views.fields.FormField +import com.futo.platformplayer.views.fields.FormFieldButton import com.futo.platformplayer.views.fields.FormFieldWarning +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import kotlinx.serialization.Serializable @Serializable @@ -110,7 +116,28 @@ class SourcePluginDescriptor { var enableShorts: Boolean? = null; } - @FormField(R.string.ratelimit, "group", R.string.ratelimit_description, 3) + @FormField(R.string.sync, "group", R.string.sync_desc, 3) + var sync = Sync(); + @Serializable + class Sync { + @FormField(R.string.sync_history, FieldForm.TOGGLE, R.string.sync_history_desc, 1) + var enableHistorySync: Boolean? = null; + + @FormField(R.string.sync_history, FieldForm.BUTTON, R.string.sync_history_desc, 2) + @FormFieldButton() + fun syncHistoryNow() { + StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) { + val clients = StatePlatform.instance.getEnabledClients(); + for (client in clients) { + if (client is JSClient) {//) && client.descriptor.appSettings.sync.enableHistorySync == true) { + StateHistory.instance.syncRemoteHistory(client); + } + } + }; + } + } + + @FormField(R.string.ratelimit, "group", R.string.ratelimit_description, 4) var rateLimit = RateLimit(); @Serializable class RateLimit { diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSVideo.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSVideo.kt index 13c0aead..0b033af7 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSVideo.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSVideo.kt @@ -8,6 +8,10 @@ import com.futo.platformplayer.api.media.models.video.IPlatformVideo import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig import com.futo.platformplayer.getOrDefault import com.futo.platformplayer.getOrThrow +import com.futo.platformplayer.serializers.OffsetDateTimeNullableSerializer +import java.time.LocalDateTime +import java.time.OffsetDateTime +import java.time.ZoneOffset open class JSVideo : JSContent, IPlatformVideo, IPluginSourced { final override val contentType: ContentType get() = ContentType.MEDIA; @@ -17,6 +21,10 @@ open class JSVideo : JSContent, IPlatformVideo, IPluginSourced { final override val duration: Long; final override val viewCount: Long; + override var playbackTime: Long = -1; + @kotlinx.serialization.Serializable(with = OffsetDateTimeNullableSerializer::class) + override var playbackDate: OffsetDateTime? = null; + final override val isLive: Boolean; final override val isShort: Boolean; @@ -29,5 +37,11 @@ open class JSVideo : JSContent, IPlatformVideo, IPluginSourced { viewCount = _content.getOrThrow(config, "viewCount", contextName); isLive = _content.getOrThrow(config, "isLive", contextName); isShort = _content.getOrDefault(config, "isShort", contextName, false) ?: false; + playbackTime = _content.getOrDefault(config, "playbackTime", contextName, -1)?.toLong() ?: -1; + val playbackDateInt = _content.getOrDefault(config, "playbackDate", contextName, null)?.toLong(); + if(playbackDateInt == null || playbackDateInt == 0.toLong()) + playbackDate = null; + else + playbackDate = OffsetDateTime.of(LocalDateTime.ofEpochSecond(playbackDateInt, 0, ZoneOffset.UTC), ZoneOffset.UTC); } } \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/local/models/LocalVideoDetails.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/local/models/LocalVideoDetails.kt index 1c169e64..7dff9cc5 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/local/models/LocalVideoDetails.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/local/models/LocalVideoDetails.kt @@ -11,7 +11,6 @@ import com.futo.platformplayer.api.media.models.playback.IPlaybackTracker import com.futo.platformplayer.api.media.models.ratings.IRating import com.futo.platformplayer.api.media.models.ratings.RatingLikes import com.futo.platformplayer.api.media.models.streams.IVideoSourceDescriptor -import com.futo.platformplayer.api.media.models.streams.DownloadedVideoMuxedSourceDescriptor import com.futo.platformplayer.api.media.models.streams.sources.IDashManifestSource import com.futo.platformplayer.api.media.models.streams.sources.IHLSManifestSource import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource @@ -19,7 +18,7 @@ import com.futo.platformplayer.api.media.models.subtitles.ISubtitleSource import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails import com.futo.platformplayer.api.media.platforms.local.models.sources.LocalVideoFileSource import com.futo.platformplayer.api.media.structures.IPager -import com.futo.platformplayer.downloads.VideoLocal +import com.futo.platformplayer.serializers.OffsetDateTimeNullableSerializer import java.io.File import java.time.Instant import java.time.OffsetDateTime @@ -53,6 +52,10 @@ class LocalVideoDetails: IPlatformVideoDetails { override val isLive: Boolean = false; override val isShort: Boolean = false; + override var playbackTime: Long = -1; + @kotlinx.serialization.Serializable(with = OffsetDateTimeNullableSerializer::class) + override var playbackDate: OffsetDateTime? = null; + constructor(file: File) { id = PlatformID("Local", file.path, "LOCAL") name = file.name; diff --git a/app/src/main/java/com/futo/platformplayer/developer/DeveloperEndpoints.kt b/app/src/main/java/com/futo/platformplayer/developer/DeveloperEndpoints.kt index acb57b78..4a7b8dd7 100644 --- a/app/src/main/java/com/futo/platformplayer/developer/DeveloperEndpoints.kt +++ b/app/src/main/java/com/futo/platformplayer/developer/DeveloperEndpoints.kt @@ -47,10 +47,10 @@ class DeveloperEndpoints(private val context: Context) { private val testPluginOrThrow: V8Plugin get() = _testPlugin ?: throw IllegalStateException("Attempted to use test plugin without plugin"); private val _testPluginVariables: HashMap = hashMapOf(); - private inline fun createRemoteObjectArray(objs: Iterable): List { - val remotes = mutableListOf(); + private inline fun createRemoteObjectArray(objs: Iterable): List { + val remotes = mutableListOf(); for(obj in objs) - remotes.add(createRemoteObject(obj)!!); + remotes.add(createRemoteObject(obj)); return remotes; } private inline fun createRemoteObject(obj: T): V8RemoteObject? { diff --git a/app/src/main/java/com/futo/platformplayer/downloads/VideoLocal.kt b/app/src/main/java/com/futo/platformplayer/downloads/VideoLocal.kt index 06095058..b3b877ab 100644 --- a/app/src/main/java/com/futo/platformplayer/downloads/VideoLocal.kt +++ b/app/src/main/java/com/futo/platformplayer/downloads/VideoLocal.kt @@ -73,6 +73,10 @@ class VideoLocal: IPlatformVideoDetails, IStoreItem { override val isShort: Boolean get() = videoSerialized.isShort; + override var playbackTime: Long = -1; + @kotlinx.serialization.Serializable(with = OffsetDateTimeNullableSerializer::class) + override var playbackDate: OffsetDateTime? = null; + //TODO: Offline subtitles override val subtitles: List = listOf(); diff --git a/app/src/main/java/com/futo/platformplayer/engine/dev/V8RemoteObject.kt b/app/src/main/java/com/futo/platformplayer/engine/dev/V8RemoteObject.kt index 9aa0c6cc..030c3646 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/dev/V8RemoteObject.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/dev/V8RemoteObject.kt @@ -136,7 +136,7 @@ class V8RemoteObject { } - fun List.serialize() : String { + fun List.serialize() : String { return _gson.toJson(this); } } diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/TutorialFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/TutorialFragment.kt index ba1c60d6..73ee0295 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/TutorialFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/TutorialFragment.kt @@ -32,6 +32,7 @@ import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails import com.futo.platformplayer.api.media.structures.EmptyPager import com.futo.platformplayer.api.media.structures.IPager import com.futo.platformplayer.fragment.mainactivity.topbar.NavigationTopBarFragment +import com.futo.platformplayer.serializers.OffsetDateTimeNullableSerializer import com.futo.platformplayer.views.pills.WidePillButton import java.time.OffsetDateTime @@ -152,6 +153,9 @@ class TutorialFragment : MainFragment() { override val viewCount: Long = -1 override val video: IVideoSourceDescriptor = TutorialVideoSourceDescriptor(videoUrl, duration, width, height) override val isShort: Boolean = false; + override var playbackTime: Long = -1; + @kotlinx.serialization.Serializable(with = OffsetDateTimeNullableSerializer::class) + override var playbackDate: OffsetDateTime? = null; override fun getComments(client: IPlatformClient): IPager { return EmptyPager() } diff --git a/app/src/main/java/com/futo/platformplayer/states/StateApp.kt b/app/src/main/java/com/futo/platformplayer/states/StateApp.kt index 9757f005..c7174c12 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateApp.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateApp.kt @@ -636,6 +636,20 @@ class StateApp { } } } + + scopeOrNull?.launch(Dispatchers.IO) { + val enabledPlugins = StatePlatform.instance.getEnabledClients(); + for(plugin in enabledPlugins) { + try { + if(plugin is JSClient) { + if(plugin.descriptor.appSettings.sync.enableHistorySync == true) + StateHistory.instance.syncRemoteHistory(plugin); + } + } catch (ex: Throwable) { + Logger.e(TAG, "Failed to update remote history for ${plugin.name}", ex); + } + } + } } fun mainAppStartedWithExternalFiles(context: Context) { diff --git a/app/src/main/java/com/futo/platformplayer/states/StateHistory.kt b/app/src/main/java/com/futo/platformplayer/states/StateHistory.kt index 97ab82c1..f3bcca55 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateHistory.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateHistory.kt @@ -1,15 +1,18 @@ package com.futo.platformplayer.states import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.api.media.models.contents.IPlatformContent import com.futo.platformplayer.api.media.models.video.IPlatformVideo import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo +import com.futo.platformplayer.api.media.platforms.js.JSClient import com.futo.platformplayer.api.media.structures.IPager import com.futo.platformplayer.constructs.Event2 import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.models.HistoryVideo import com.futo.platformplayer.models.ImportCache -import com.futo.platformplayer.states.StatePlaylists.Companion +import com.futo.platformplayer.states.StateApp.Companion import com.futo.platformplayer.stores.FragmentedStorage +import com.futo.platformplayer.stores.StringDateMapStorage import com.futo.platformplayer.stores.db.ManagedDBStore import com.futo.platformplayer.stores.db.types.DBHistory import com.futo.platformplayer.stores.v2.ReconstructStore @@ -19,7 +22,6 @@ import kotlinx.coroutines.launch import java.time.OffsetDateTime import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentMap -import kotlin.math.min class StateHistory { //Legacy @@ -31,6 +33,8 @@ class StateHistory { }) .load(); + private val _remoteHistoryDatesStore = FragmentedStorage.get("remoteHistoryDates"); + private val historyIndex: ConcurrentMap = ConcurrentHashMap(); val _historyDBStore = ManagedDBStore.create("history", DBHistory.Descriptor()) .withIndex({ it.url }, historyIndex, false, true) @@ -186,8 +190,95 @@ class StateHistory { val toDelete = _historyDBStore.getAllIndexes().filter { minutesToDelete == -1L || (now - it.datetime) < minutesToDelete * 60 }; for(item in toDelete) _historyDBStore.delete(item); + _remoteHistoryDatesStore.map = HashMap(); + _remoteHistoryDatesStore.save(); } + fun syncRemoteHistory(plugin: JSClient) { + if (plugin.capabilities.hasGetUserHistory && + plugin.isLoggedIn) { + Logger.i(TAG, "Syncing remote history for plugin [${plugin.name}]"); + + val hist = StatePlatform.instance.getUserHistory(plugin.id); + + syncRemoteHistory(plugin.id, hist, 100, 3); + } + } + fun syncRemoteHistory(pluginId: String, videos: IPager, maxVideos: Int, maxPages: Int) { + val lastDate = _remoteHistoryDatesStore.get(pluginId) ?: OffsetDateTime.MIN; + val maxVideosCount = if(maxVideos <= 0) 500 else maxVideos; + val maxPageCount = if(maxPages <= 0) 3 else maxPages; + var exceededDate = false; + try { + val toSync = mutableListOf(); + var pageCount = 0; + var videoCount = 0; + var isFirst = true; + var oldestPlayback = OffsetDateTime.MAX; + var newestPlayback = OffsetDateTime.MIN; + do { + if (!isFirst) videos.nextPage(); + val newVideos = videos.getResults(); + + var foundVideos = false; + var toSyncAddedCount = 0; + for(video in newVideos) { + if(video is IPlatformVideo && video.playbackDate != null) { + + if(video.playbackDate!! < lastDate) { + exceededDate = true; + break; + } + + if(video.playbackTime > 0) { + toSync.add(video); + toSyncAddedCount++; + foundVideos = true; + oldestPlayback = video.playbackDate!!; + if(newestPlayback == OffsetDateTime.MIN) + newestPlayback = video.playbackDate!!; + } + } + } + + pageCount++; + videoCount += newVideos.size; + isFirst = false; + + if(!foundVideos) + { + Logger.i(TAG, "Found no more videos in remote history"); + break; + } + } + while(videos.hasMorePages() && videoCount <= maxVideosCount && pageCount <= maxPageCount && !exceededDate); + + var updated = 0; + if(oldestPlayback < OffsetDateTime.MAX) { + for(video in toSync){ + val hist = getHistoryByVideo(video, true, video.playbackDate); + if(hist != null && hist.position < video.playbackTime) { + Logger.i(TAG, "Updated history for video [${video.name}] from remote history"); + updateHistoryPosition(video, hist, true, video.playbackTime, video.playbackDate, false); + updated++; + } + } + if(updated > 0) { + _remoteHistoryDatesStore.setAndSave(pluginId, newestPlayback); + + try { + val client = StatePlatform.instance.getClient(pluginId); + UIDialogs.appToast("Updated ${updated} history from ${client.name}") + } + catch(ex: Throwable){} + } + } + } + catch(ex: Throwable) { + val plugin = if(pluginId != StateDeveloper.DEV_ID) StatePlugins.instance.getPlugin(pluginId) else null; + Logger.e(TAG, "Sync Remote History failed for [${plugin?.config?.name}] due to: " + ex.message) + } + } companion object { val TAG = "StateHistory"; diff --git a/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt b/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt index 47aa3959..cba656dd 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt @@ -1036,6 +1036,16 @@ class StatePlatform { return client.getLiveChatWindow(url); } + //Account + fun getUserHistory(id: String): IPager { + val client = getClient(id); + if(client is JSClient && client.isLoggedIn) { + return client.fromPool(_pagerClientPool).getUserHistory() + } + return EmptyPager(); + } + + fun injectDevPlugin(source: SourcePluginConfig, script: String): String? { var devId: String? = null; diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c1327051..5ddeef06 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -11,6 +11,8 @@ General Channel Home + Sync Remote History + Synchronize account history from this platform on startup Progress Bar Advanced Settings If advanced settings should be shown, this exposes additional settings to finetune your experience. @@ -592,6 +594,8 @@ Various tests against a custom source Writes to disk till no space is left Visibility + Sync + Synchronization of platform data Check for updates If a plugin should be checked for updates on startup Automatic Update