Compare commits

...

6 Commits

20 changed files with 331 additions and 26 deletions
@@ -0,0 +1,94 @@
{
"formatVersion": 1,
"database": {
"version": 3,
"identityHash": "ffba56c2f572c25080ce8596e8bb8945",
"entities": [
{
"tableName": "history",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `url` TEXT NOT NULL, `position` INTEGER NOT NULL, `datetime` INTEGER NOT NULL, `name` TEXT NOT NULL, `serialized` BLOB)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "position",
"columnName": "position",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "datetime",
"columnName": "datetime",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "serialized",
"columnName": "serialized",
"affinity": "BLOB",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_history_url",
"unique": false,
"columnNames": [
"url"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_history_url` ON `${TABLE_NAME}` (`url`)"
},
{
"name": "index_history_name",
"unique": false,
"columnNames": [
"name"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_history_name` ON `${TABLE_NAME}` (`name`)"
},
{
"name": "index_history_datetime",
"unique": false,
"columnNames": [
"datetime"
],
"orders": [
"DESC"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_history_datetime` ON `${TABLE_NAME}` (`datetime` DESC)"
}
],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ffba56c2f572c25080ce8596e8bb8945')"
]
}
}
@@ -0,0 +1,88 @@
{
"formatVersion": 1,
"database": {
"version": 5,
"identityHash": "eb813d54b9c44d29f1d7bb198a16d4d1",
"entities": [
{
"tableName": "subscription_cache",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `url` TEXT, `channelUrl` TEXT, `datetime` INTEGER, `serialized` BLOB)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "channelUrl",
"columnName": "channelUrl",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "datetime",
"columnName": "datetime",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "serialized",
"columnName": "serialized",
"affinity": "BLOB",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_subscription_cache_url",
"unique": false,
"columnNames": [
"url"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_subscription_cache_url` ON `${TABLE_NAME}` (`url`)"
},
{
"name": "index_subscription_cache_channelUrl",
"unique": false,
"columnNames": [
"channelUrl"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_subscription_cache_channelUrl` ON `${TABLE_NAME}` (`channelUrl`)"
},
{
"name": "index_subscription_cache_datetime",
"unique": false,
"columnNames": [
"datetime"
],
"orders": [
"DESC"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_subscription_cache_datetime` ON `${TABLE_NAME}` (`datetime` DESC)"
}
],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'eb813d54b9c44d29f1d7bb198a16d4d1')"
]
}
}
@@ -0,0 +1,52 @@
{
"formatVersion": 1,
"database": {
"version": 3,
"identityHash": "6e3b2d286325c4ea8a7a4c94c290daec",
"entities": [
{
"tableName": "testing",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`someString` TEXT NOT NULL, `someNum` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT, `serialized` BLOB)",
"fields": [
{
"fieldPath": "someString",
"columnName": "someString",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "someNum",
"columnName": "someNum",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "serialized",
"columnName": "serialized",
"affinity": "BLOB",
"notNull": false
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6e3b2d286325c4ea8a7a4c94c290daec')"
]
}
}
@@ -7,6 +7,7 @@ import android.view.LayoutInflater
import android.widget.Button
import com.futo.platformplayer.R
import com.futo.platformplayer.activities.MainActivity
import com.futo.platformplayer.fragment.mainactivity.main.SourcesFragment
import com.futo.platformplayer.readBytes
import com.futo.platformplayer.states.StateApp
import com.futo.platformplayer.states.StateBackup
@@ -19,6 +20,7 @@ class ImportOptionsDialog: AlertDialog {
private lateinit var _button_import_ezip: BigButton;
private lateinit var _button_import_txt: BigButton;
private lateinit var _button_import_newpipe_subs: BigButton;
private lateinit var _button_import_platform: BigButton;
private lateinit var _button_close: Button;
@@ -33,6 +35,7 @@ class ImportOptionsDialog: AlertDialog {
_button_import_ezip = findViewById(R.id.button_import_ezip);
_button_import_txt = findViewById(R.id.button_import_txt);
_button_import_newpipe_subs = findViewById(R.id.button_import_newpipe_subs);
_button_import_platform = findViewById(R.id.button_import_platform);
_button_close = findViewById(R.id.button_cancel);
_button_import_zip.onClick.subscribe {
@@ -61,6 +64,10 @@ class ImportOptionsDialog: AlertDialog {
StateBackup.importNewPipeSubs(_context, json);
};
};
_button_import_platform.onClick.subscribe {
dismiss();
_context.navigate(_context.getFragment<SourcesFragment>());
};
_button_close.setOnClickListener {
dismiss();
}
@@ -264,6 +264,31 @@ class SourceDetailFragment : MainFragment() {
}
)
);
val migrationButtons = mutableListOf<BigButton>();
if (isEnabled && source.capabilities.hasGetUserSubscriptions) {
migrationButtons.add(
BigButton(c, context.getString(R.string.import_subscriptions), context.getString(R.string.login_required), R.drawable.ic_subscriptions) {
}.apply { this.alpha = 0.5f }
);
}
if (isEnabled && source.capabilities.hasGetUserPlaylists && source.capabilities.hasGetPlaylist) {
val bigButton = BigButton(c, context.getString(R.string.import_playlists), context.getString(R.string.login_required), R.drawable.ic_playlist) {
}.apply { this.alpha = 0.5f };
bigButton.layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT).apply {
setMargins(0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5f, resources.displayMetrics).toInt(), 0, 0);
};
migrationButtons.add(bigButton);
}
if (migrationButtons.size > 0) {
groups.add(BigButtonGroup(c, context.getString(R.string.migration), *migrationButtons.toTypedArray()));
}
}
}
@@ -538,6 +538,7 @@ class VideoDetailView : ConstraintLayout {
if(!_destroyed) {
updateQueueState();
StatePlayer.instance.updateMediaSession(null);
_cast.setLoopVisible(!StatePlayer.instance.hasQueue);
}
};
StatePlayer.instance.onVideoChanging.subscribe(this) {
@@ -565,9 +566,7 @@ class VideoDetailView : ConstraintLayout {
};
_upNext.onNextItem.subscribe {
val item = StatePlayer.instance.nextQueueItem();
if(item != null)
setVideoOverview(item, true);
nextVideo(true, true, true);
};
_upNext.onOpenQueueClick.subscribe {
_container_content_queue.updateQueue();
@@ -17,6 +17,7 @@ import kotlinx.coroutines.*
import kotlinx.serialization.Serializable
import java.nio.ByteBuffer
import java.time.OffsetDateTime
import kotlin.system.measureTimeMillis
class PolycentricCache {
data class CachedOwnedClaims(val ownedClaims: List<OwnedClaim>?, val creationTime: OffsetDateTime = OffsetDateTime.now()) {
@@ -29,8 +30,15 @@ class PolycentricCache {
private val _cache = hashMapOf<PlatformID, CachedOwnedClaims>()
private val _profileCache = hashMapOf<PublicKey, CachedPolycentricProfile>()
private val _profileUrlCache = FragmentedStorage.get<CachedPolycentricProfileStorage>("profileUrlCache")
private val _profileUrlCache: CachedPolycentricProfileStorage;
private val _scope = CoroutineScope(Dispatchers.IO);
init {
Logger.i(TAG, "Initializing Polycentric cache");
val time = measureTimeMillis {
_profileUrlCache = FragmentedStorage.get<CachedPolycentricProfileStorage>("profileUrlCache")
}
Logger.i(TAG, "Initialized Polycentric cache (${_profileUrlCache.map.size}, ${time}ms)");
}
private val _taskGetProfile = BatchedTaskHandler<PublicKey, CachedPolycentricProfile>(_scope,
{ system ->
@@ -222,7 +230,7 @@ class PolycentricCache {
}
}
suspend fun getProfileAsync(id: PlatformID, url: String? = null): CachedPolycentricProfile? {
suspend fun getProfileAsync(id: PlatformID, urlNullCache: String? = null): CachedPolycentricProfile? {
if (!StatePolycentric.instance.enabled || id.claimType <= 0) {
return CachedPolycentricProfile(null);
}
@@ -243,8 +251,8 @@ class PolycentricCache {
Logger.v(TAG, "getProfileAsync (id: $id) != null (with retrieved valid claims)")
return getProfileAsync(claims.ownedClaims.first().system).await()
} else {
if(url != null)
_profileUrlCache.setAndSave(url, PolycentricCache.CachedPolycentricProfile(null));
if(urlNullCache != null)
_profileUrlCache.setAndSave(urlNullCache, PolycentricCache.CachedPolycentricProfile(null));
return null;
}
}
@@ -99,7 +99,7 @@ class StateCache {
if(existing != null && doUpdate) {
_subscriptionCache.update(existing.id!!, serialized);
return true;
return false;
}
else if(existing == null) {
_subscriptionCache.insert(serialized);
@@ -57,12 +57,16 @@ class StatePlayer {
var queueShuffle: Boolean = false
private set;
val hasQueue: Boolean get() {
val queueSize: Int get() {
synchronized(_queue) {
return _queue.isNotEmpty()
return _queue.size
}
}
val hasQueue: Boolean get() {
return queueSize > 1
}
val queueName: String get() = _queueName ?: _queueType;
//Events
@@ -169,10 +169,10 @@ class StatePolycentric {
}
}
fun getChannelUrls(url: String, channelId: PlatformID? = null, cacheOnly: Boolean = false): List<String> {
return getChannelUrlsWithUpdateResult(url, channelId, cacheOnly).second;
fun getChannelUrls(url: String, channelId: PlatformID? = null, cacheOnly: Boolean = false, doCacheNull: Boolean = false): List<String> {
return getChannelUrlsWithUpdateResult(url, channelId, cacheOnly, doCacheNull).second;
}
fun getChannelUrlsWithUpdateResult(url: String, channelId: PlatformID? = null, cacheOnly: Boolean = false): Pair<Boolean, List<String>> {
fun getChannelUrlsWithUpdateResult(url: String, channelId: PlatformID? = null, cacheOnly: Boolean = false, doCacheNull: Boolean = false): Pair<Boolean, List<String>> {
var didUpdate = false;
if (!enabled) {
return Pair(false, listOf(url));
@@ -184,7 +184,7 @@ class StatePolycentric {
if (polycentricCached == null && channelId != null) {
Logger.i("StateSubscriptions", "Get polycentric profile not cached");
if(!cacheOnly) {
polycentricProfile = runBlocking { PolycentricCache.instance.getProfileAsync(channelId, url) }?.profile;
polycentricProfile = runBlocking { PolycentricCache.instance.getProfileAsync(channelId, if(doCacheNull) url else null) }?.profile;
didUpdate = true;
}
} else {
@@ -258,11 +258,12 @@ class StateSubscriptions {
var polycentricBudget: Int = 10;
val subUrls = getSubscriptions().parallelStream().map {
if(usePolycentric) {
val result = StatePolycentric.instance.getChannelUrlsWithUpdateResult(it.channel.url, it.channel.id, polycentricBudget <= 0);
if(result.first)
val result = StatePolycentric.instance.getChannelUrlsWithUpdateResult(it.channel.url, it.channel.id, polycentricBudget <= 0, true);
if(result.first) {
synchronized(lock) {
polycentricBudget--;
}
}
Pair(it, result.second);
}
else
@@ -106,6 +106,10 @@ class CastView : ConstraintLayout {
_gestureControlView.stopAllGestures();
}
fun setLoopVisible(visible: Boolean) {
_buttonLoop.visibility = if (visible) View.VISIBLE else View.GONE;
}
fun setIsPlaying(isPlaying: Boolean) {
_updateTimeJob?.cancel();
@@ -22,7 +22,10 @@ class QueueEditorOverlay : LinearLayout {
_topbar.onClose.subscribe(this, onClose::emit);
_editor.onVideoOrderChanged.subscribe { StatePlayer.instance.setQueueWithExisting(it) }
_editor.onVideoRemoved.subscribe { v -> StatePlayer.instance.removeFromQueue(v) }
_editor.onVideoRemoved.subscribe { v ->
StatePlayer.instance.removeFromQueue(v);
_topbar.setInfo(context.getString(R.string.queue), "${StatePlayer.instance.queueSize} " + context.getString(R.string.videos));
}
_editor.onVideoClicked.subscribe { v -> StatePlayer.instance.setQueuePosition(v) }
_topbar.setInfo(context.getString(R.string.queue), "");
@@ -305,6 +305,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
StatePlayer.instance.onQueueChanged.subscribe(this) {
CoroutineScope(Dispatchers.Main).launch(Dispatchers.Main) {
setLoopVisible(!StatePlayer.instance.hasQueue)
updateNextPrevious();
}
}
@@ -361,6 +362,11 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
_currentChapterLoopActive = false;
}
fun setLoopVisible(visible: Boolean) {
_control_loop.visibility = if (visible) View.VISIBLE else View.GONE;
_control_loop_fullscreen.visibility = if (visible) View.VISIBLE else View.GONE;
}
fun stopAllGestures() {
gestureControl.stopAllGestures();
}
@@ -391,11 +397,11 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
fun setArtwork(drawable: Drawable?) {
if (drawable != null) {
_videoView.defaultArtwork = drawable;
_videoView.useArtwork = true;
_videoView.artworkDisplayMode = StyledPlayerView.ARTWORK_DISPLAY_MODE_FILL;
fitOrFill(isFullScreen);
} else {
_videoView.defaultArtwork = null;
_videoView.useArtwork = false;
_videoView.artworkDisplayMode = StyledPlayerView.ARTWORK_DISPLAY_MODE_OFF;
}
}
@@ -71,6 +71,16 @@
android:paddingTop="10dp"
android:layout_marginTop="5dp"
android:layout_marginBottom="10dp">
<com.futo.platformplayer.views.buttons.BigButton
android:id="@+id/button_import_platform"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleY="0.9"
android:scaleX="0.9"
app:buttonIcon="@drawable/ic_sources_filled"
app:buttonText="Import from Platform"
app:buttonBackground="@drawable/background_big_button_black"
app:buttonSubText="Import your data from a specific source" />
<com.futo.platformplayer.views.buttons.BigButton
android:id="@+id/button_import_zip"
android:layout_width="match_parent"
+4 -3
View File
@@ -70,10 +70,11 @@
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintLeft_toRightOf="@id/image_thumbnail"
app:layout_constraintLeft_toLeftOf="@id/text_body"
app:layout_constraintTop_toBottomOf="@id/text_body"
android:layout_marginLeft="-10dp"
android:layout_marginTop="8dp"
android:gravity="center_vertical">
@@ -83,7 +84,7 @@
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginStart="10dp" />
android:layout_marginStart="9dp" />
<LinearLayout
android:id="@+id/layout_rating"
@@ -29,7 +29,7 @@
android:textColor="@color/white"
android:textSize="13dp"
android:gravity="center_vertical"
tools:text="500K" />
tools:text="50K" />
<com.futo.platformplayer.views.LoaderView
android:id="@+id/loader_likes"
@@ -42,6 +42,8 @@
android:id="@+id/pill_seperator"
android:layout_width="1dp"
android:layout_height="wrap_content"
android:layout_marginTop="7dp"
android:layout_marginBottom="7dp"
android:background="#808080"/>
@@ -68,7 +70,7 @@
android:textColor="@color/white"
android:gravity="center_vertical"
android:textSize="13dp"
tools:text="500K" />
tools:text="50K" />
<com.futo.platformplayer.views.LoaderView
android:id="@+id/loader_dislikes"
android:layout_width="14dp"
+1
View File
@@ -577,6 +577,7 @@
<string name="import_your_subscriptions_from_this_source">Import your subscriptions from this source</string>
<string name="import_your_playlists_from_this_source">Import your playlists from this source</string>
<string name="login">Login</string>
<string name="login_required">Login Required</string>
<string name="sign_into_the_platform_of_this_source">Sign into the platform of this source</string>
<string name="management">Management</string>
<string name="uninstall">Uninstall</string>