mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2025-05-02 07:34:25 +02:00
WIP Watchlater sync
This commit is contained in:
parent
0034665965
commit
6cee33b449
@ -233,3 +233,42 @@ fun String.decodeUnicode(): String {
|
|||||||
}
|
}
|
||||||
return sb.toString()
|
return sb.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <T> smartMerge(targetArr: List<T>, toMerge: List<T>) : List<T>{
|
||||||
|
val missingToMerge = toMerge.filter { !targetArr.contains(it) }.toList();
|
||||||
|
val newArrResult = targetArr.toMutableList();
|
||||||
|
|
||||||
|
for(missing in missingToMerge) {
|
||||||
|
val newIndex = findNewIndex(toMerge, newArrResult, missing);
|
||||||
|
newArrResult.add(newIndex, missing);
|
||||||
|
}
|
||||||
|
|
||||||
|
return newArrResult;
|
||||||
|
}
|
||||||
|
fun <T> findNewIndex(originalArr: List<T>, newArr: List<T>, item: T): Int{
|
||||||
|
var originalIndex = originalArr.indexOf(item);
|
||||||
|
var newIndex = -1;
|
||||||
|
|
||||||
|
for(i in originalIndex-1 downTo 0) {
|
||||||
|
val previousItem = originalArr[i];
|
||||||
|
val indexInNewArr = newArr.indexOfFirst { it == previousItem };
|
||||||
|
if(indexInNewArr >= 0) {
|
||||||
|
newIndex = indexInNewArr + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(newIndex < 0) {
|
||||||
|
for(i in originalIndex+1 until originalArr.size) {
|
||||||
|
val previousItem = originalArr[i];
|
||||||
|
val indexInNewArr = newArr.indexOfFirst { it == previousItem };
|
||||||
|
if(indexInNewArr >= 0) {
|
||||||
|
newIndex = indexInNewArr - 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(newIndex < 0)
|
||||||
|
return originalArr.size;
|
||||||
|
else
|
||||||
|
return newIndex;
|
||||||
|
}
|
@ -389,7 +389,7 @@ class MenuBottomBarFragment : MainActivityFragment() {
|
|||||||
}),
|
}),
|
||||||
ButtonDefinition(96, R.drawable.ic_disabled_visible, R.drawable.ic_disabled_visible, R.string.privacy_mode, canToggle = true, { false }, {
|
ButtonDefinition(96, R.drawable.ic_disabled_visible, R.drawable.ic_disabled_visible, R.string.privacy_mode, canToggle = true, { false }, {
|
||||||
UIDialogs.showDialog(it.context ?: return@ButtonDefinition, R.drawable.ic_disabled_visible_purple, "Privacy Mode",
|
UIDialogs.showDialog(it.context ?: return@ButtonDefinition, R.drawable.ic_disabled_visible_purple, "Privacy Mode",
|
||||||
"All requests will be processed anonymously (unauthenticated), playback and history tracking will be disabled.\n\nTap the icon to disable.", null, 0,
|
"All requests will be processed anonymously (any logins will be disabled except for the personalized home page), local playback and history tracking will also be disabled.\n\nTap the icon to disable.", null, 0,
|
||||||
UIDialogs.Action("Cancel", {
|
UIDialogs.Action("Cancel", {
|
||||||
StateApp.instance.setPrivacyMode(false);
|
StateApp.instance.setPrivacyMode(false);
|
||||||
}, UIDialogs.ActionStyle.NONE),
|
}, UIDialogs.ActionStyle.NONE),
|
||||||
|
@ -12,6 +12,7 @@ import android.widget.LinearLayout
|
|||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.futo.platformplayer.R
|
import com.futo.platformplayer.R
|
||||||
@ -23,6 +24,8 @@ import com.futo.platformplayer.states.StatePlaylists
|
|||||||
import com.futo.platformplayer.views.adapters.*
|
import com.futo.platformplayer.views.adapters.*
|
||||||
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuOverlay
|
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuOverlay
|
||||||
import com.google.android.material.appbar.AppBarLayout
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
||||||
class PlaylistsFragment : MainFragment() {
|
class PlaylistsFragment : MainFragment() {
|
||||||
@ -119,7 +122,9 @@ class PlaylistsFragment : MainFragment() {
|
|||||||
|
|
||||||
findViewById<TextView>(R.id.text_view_all).setOnClickListener { _fragment.navigate<WatchLaterFragment>(context.getString(R.string.watch_later)); };
|
findViewById<TextView>(R.id.text_view_all).setOnClickListener { _fragment.navigate<WatchLaterFragment>(context.getString(R.string.watch_later)); };
|
||||||
StatePlaylists.instance.onWatchLaterChanged.subscribe(this) {
|
StatePlaylists.instance.onWatchLaterChanged.subscribe(this) {
|
||||||
|
fragment.lifecycleScope.launch(Dispatchers.Main) {
|
||||||
updateWatchLater();
|
updateWatchLater();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,7 +96,7 @@ class WatchLaterFragment : MainFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onVideoOrderChanged(videos: List<IPlatformVideo>) {
|
override fun onVideoOrderChanged(videos: List<IPlatformVideo>) {
|
||||||
StatePlaylists.instance.updateWatchLater(ArrayList(videos.map { it as SerializedPlatformVideo }));
|
StatePlaylists.instance.updateWatchLater(ArrayList(videos.map { it as SerializedPlatformVideo }), true);
|
||||||
}
|
}
|
||||||
override fun onVideoRemoved(video: IPlatformVideo) {
|
override fun onVideoRemoved(video: IPlatformVideo) {
|
||||||
if (video is SerializedPlatformVideo) {
|
if (video is SerializedPlatformVideo) {
|
||||||
|
@ -3,6 +3,7 @@ package com.futo.platformplayer.states
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
import com.futo.platformplayer.R
|
import com.futo.platformplayer.R
|
||||||
import com.futo.platformplayer.api.media.exceptions.NoPlatformClientException
|
import com.futo.platformplayer.api.media.exceptions.NoPlatformClientException
|
||||||
import com.futo.platformplayer.api.media.models.channels.IPlatformChannel
|
import com.futo.platformplayer.api.media.models.channels.IPlatformChannel
|
||||||
@ -17,22 +18,27 @@ import com.futo.platformplayer.exceptions.ReconstructionException
|
|||||||
import com.futo.platformplayer.logging.Logger
|
import com.futo.platformplayer.logging.Logger
|
||||||
import com.futo.platformplayer.models.ImportCache
|
import com.futo.platformplayer.models.ImportCache
|
||||||
import com.futo.platformplayer.models.Playlist
|
import com.futo.platformplayer.models.Playlist
|
||||||
|
import com.futo.platformplayer.smartMerge
|
||||||
import com.futo.platformplayer.states.StateSubscriptionGroups.Companion
|
import com.futo.platformplayer.states.StateSubscriptionGroups.Companion
|
||||||
import com.futo.platformplayer.stores.FragmentedStorage
|
import com.futo.platformplayer.stores.FragmentedStorage
|
||||||
import com.futo.platformplayer.stores.StringArrayStorage
|
import com.futo.platformplayer.stores.StringArrayStorage
|
||||||
import com.futo.platformplayer.stores.StringDateMapStorage
|
import com.futo.platformplayer.stores.StringDateMapStorage
|
||||||
|
import com.futo.platformplayer.stores.StringStorage
|
||||||
import com.futo.platformplayer.stores.v2.ManagedStore
|
import com.futo.platformplayer.stores.v2.ManagedStore
|
||||||
import com.futo.platformplayer.stores.v2.ReconstructStore
|
import com.futo.platformplayer.stores.v2.ReconstructStore
|
||||||
import com.futo.platformplayer.sync.internal.GJSyncOpcodes
|
import com.futo.platformplayer.sync.internal.GJSyncOpcodes
|
||||||
import com.futo.platformplayer.sync.models.SyncPlaylistsPackage
|
import com.futo.platformplayer.sync.models.SyncPlaylistsPackage
|
||||||
import com.futo.platformplayer.sync.models.SyncSubscriptionGroupsPackage
|
import com.futo.platformplayer.sync.models.SyncSubscriptionGroupsPackage
|
||||||
import com.futo.platformplayer.sync.models.SyncSubscriptionsPackage
|
import com.futo.platformplayer.sync.models.SyncSubscriptionsPackage
|
||||||
|
import com.futo.platformplayer.sync.models.SyncWatchLaterPackage
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.time.Instant
|
||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
|
import java.time.ZoneOffset
|
||||||
|
|
||||||
/***
|
/***
|
||||||
* Used to maintain playlists
|
* Used to maintain playlists
|
||||||
@ -50,6 +56,11 @@ class StatePlaylists {
|
|||||||
.load();
|
.load();
|
||||||
private val _watchlistOrderStore = FragmentedStorage.get<StringArrayStorage>("watchListOrder"); //Temporary workaround to add order..
|
private val _watchlistOrderStore = FragmentedStorage.get<StringArrayStorage>("watchListOrder"); //Temporary workaround to add order..
|
||||||
|
|
||||||
|
private val _watchLaterReorderTime = FragmentedStorage.get<StringStorage>("watchLaterReorderTime");
|
||||||
|
private val _watchLaterAdds = FragmentedStorage.get<StringDateMapStorage>("watchLaterAdds");
|
||||||
|
private val _watchLaterRemovals = FragmentedStorage.get<StringDateMapStorage>("watchLaterRemovals");
|
||||||
|
|
||||||
|
|
||||||
val playlistStore = FragmentedStorage.storeJson<Playlist>("playlists")
|
val playlistStore = FragmentedStorage.storeJson<Playlist>("playlists")
|
||||||
.withRestore(PlaylistBackup())
|
.withRestore(PlaylistBackup())
|
||||||
.load();
|
.load();
|
||||||
@ -59,6 +70,34 @@ class StatePlaylists {
|
|||||||
|
|
||||||
val onWatchLaterChanged = Event0();
|
val onWatchLaterChanged = Event0();
|
||||||
|
|
||||||
|
fun getWatchLaterAddTime(url: String): OffsetDateTime? {
|
||||||
|
return _watchLaterAdds.get(url)
|
||||||
|
}
|
||||||
|
fun setWatchLaterAddTime(url: String, time: OffsetDateTime) {
|
||||||
|
_watchLaterAdds.setAndSave(url, time);
|
||||||
|
}
|
||||||
|
fun getWatchLaterRemovalTime(url: String): OffsetDateTime? {
|
||||||
|
return _watchLaterRemovals.get(url);
|
||||||
|
}
|
||||||
|
fun getWatchLaterLastReorderTime(): OffsetDateTime{
|
||||||
|
val value = _watchLaterReorderTime.value;
|
||||||
|
if(value.isEmpty())
|
||||||
|
return OffsetDateTime.MIN;
|
||||||
|
val tryParse = value.toLongOrNull() ?: 0;
|
||||||
|
return OffsetDateTime.ofInstant(Instant.ofEpochSecond(tryParse), ZoneOffset.UTC);
|
||||||
|
}
|
||||||
|
private fun setWatchLaterReorderTime() {
|
||||||
|
val now = OffsetDateTime.now().toEpochSecond();
|
||||||
|
_watchLaterReorderTime.setAndSave(now.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getWatchLaterOrdering() = _watchlistOrderStore.getAllValues().toList();
|
||||||
|
|
||||||
|
fun updateWatchLaterOrdering(order: List<String>) {
|
||||||
|
_watchlistOrderStore.set(*smartMerge(order, getWatchLaterOrdering()).toTypedArray());
|
||||||
|
_watchlistOrderStore.save();
|
||||||
|
}
|
||||||
|
|
||||||
fun toMigrateCheck(): List<ManagedStore<*>> {
|
fun toMigrateCheck(): List<ManagedStore<*>> {
|
||||||
return listOf(playlistStore, _watchlistStore);
|
return listOf(playlistStore, _watchlistStore);
|
||||||
}
|
}
|
||||||
@ -68,12 +107,14 @@ class StatePlaylists {
|
|||||||
return _watchlistStore.getItems().sortedBy { order.indexOf(it.url) };
|
return _watchlistStore.getItems().sortedBy { order.indexOf(it.url) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fun updateWatchLater(updated: List<SerializedPlatformVideo>) {
|
fun updateWatchLater(updated: List<SerializedPlatformVideo>, isUserInteraction: Boolean = false) {
|
||||||
|
var wasModified = false;
|
||||||
synchronized(_watchlistStore) {
|
synchronized(_watchlistStore) {
|
||||||
//_watchlistStore.deleteAll();
|
//_watchlistStore.deleteAll();
|
||||||
val existing = _watchlistStore.getItems();
|
val existing = _watchlistStore.getItems();
|
||||||
val toAdd = updated.filter { u -> !existing.any { u.url == it.url } };
|
val toAdd = updated.filter { u -> !existing.any { u.url == it.url } };
|
||||||
val toRemove = existing.filter { u -> !updated.any { u.url == it.url } };
|
val toRemove = existing.filter { u -> !updated.any { u.url == it.url } };
|
||||||
|
wasModified = toAdd.size > 0 || toRemove.size > 0;
|
||||||
Logger.i(TAG, "WatchLater changed:\nTo Add:\n" +
|
Logger.i(TAG, "WatchLater changed:\nTo Add:\n" +
|
||||||
(if(toAdd.size == 0) "None" else toAdd.map { " + " + it.name }.joinToString("\n")) +
|
(if(toAdd.size == 0) "None" else toAdd.map { " + " + it.name }.joinToString("\n")) +
|
||||||
"\nTo Remove:\n" +
|
"\nTo Remove:\n" +
|
||||||
@ -86,6 +127,11 @@ class StatePlaylists {
|
|||||||
}
|
}
|
||||||
onWatchLaterChanged.emit();
|
onWatchLaterChanged.emit();
|
||||||
|
|
||||||
|
if(isUserInteraction) {
|
||||||
|
setWatchLaterReorderTime();
|
||||||
|
broadcastWatchLater(!wasModified);
|
||||||
|
}
|
||||||
|
|
||||||
if(StateDownloads.instance.getWatchLaterDescriptor() != null) {
|
if(StateDownloads.instance.getWatchLaterDescriptor() != null) {
|
||||||
StateDownloads.instance.checkForOutdatedPlaylistVideos(VideoDownload.GROUP_WATCHLATER);
|
StateDownloads.instance.checkForOutdatedPlaylistVideos(VideoDownload.GROUP_WATCHLATER);
|
||||||
}
|
}
|
||||||
@ -96,32 +142,56 @@ class StatePlaylists {
|
|||||||
return _watchlistStore.getItems().firstOrNull { it.url == url };
|
return _watchlistStore.getItems().firstOrNull { it.url == url };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fun removeFromWatchLater(url: String) {
|
fun removeFromWatchLater(url: String, isUserInteraction: Boolean = false) {
|
||||||
val item = getWatchLaterFromUrl(url);
|
val item = getWatchLaterFromUrl(url);
|
||||||
if(item != null){
|
if(item != null){
|
||||||
removeFromWatchLater(item);
|
removeFromWatchLater(item, isUserInteraction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fun removeFromWatchLater(video: SerializedPlatformVideo) {
|
fun removeFromWatchLater(video: SerializedPlatformVideo, isUserInteraction: Boolean = false, time: OffsetDateTime? = null) {
|
||||||
synchronized(_watchlistStore) {
|
synchronized(_watchlistStore) {
|
||||||
_watchlistStore.delete(video);
|
_watchlistStore.delete(video);
|
||||||
_watchlistOrderStore.set(*_watchlistOrderStore.values.filter { it != video.url }.toTypedArray());
|
_watchlistOrderStore.set(*_watchlistOrderStore.values.filter { it != video.url }.toTypedArray());
|
||||||
_watchlistOrderStore.save();
|
_watchlistOrderStore.save();
|
||||||
|
if(time != null)
|
||||||
|
_watchLaterRemovals.setAndSave(video.url, time);
|
||||||
}
|
}
|
||||||
onWatchLaterChanged.emit();
|
onWatchLaterChanged.emit();
|
||||||
|
|
||||||
|
if(isUserInteraction) {
|
||||||
|
val now = OffsetDateTime.now();
|
||||||
|
if(time == null) {
|
||||||
|
_watchLaterRemovals.setAndSave(video.url, now);
|
||||||
|
broadcastWatchLaterRemoval(video.url, now);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
broadcastWatchLaterRemoval(video.url, time);
|
||||||
|
}
|
||||||
|
|
||||||
if(StateDownloads.instance.getWatchLaterDescriptor() != null) {
|
if(StateDownloads.instance.getWatchLaterDescriptor() != null) {
|
||||||
StateDownloads.instance.checkForOutdatedPlaylistVideos(VideoDownload.GROUP_WATCHLATER);
|
StateDownloads.instance.checkForOutdatedPlaylistVideos(VideoDownload.GROUP_WATCHLATER);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fun addToWatchLater(video: SerializedPlatformVideo) {
|
fun addToWatchLater(video: SerializedPlatformVideo, isUserInteraction: Boolean = false, orderPosition: Int = -1) {
|
||||||
synchronized(_watchlistStore) {
|
synchronized(_watchlistStore) {
|
||||||
_watchlistStore.saveAsync(video);
|
_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);
|
||||||
|
_watchlistOrderStore.set(*existing.toTypedArray());
|
||||||
|
}
|
||||||
_watchlistOrderStore.save();
|
_watchlistOrderStore.save();
|
||||||
}
|
}
|
||||||
onWatchLaterChanged.emit();
|
onWatchLaterChanged.emit();
|
||||||
|
|
||||||
|
if(isUserInteraction) {
|
||||||
|
val now = OffsetDateTime.now();
|
||||||
|
_watchLaterAdds.setAndSave(video.url, now);
|
||||||
|
broadcastWatchLaterAddition(video, now);
|
||||||
|
}
|
||||||
|
|
||||||
StateDownloads.instance.checkForOutdatedPlaylists();
|
StateDownloads.instance.checkForOutdatedPlaylists();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,6 +221,36 @@ class StatePlaylists {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun broadcastWatchLater(orderOnly: Boolean = false) {
|
||||||
|
StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) {
|
||||||
|
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()));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
private fun broadcastWatchLaterAddition(video: SerializedPlatformVideo, time: OffsetDateTime) {
|
||||||
|
StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) {
|
||||||
|
StateSync.instance.broadcastJsonData(GJSyncOpcodes.syncWatchLater, SyncWatchLaterPackage(
|
||||||
|
listOf(video),
|
||||||
|
mapOf(Pair(video.url, time.toEpochSecond())),
|
||||||
|
mapOf(),
|
||||||
|
|
||||||
|
))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
private fun broadcastWatchLaterRemoval(url: String, time: OffsetDateTime) {
|
||||||
|
StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) {
|
||||||
|
StateSync.instance.broadcastJsonData(GJSyncOpcodes.syncWatchLater, SyncWatchLaterPackage(
|
||||||
|
listOf(),
|
||||||
|
mapOf(Pair(url, time.toEpochSecond())),
|
||||||
|
mapOf()
|
||||||
|
))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun createPlaylistFromChannel(channelUrl: String, onPage: (Int) -> Unit): Playlist {
|
suspend fun createPlaylistFromChannel(channelUrl: String, onPage: (Int) -> Unit): Playlist {
|
||||||
val channel = StatePlatform.instance.getChannel(channelUrl).await();
|
val channel = StatePlatform.instance.getChannel(channelUrl).await();
|
||||||
return createPlaylistFromChannel(channel, onPage);
|
return createPlaylistFromChannel(channel, onPage);
|
||||||
|
@ -13,5 +13,6 @@ class GJSyncOpcodes {
|
|||||||
val syncHistory: UByte = 203.toUByte();
|
val syncHistory: UByte = 203.toUByte();
|
||||||
val syncSubscriptionGroups: UByte = 204.toUByte();
|
val syncSubscriptionGroups: UByte = 204.toUByte();
|
||||||
val syncPlaylists: UByte = 205.toUByte();
|
val syncPlaylists: UByte = 205.toUByte();
|
||||||
|
val syncWatchLater: UByte = 206.toUByte();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -7,6 +7,7 @@ import com.futo.platformplayer.logging.Logger
|
|||||||
import com.futo.platformplayer.models.HistoryVideo
|
import com.futo.platformplayer.models.HistoryVideo
|
||||||
import com.futo.platformplayer.models.Subscription
|
import com.futo.platformplayer.models.Subscription
|
||||||
import com.futo.platformplayer.models.SubscriptionGroup
|
import com.futo.platformplayer.models.SubscriptionGroup
|
||||||
|
import com.futo.platformplayer.smartMerge
|
||||||
import com.futo.platformplayer.states.StateApp
|
import com.futo.platformplayer.states.StateApp
|
||||||
import com.futo.platformplayer.states.StateBackup
|
import com.futo.platformplayer.states.StateBackup
|
||||||
import com.futo.platformplayer.states.StateHistory
|
import com.futo.platformplayer.states.StateHistory
|
||||||
@ -21,6 +22,7 @@ import com.futo.platformplayer.sync.models.SendToDevicePackage
|
|||||||
import com.futo.platformplayer.sync.models.SyncPlaylistsPackage
|
import com.futo.platformplayer.sync.models.SyncPlaylistsPackage
|
||||||
import com.futo.platformplayer.sync.models.SyncSubscriptionGroupsPackage
|
import com.futo.platformplayer.sync.models.SyncSubscriptionGroupsPackage
|
||||||
import com.futo.platformplayer.sync.models.SyncSubscriptionsPackage
|
import com.futo.platformplayer.sync.models.SyncSubscriptionsPackage
|
||||||
|
import com.futo.platformplayer.sync.models.SyncWatchLaterPackage
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
@ -283,6 +285,38 @@ class SyncSession : IAuthorizable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GJSyncOpcodes.syncWatchLater -> {
|
||||||
|
val dataBody = ByteArray(data.remaining());
|
||||||
|
data.get(dataBody);
|
||||||
|
val json = String(dataBody, Charsets.UTF_8);
|
||||||
|
val pack = Serializer.json.decodeFromString<SyncWatchLaterPackage>(json);
|
||||||
|
|
||||||
|
Logger.i(TAG, "SyncWatchLater received ${pack.videos.size} (${pack.videoAdds?.size}, ${pack.videoRemovals?.size})");
|
||||||
|
|
||||||
|
val allExisting = StatePlaylists.instance.getWatchLater();
|
||||||
|
for(video in pack.videos) {
|
||||||
|
val existing = allExisting.firstOrNull { it.url == video.url };
|
||||||
|
val time = if(pack.videoAdds != null && pack.videoAdds.containsKey(video.url)) OffsetDateTime.ofInstant(Instant.ofEpochSecond(pack.videoAdds[video.url] ?: 0), ZoneOffset.UTC) else OffsetDateTime.MIN;
|
||||||
|
|
||||||
|
if(existing == null) {
|
||||||
|
StatePlaylists.instance.addToWatchLater(video, false);
|
||||||
|
if(time > OffsetDateTime.MIN)
|
||||||
|
StatePlaylists.instance.setWatchLaterAddTime(video.url, time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(removal in pack.videoRemovals) {
|
||||||
|
val watchLater = allExisting.firstOrNull { it.url == removal.key } ?: continue;
|
||||||
|
val creation = StatePlaylists.instance.getWatchLaterRemovalTime(watchLater.url) ?: OffsetDateTime.MIN;
|
||||||
|
val removalTime = OffsetDateTime.ofInstant(Instant.ofEpochSecond(removal.value), ZoneOffset.UTC);
|
||||||
|
if(creation < removalTime)
|
||||||
|
StatePlaylists.instance.removeFromWatchLater(watchLater, false, removalTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
val packReorderTime = OffsetDateTime.ofInstant(Instant.ofEpochSecond(pack.reorderTime), ZoneOffset.UTC);
|
||||||
|
if(StatePlaylists.instance.getWatchLaterLastReorderTime() < packReorderTime && pack.ordering != null)
|
||||||
|
StatePlaylists.instance.updateWatchLaterOrdering(smartMerge(pack.ordering!!, StatePlaylists.instance.getWatchLaterOrdering()));
|
||||||
|
}
|
||||||
|
|
||||||
GJSyncOpcodes.syncHistory -> {
|
GJSyncOpcodes.syncHistory -> {
|
||||||
val dataBody = ByteArray(data.remaining());
|
val dataBody = ByteArray(data.remaining());
|
||||||
data.get(dataBody);
|
data.get(dataBody);
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
package com.futo.platformplayer.sync.models
|
||||||
|
|
||||||
|
import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo
|
||||||
|
import com.futo.platformplayer.models.Playlist
|
||||||
|
import com.futo.platformplayer.models.Subscription
|
||||||
|
import com.futo.platformplayer.models.SubscriptionGroup
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import java.time.OffsetDateTime
|
||||||
|
import java.util.Dictionary
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class SyncWatchLaterPackage(
|
||||||
|
var videos: List<SerializedPlatformVideo>,
|
||||||
|
var videoAdds: Map<String, Long>,
|
||||||
|
var videoRemovals: Map<String, Long>,
|
||||||
|
var reorderTime: Long = 0,
|
||||||
|
var ordering: List<String>? = null
|
||||||
|
)
|
@ -1 +1 @@
|
|||||||
Subproject commit 0ce91be276681ab82d26f9471523beab6b2a0a00
|
Subproject commit d6d3b709b8fe02ea203b20192215e888cc84042b
|
Loading…
x
Reference in New Issue
Block a user