Merge branch 'master' of gitlab.futo.org:videostreaming/grayjay into db-store

This commit is contained in:
Kelvin 2023-11-20 14:24:48 +01:00
commit f52b731615
39 changed files with 1201 additions and 179 deletions

View file

@ -211,6 +211,16 @@ class PlatformNestedMediaContent extends PlatformContent {
this.contentThumbnails = obj.contentThumbnails ?? new Thumbnails(); this.contentThumbnails = obj.contentThumbnails ?? new Thumbnails();
} }
} }
class PlatformLockedContent extends PlatformContent {
constructor(obj) {
super(obj, 70);
obj = obj ?? {};
this.contentName = obj.contentName;
this.contentThumbnails = obj.contentThumbnails ?? new Thumbnails();
this.unlockUrl = obj.unlockUrl ?? "";
this.lockDescription = obj.lockDescription;
}
}
class PlatformVideo extends PlatformContent { class PlatformVideo extends PlatformContent {
constructor(obj) { constructor(obj) {
super(obj, 1); super(obj, 1);

View file

@ -360,6 +360,15 @@ class Settings : FragmentedStorageFileJson() {
var backgroundSwitchToAudio: Boolean = true; var backgroundSwitchToAudio: Boolean = true;
} }
@FormField(R.string.comments, "group", R.string.comments_description, 4)
var comments = CommentSettings();
@Serializable
class CommentSettings {
@FormField(R.string.default_comment_section, FieldForm.DROPDOWN, -1, 0)
@DropdownFieldOptionsId(R.array.comment_sections)
var defaultCommentSection: Int = 0;
}
@FormField(R.string.downloads, "group", R.string.configure_downloading_of_videos, 5) @FormField(R.string.downloads, "group", R.string.configure_downloading_of_videos, 5)
var downloads = Downloads(); var downloads = Downloads();
@Serializable @Serializable
@ -417,6 +426,9 @@ class Settings : FragmentedStorageFileJson() {
@Serializable(with = FlexibleBooleanSerializer::class) @Serializable(with = FlexibleBooleanSerializer::class)
var enabled: Boolean = true; var enabled: Boolean = true;
@FormField(R.string.keep_screen_on, FieldForm.TOGGLE, R.string.keep_screen_on_while_casting, 1)
@Serializable(with = FlexibleBooleanSerializer::class)
var keepScreenOn: Boolean = true;
/*TODO: Should we have a different casting quality? /*TODO: Should we have a different casting quality?
@FormField("Preferred Casting Quality", FieldForm.DROPDOWN, "", 3) @FormField("Preferred Casting Quality", FieldForm.DROPDOWN, "", 3)

View file

@ -7,19 +7,21 @@ import com.futo.platformplayer.api.media.Serializer
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import java.io.BufferedInputStream
import java.io.BufferedReader import java.io.BufferedReader
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream
import java.io.StringWriter import java.io.StringWriter
import java.net.SocketTimeoutException import java.net.SocketTimeoutException
import java.nio.ByteBuffer
class HttpContext : AutoCloseable { class HttpContext : AutoCloseable {
private val _stream: BufferedInputStream; private val _inputStream: InputStream;
private var _responseStream: OutputStream? = null; private var _responseStream: OutputStream? = null;
var id: String? = null; var id: String? = null;
var head: String = ""; var head: String = "";
var headers: HttpHeaders = HttpHeaders(); var headers: HttpHeaders = HttpHeaders();
@ -40,103 +42,131 @@ class HttpContext : AutoCloseable {
private val _responseHeaders: HttpHeaders = HttpHeaders(); private val _responseHeaders: HttpHeaders = HttpHeaders();
private val newLineByte = "\n"[0];
private fun readStreamLine(): String {
//TODO: This is not ideal..
var twoByteArray = ByteBuffer.allocate(2);
var lastChar: Char = Char.MIN_VALUE;
val builder = StringBuilder();
do {
val firstByte = _stream.read();
if(firstByte == -1)
break;
if(isCharacter2Bytes(firstByte)) {
twoByteArray.put(0, firstByte.toByte());
val secondByte = _stream.read();
if(secondByte == -1)
break;
twoByteArray.put(1, secondByte.toByte());
}
else
lastChar = firstByte.toChar();
builder.append(lastChar);
if(lastChar == newLineByte)
break;
}
while(lastChar != Char.MIN_VALUE);
return builder.toString();
}
constructor(stream: BufferedInputStream, responseStream: OutputStream? = null, requestId: String? = null, timeout: Int? = null) { constructor(inputStream: InputStream, responseStream: OutputStream? = null, requestId: String? = null, timeout: Int? = null) {
_stream = stream; _inputStream = inputStream;
_responseStream = responseStream; _responseStream = responseStream;
this.id = requestId; this.id = requestId;
try { val headerBytes = readHeaderBytes()
head = readStreamLine() ?: throw EmptyRequestException("No head found"); ByteArrayInputStream(headerBytes).use {
} val reader = it.bufferedReader(Charsets.UTF_8)
catch(ex: SocketTimeoutException) { try {
if((timeout ?: 0) > 0) head = reader.readLine() ?: throw EmptyRequestException("No head found");
throw KeepAliveTimeoutException("Keep-Alive timedout", ex); }
throw ex; catch(ex: SocketTimeoutException) {
} if((timeout ?: 0) > 0)
throw KeepAliveTimeoutException("Keep-Alive timedout", ex);
val methodEndIndex = head.indexOf(' '); throw ex;
val urlEndIndex = head.indexOf(' ', methodEndIndex + 1);
if (methodEndIndex == -1 || urlEndIndex == -1) {
Logger.w(TAG, "Skipped request, wrong format.");
throw IllegalStateException("Invalid request");
}
method = head.substring(0, methodEndIndex);
path = head.substring(methodEndIndex + 1, urlEndIndex);
if (path.contains("?")) {
val queryPartIndex = path.indexOf("?");
val queryParts = path.substring(queryPartIndex + 1).split("&");
path = path.substring(0, queryPartIndex);
for(queryPart in queryParts) {
val eqIndex = queryPart.indexOf("=");
if(eqIndex > 0)
query.put(queryPart.substring(0, eqIndex), queryPart.substring(eqIndex + 1));
else
query.put(queryPart, "");
} }
}
while (true) { val methodEndIndex = head.indexOf(' ');
val line = readStreamLine(); val urlEndIndex = head.indexOf(' ', methodEndIndex + 1);
val headerEndIndex = line.indexOf(":"); if (methodEndIndex == -1 || urlEndIndex == -1) {
if (headerEndIndex == -1) Logger.w(TAG, "Skipped request, wrong format.");
break; throw IllegalStateException("Invalid request");
}
val headerKey = line.substring(0, headerEndIndex).lowercase() method = head.substring(0, methodEndIndex);
val headerValue = line.substring(headerEndIndex + 1).trim(); path = head.substring(methodEndIndex + 1, urlEndIndex);
headers[headerKey] = headerValue;
when(headerKey) { if (path.contains("?")) {
"content-length" -> contentLength = headerValue.toLong(); val queryPartIndex = path.indexOf("?");
"content-type" -> contentType = headerValue; val queryParts = path.substring(queryPartIndex + 1).split("&");
"connection" -> keepAlive = headerValue.lowercase() == "keep-alive"; path = path.substring(0, queryPartIndex);
"keep-alive" -> {
val keepAliveParams = headerValue.split(","); for(queryPart in queryParts) {
for(keepAliveParam in keepAliveParams) { val eqIndex = queryPart.indexOf("=");
val eqIndex = keepAliveParam.indexOf("="); if(eqIndex > 0)
if(eqIndex > 0){ query.put(queryPart.substring(0, eqIndex), queryPart.substring(eqIndex + 1));
when(keepAliveParam.substring(0, eqIndex)) { else
"timeout" -> keepAliveTimeout = keepAliveParam.substring(eqIndex+1).toInt(); query.put(queryPart, "");
"max" -> keepAliveTimeout = keepAliveParam.substring(eqIndex+1).toInt(); }
}
while (true) {
val line = reader.readLine();
val headerEndIndex = line.indexOf(":");
if (headerEndIndex == -1)
break;
val headerKey = line.substring(0, headerEndIndex).lowercase()
val headerValue = line.substring(headerEndIndex + 1).trim();
headers[headerKey] = headerValue;
when(headerKey) {
"content-length" -> contentLength = headerValue.toLong();
"content-type" -> contentType = headerValue;
"connection" -> keepAlive = headerValue.lowercase() == "keep-alive";
"keep-alive" -> {
val keepAliveParams = headerValue.split(",");
for(keepAliveParam in keepAliveParams) {
val eqIndex = keepAliveParam.indexOf("=");
if(eqIndex > 0){
when(keepAliveParam.substring(0, eqIndex)) {
"timeout" -> keepAliveTimeout = keepAliveParam.substring(eqIndex+1).toInt();
"max" -> keepAliveTimeout = keepAliveParam.substring(eqIndex+1).toInt();
}
} }
} }
} }
} }
if(line.isNullOrEmpty())
break;
} }
if(line.isNullOrEmpty())
break;
} }
} }
private fun readHeaderBytes(): ByteArray {
val headerBytes = ByteArrayOutputStream()
var crlfCount = 0
while (crlfCount < 4) {
val b = _inputStream.read()
if (b == -1) {
throw IOException("Unexpected end of stream while reading headers")
}
if (b == 0x0D || b == 0x0A) { // CR or LF
crlfCount++
} else {
crlfCount = 0
}
headerBytes.write(b)
}
return headerBytes.toByteArray()
}
fun readContentBytes(buffer: ByteArray, length: Int): Int {
val remainingBytes = (contentLength - _totalRead).coerceAtMost(length.toLong()).toInt()
val read = _inputStream.read(buffer, 0, remainingBytes);
if (read > 0) {
_totalRead += read
}
return read;
}
fun readContentString(): String {
val byteArrayOutputStream = ByteArrayOutputStream()
val buffer = ByteArray(4096)
var read: Int
while (true) {
read = readContentBytes(buffer, buffer.size)
if (read <= 0) break
byteArrayOutputStream.write(buffer, 0, read)
}
return byteArrayOutputStream.toString(Charsets.UTF_8.name())
}
inline fun <reified T> readContentJson() : T {
return Serializer.json.decodeFromString(readContentString());
}
fun skipBody() {
if (contentLength > 0)
_inputStream.skip(contentLength - _totalRead)
}
fun getHttpHeaderString(): String { fun getHttpHeaderString(): String {
val writer = StringWriter(); val writer = StringWriter();
writer.write(head + "\r\n"); writer.write(head + "\r\n");
@ -200,58 +230,13 @@ class HttpContext : AutoCloseable {
statusCode = status; statusCode = status;
} }
fun readContentBytes(buffer: ByteArray, length: Int) : Int {
val reading = if(contentLength - _totalRead < length)
(contentLength - _totalRead).toInt();
else
length;
val read = _stream.read(buffer, 0, reading);
_totalRead += read;
return read;
}
fun readContentString() : String{
val writer = StringWriter();
var read = 0;
val buffer = ByteArray(8192);
val twoByteArray = ByteArray(2);
do {
read = readContentBytes(buffer, buffer.size);
if(read > 0) {
if (isCharacter2Bytes(buffer[read - 1].toInt())) {
//Fixes overlapping buffers
writer.write(String(buffer, 0, read - 1));
twoByteArray[0] = buffer[read - 1];
val secondByte = _stream.read();
if (secondByte < 0)
break;
twoByteArray[1] = secondByte.toByte();
writer.write(String(twoByteArray));
} else
writer.write(String(buffer, 0, read));
}
} while(read > 0);
return writer.toString();
}
inline fun <reified T> readContentJson() : T {
return Serializer.json.decodeFromString(readContentString());
}
fun skipBody() {
if(contentLength > 0)
_stream.skip(contentLength - _totalRead);
}
override fun close() { override fun close() {
if(!keepAlive) { if(!keepAlive) {
_stream?.close(); _inputStream.close();
_responseStream?.close(); _responseStream?.close();
} }
} }
private fun isCharacter2Bytes(firstByte: Int): Boolean {
return firstByte and 0xE0 == 0xC0
}
companion object { companion object {
private val TAG = "HttpRequest"; private val TAG = "HttpRequest";
private val statusCodeMap = mapOf( private val statusCodeMap = mapOf(

View file

@ -13,6 +13,7 @@ enum class ContentType(val value: Int) {
NESTED_VIDEO(11), NESTED_VIDEO(11),
LOCKED(70),
PLACEHOLDER(90), PLACEHOLDER(90),
DEFERRED(91); DEFERRED(91);

View file

@ -0,0 +1,13 @@
package com.futo.platformplayer.api.media.models.locked
import com.futo.platformplayer.api.media.models.Thumbnails
import com.futo.platformplayer.api.media.models.contents.ContentType
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
interface IPlatformLockedContent: IPlatformContent {
val lockContentType: ContentType;
val lockDescription: String?;
val unlockUrl: String?;
val contentName: String?;
val contentThumbnails: Thumbnails;
}

View file

@ -2,6 +2,7 @@ package com.futo.platformplayer.api.media.models.video
import com.futo.platformplayer.api.media.models.contents.ContentType import com.futo.platformplayer.api.media.models.contents.ContentType
import com.futo.platformplayer.api.media.models.contents.IPlatformContent import com.futo.platformplayer.api.media.models.contents.IPlatformContent
import com.futo.platformplayer.api.media.models.locked.IPlatformLockedContent
import com.futo.platformplayer.api.media.models.nested.IPlatformNestedContent import com.futo.platformplayer.api.media.models.nested.IPlatformNestedContent
import com.futo.platformplayer.api.media.models.post.IPlatformPost import com.futo.platformplayer.api.media.models.post.IPlatformPost
import com.futo.platformplayer.serializers.PlatformContentSerializer import com.futo.platformplayer.serializers.PlatformContentSerializer
@ -18,6 +19,7 @@ interface SerializedPlatformContent: IPlatformContent {
ContentType.MEDIA -> SerializedPlatformVideo.fromVideo(content as IPlatformVideo); ContentType.MEDIA -> SerializedPlatformVideo.fromVideo(content as IPlatformVideo);
ContentType.NESTED_VIDEO -> SerializedPlatformNestedContent.fromNested(content as IPlatformNestedContent); ContentType.NESTED_VIDEO -> SerializedPlatformNestedContent.fromNested(content as IPlatformNestedContent);
ContentType.POST -> SerializedPlatformPost.fromPost(content as IPlatformPost); ContentType.POST -> SerializedPlatformPost.fromPost(content as IPlatformPost);
ContentType.LOCKED -> SerializedPlatformLockedContent.fromLocked(content as IPlatformLockedContent);
else -> throw NotImplementedError("Content type ${content.contentType} not implemented"); else -> throw NotImplementedError("Content type ${content.contentType} not implemented");
}; };
} }

View file

@ -0,0 +1,62 @@
package com.futo.platformplayer.api.media.models.video
import com.futo.platformplayer.api.media.PlatformID
import com.futo.platformplayer.api.media.Serializer
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.api.media.models.locked.IPlatformLockedContent
import com.futo.platformplayer.api.media.models.nested.IPlatformNestedContent
import com.futo.platformplayer.serializers.OffsetDateTimeNullableSerializer
import com.futo.platformplayer.states.StatePlatform
import com.futo.polycentric.core.combineHashCodes
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.time.OffsetDateTime
@kotlinx.serialization.Serializable
open class SerializedPlatformLockedContent(
override val id: PlatformID,
override val name: String,
override val author: PlatformAuthorLink,
@kotlinx.serialization.Serializable(with = OffsetDateTimeNullableSerializer::class)
override val datetime: OffsetDateTime?,
override val url: String,
override val shareUrl: String,
override val lockContentType: ContentType,
override val contentName: String?,
override val lockDescription: String? = null,
override val unlockUrl: String? = null,
override val contentThumbnails: Thumbnails
) : IPlatformLockedContent, SerializedPlatformContent {
final override val contentType: ContentType get() = ContentType.LOCKED;
override fun toJson() : String {
return Json.encodeToString(this);
}
override fun fromJson(str : String) : SerializedPlatformLockedContent {
return Serializer.json.decodeFromString<SerializedPlatformLockedContent>(str);
}
override fun fromJsonArray(str : String) : Array<SerializedPlatformContent> {
return Serializer.json.decodeFromString<Array<SerializedPlatformContent>>(str);
}
companion object {
fun fromLocked(content: IPlatformLockedContent) : SerializedPlatformLockedContent {
return SerializedPlatformLockedContent(
content.id,
content.name,
content.author,
content.datetime,
content.url,
content.shareUrl,
content.lockContentType,
content.contentName,
content.lockDescription,
content.unlockUrl,
content.contentThumbnails
);
}
}
}

View file

@ -23,6 +23,7 @@ interface IJSContent: IPlatformContent {
ContentType.POST -> JSPost(config, obj); ContentType.POST -> JSPost(config, obj);
ContentType.NESTED_VIDEO -> JSNestedMediaContent(config, obj); ContentType.NESTED_VIDEO -> JSNestedMediaContent(config, obj);
ContentType.PLAYLIST -> JSPlaylist(config, obj); ContentType.PLAYLIST -> JSPlaylist(config, obj);
ContentType.LOCKED -> JSLockedContent(config, obj);
else -> throw NotImplementedError("Unknown content type ${type}"); else -> throw NotImplementedError("Unknown content type ${type}");
} }
} }

View file

@ -0,0 +1,36 @@
package com.futo.platformplayer.api.media.platforms.js.models
import com.caoccao.javet.values.reference.V8ValueObject
import com.futo.platformplayer.api.media.models.Thumbnails
import com.futo.platformplayer.api.media.models.contents.ContentType
import com.futo.platformplayer.api.media.models.locked.IPlatformLockedContent
import com.futo.platformplayer.api.media.models.nested.IPlatformNestedContent
import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig
import com.futo.platformplayer.getOrDefault
import com.futo.platformplayer.getOrThrow
import com.futo.platformplayer.states.StatePlatform
//TODO: Refactor into video-only
class JSLockedContent: IPlatformLockedContent, JSContent {
override val contentType: ContentType get() = ContentType.LOCKED;
override val lockContentType: ContentType get() = ContentType.MEDIA;
override val lockDescription: String?;
override val unlockUrl: String?;
override val contentName: String?;
override val contentThumbnails: Thumbnails;
constructor(config: SourcePluginConfig, obj: V8ValueObject): super(config, obj) {
val contextName = "PlatformLockedContent";
this.contentName = obj.getOrDefault(config, "contentName", contextName, null);
this.contentThumbnails = obj.getOrDefault<V8ValueObject?>(config, "contentThumbnails", contextName, null)?.let {
return@let Thumbnails.fromV8(config, it);
} ?: Thumbnails();
lockDescription = obj.getOrDefault(config, "lockDescription", contextName, null);
unlockUrl = obj.getOrDefault(config, "unlockUrl", contextName, null);
}
}

View file

@ -59,8 +59,6 @@ abstract class JSPager<T> : IPager<T> {
} }
override fun getResults(): List<T> { override fun getResults(): List<T> {
warnIfMainThread("JSPager.getResults");
val previousResults = _lastResults?.let { val previousResults = _lastResults?.let {
if(!_resultChanged) if(!_resultChanged)
return@let it; return@let it;
@ -70,6 +68,7 @@ abstract class JSPager<T> : IPager<T> {
if(previousResults != null) if(previousResults != null)
return previousResults; return previousResults;
warnIfMainThread("JSPager.getResults");
val items = pager.getOrThrow<V8ValueArray>(config, "results", "JSPager"); val items = pager.getOrThrow<V8ValueArray>(config, "results", "JSPager");
val newResults = items.toArray() val newResults = items.toArray()
.map { convertResult(it as V8ValueObject) } .map { convertResult(it as V8ValueObject) }

View file

@ -95,6 +95,8 @@ class AutoUpdateDialog(context: Context?) : AlertDialog(context) {
_buttonUpdate.visibility = Button.GONE; _buttonUpdate.visibility = Button.GONE;
setCancelable(false); setCancelable(false);
setCanceledOnTouchOutside(false); setCanceledOnTouchOutside(false);
Logger.i(TAG, "Keep screen on set update")
window?.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); window?.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
_text.text = context.resources.getText(R.string.downloading_update); _text.text = context.resources.getText(R.string.downloading_update);
@ -178,6 +180,7 @@ class AutoUpdateDialog(context: Context?) : AlertDialog(context) {
} }
} finally { } finally {
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
Logger.i(TAG, "Keep screen on unset install")
window?.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); window?.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
} }
} }

View file

@ -134,6 +134,8 @@ class ImportDialog : AlertDialog {
setCancelable(false); setCancelable(false);
setCanceledOnTouchOutside(false); setCanceledOnTouchOutside(false);
Logger.i(TAG, "Keep screen on set import")
window?.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); window?.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
_updateSpinner.drawable?.assume<Animatable>()?.start(); _updateSpinner.drawable?.assume<Animatable>()?.start();
@ -201,6 +203,7 @@ class ImportDialog : AlertDialog {
} catch (e: Throwable) { } catch (e: Throwable) {
Logger.e(TAG, "Failed to update import UI.", e) Logger.e(TAG, "Failed to update import UI.", e)
} finally { } finally {
Logger.i(TAG, "Keep screen on unset update")
window?.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); window?.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
} }
} }

View file

@ -144,6 +144,7 @@ class MigrateDialog : AlertDialog {
setCancelable(false); setCancelable(false);
setCanceledOnTouchOutside(false); setCanceledOnTouchOutside(false);
Logger.i(TAG, "Keep screen on set restore")
window?.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); window?.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
_updateSpinner.drawable?.assume<Animatable>()?.start(); _updateSpinner.drawable?.assume<Animatable>()?.start();
@ -214,6 +215,7 @@ class MigrateDialog : AlertDialog {
} catch (e: Throwable) { } catch (e: Throwable) {
Logger.e(TAG, "Failed to update import UI.", e) Logger.e(TAG, "Failed to update import UI.", e)
} finally { } finally {
Logger.i(TAG, "Keep screen on unset restore")
window?.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); window?.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
} }
} }

View file

@ -4,7 +4,6 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
@ -13,7 +12,6 @@ import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.states.StatePlatform import com.futo.platformplayer.states.StatePlatform
import com.futo.platformplayer.R import com.futo.platformplayer.R
import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.UIDialogs
import com.futo.platformplayer.UISlideOverlays
import com.futo.platformplayer.api.media.models.PlatformAuthorLink import com.futo.platformplayer.api.media.models.PlatformAuthorLink
import com.futo.platformplayer.api.media.models.channels.IPlatformChannel import com.futo.platformplayer.api.media.models.channels.IPlatformChannel
import com.futo.platformplayer.api.media.models.contents.ContentType import com.futo.platformplayer.api.media.models.contents.ContentType
@ -36,7 +34,7 @@ import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile
import com.futo.platformplayer.states.StatePolycentric import com.futo.platformplayer.states.StatePolycentric
import com.futo.platformplayer.states.StateSubscriptions import com.futo.platformplayer.states.StateSubscriptions
import com.futo.platformplayer.views.FeedStyle import com.futo.platformplayer.views.FeedStyle
import com.futo.platformplayer.views.adapters.PreviewContentListAdapter import com.futo.platformplayer.views.adapters.feedtypes.PreviewContentListAdapter
import com.futo.platformplayer.views.adapters.ContentPreviewViewHolder import com.futo.platformplayer.views.adapters.ContentPreviewViewHolder
import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -56,6 +54,7 @@ class ChannelContentsFragment : Fragment(), IChannelTabFragment {
val onContentClicked = Event2<IPlatformContent, Long>(); val onContentClicked = Event2<IPlatformContent, Long>();
val onContentUrlClicked = Event2<String, ContentType>(); val onContentUrlClicked = Event2<String, ContentType>();
val onUrlClicked = Event1<String>();
val onChannelClicked = Event1<PlatformAuthorLink>(); val onChannelClicked = Event1<PlatformAuthorLink>();
val onAddToClicked = Event1<IPlatformContent>(); val onAddToClicked = Event1<IPlatformContent>();
val onAddToQueueClicked = Event1<IPlatformContent>(); val onAddToQueueClicked = Event1<IPlatformContent>();
@ -75,15 +74,14 @@ class ChannelContentsFragment : Fragment(), IChannelTabFragment {
} }
private val _taskLoadVideos = TaskHandler<IPlatformChannel, IPager<IPlatformContent>>({lifecycleScope}, { private val _taskLoadVideos = TaskHandler<IPlatformChannel, IPager<IPlatformContent>>({lifecycleScope}, {
return@TaskHandler getContentPager(it); val livePager = getContentPager(it);
return@TaskHandler if(_channel?.let { StateSubscriptions.instance.isSubscribed(it) } == true)
ChannelContentCache.cachePagerResults(lifecycleScope, livePager);
else livePager;
}).success { livePager -> }).success { livePager ->
setLoading(false); setLoading(false);
val pager = if(_channel?.let { StateSubscriptions.instance.isSubscribed(it) } == true) setPager(livePager);
ChannelContentCache.cachePagerResults(lifecycleScope, livePager);
else livePager;
setPager(pager);
} }
.exception<ScriptCaptchaRequiredException> { } .exception<ScriptCaptchaRequiredException> { }
.exception<Throwable> { .exception<Throwable> {
@ -155,6 +153,7 @@ class ChannelContentsFragment : Fragment(), IChannelTabFragment {
_adapterResults = PreviewContentListAdapter(view.context, FeedStyle.THUMBNAIL, _results).apply { _adapterResults = PreviewContentListAdapter(view.context, FeedStyle.THUMBNAIL, _results).apply {
this.onContentUrlClicked.subscribe(this@ChannelContentsFragment.onContentUrlClicked::emit); this.onContentUrlClicked.subscribe(this@ChannelContentsFragment.onContentUrlClicked::emit);
this.onUrlClicked.subscribe(this@ChannelContentsFragment.onUrlClicked::emit);
this.onContentClicked.subscribe(this@ChannelContentsFragment.onContentClicked::emit); this.onContentClicked.subscribe(this@ChannelContentsFragment.onContentClicked::emit);
this.onChannelClicked.subscribe(this@ChannelContentsFragment.onChannelClicked::emit); this.onChannelClicked.subscribe(this@ChannelContentsFragment.onChannelClicked::emit);
this.onAddToClicked.subscribe(this@ChannelContentsFragment.onAddToClicked::emit); this.onAddToClicked.subscribe(this@ChannelContentsFragment.onAddToClicked::emit);

View file

@ -210,6 +210,9 @@ class ChannelFragment : MainFragment() {
UIDialogs.toast(context, "Queued [$name]", false); UIDialogs.toast(context, "Queued [$name]", false);
} }
} }
adapter.onUrlClicked.subscribe { url ->
fragment.navigate<BrowserFragment>(url);
}
adapter.onContentUrlClicked.subscribe { url, contentType -> adapter.onContentUrlClicked.subscribe { url, contentType ->
when(contentType) { when(contentType) {
ContentType.MEDIA -> { ContentType.MEDIA -> {

View file

@ -1,6 +1,5 @@
package com.futo.platformplayer.fragment.mainactivity.main package com.futo.platformplayer.fragment.mainactivity.main
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
@ -17,15 +16,14 @@ import com.futo.platformplayer.api.media.structures.*
import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.states.StateMeta import com.futo.platformplayer.states.StateMeta
import com.futo.platformplayer.states.StatePlayer import com.futo.platformplayer.states.StatePlayer
import com.futo.platformplayer.states.StatePlaylists
import com.futo.platformplayer.video.PlayerManager import com.futo.platformplayer.video.PlayerManager
import com.futo.platformplayer.views.FeedStyle import com.futo.platformplayer.views.FeedStyle
import com.futo.platformplayer.views.adapters.PreviewContentListAdapter import com.futo.platformplayer.views.adapters.feedtypes.PreviewContentListAdapter
import com.futo.platformplayer.views.adapters.ContentPreviewViewHolder import com.futo.platformplayer.views.adapters.ContentPreviewViewHolder
import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader
import com.futo.platformplayer.views.adapters.InsertedViewHolder import com.futo.platformplayer.views.adapters.InsertedViewHolder
import com.futo.platformplayer.views.adapters.PreviewNestedVideoViewHolder import com.futo.platformplayer.views.adapters.feedtypes.PreviewNestedVideoViewHolder
import com.futo.platformplayer.views.adapters.PreviewVideoViewHolder import com.futo.platformplayer.views.adapters.feedtypes.PreviewVideoViewHolder
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuItem import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuItem
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuOverlay import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuOverlay
import kotlin.math.floor import kotlin.math.floor
@ -66,6 +64,7 @@ abstract class ContentFeedView<TFragment> : FeedView<TFragment, IPlatformContent
private fun attachAdapterEvents(adapter: PreviewContentListAdapter) { private fun attachAdapterEvents(adapter: PreviewContentListAdapter) {
adapter.onContentUrlClicked.subscribe(this, this@ContentFeedView::onContentUrlClicked); adapter.onContentUrlClicked.subscribe(this, this@ContentFeedView::onContentUrlClicked);
adapter.onUrlClicked.subscribe(this, this@ContentFeedView::onUrlClicked);
adapter.onContentClicked.subscribe(this) { content, time -> adapter.onContentClicked.subscribe(this) { content, time ->
this@ContentFeedView.onContentClicked(content, time); this@ContentFeedView.onContentClicked(content, time);
}; };
@ -132,6 +131,7 @@ abstract class ContentFeedView<TFragment> : FeedView<TFragment, IPlatformContent
private fun detachAdapterEvents() { private fun detachAdapterEvents() {
val adapter = recyclerData.adapter as PreviewContentListAdapter? ?: return; val adapter = recyclerData.adapter as PreviewContentListAdapter? ?: return;
adapter.onContentUrlClicked.remove(this); adapter.onContentUrlClicked.remove(this);
adapter.onUrlClicked.remove(this);
adapter.onContentClicked.remove(this); adapter.onContentClicked.remove(this);
adapter.onChannelClicked.remove(this); adapter.onChannelClicked.remove(this);
adapter.onAddToClicked.remove(this); adapter.onAddToClicked.remove(this);
@ -191,6 +191,9 @@ abstract class ContentFeedView<TFragment> : FeedView<TFragment, IPlatformContent
else -> {}; else -> {};
} }
} }
protected open fun onUrlClicked(url: String) {
fragment.navigate<BrowserFragment>(url);
}
private fun playPreview() { private fun playPreview() {
if(feedStyle == FeedStyle.THUMBNAIL) if(feedStyle == FeedStyle.THUMBNAIL)

View file

@ -20,7 +20,6 @@ import androidx.lifecycle.lifecycleScope
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.futo.platformplayer.R import com.futo.platformplayer.R
import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.UIDialogs
import com.futo.platformplayer.UISlideOverlays
import com.futo.platformplayer.api.media.PlatformID import com.futo.platformplayer.api.media.PlatformID
import com.futo.platformplayer.api.media.models.Thumbnails import com.futo.platformplayer.api.media.models.Thumbnails
import com.futo.platformplayer.api.media.models.comments.PolycentricPlatformComment import com.futo.platformplayer.api.media.models.comments.PolycentricPlatformComment
@ -46,7 +45,7 @@ import com.futo.platformplayer.views.others.CreatorThumbnail
import com.futo.platformplayer.views.platform.PlatformIndicator import com.futo.platformplayer.views.platform.PlatformIndicator
import com.futo.platformplayer.views.subscriptions.SubscribeButton import com.futo.platformplayer.views.subscriptions.SubscribeButton
import com.futo.platformplayer.views.others.Toggle import com.futo.platformplayer.views.others.Toggle
import com.futo.platformplayer.views.adapters.PreviewPostView import com.futo.platformplayer.views.adapters.feedtypes.PreviewPostView
import com.futo.platformplayer.views.overlays.RepliesOverlay import com.futo.platformplayer.views.overlays.RepliesOverlay
import com.futo.platformplayer.views.pills.PillRatingLikesDislikes import com.futo.platformplayer.views.pills.PillRatingLikesDislikes
import com.futo.polycentric.core.ApiMethods import com.futo.polycentric.core.ApiMethods

View file

@ -280,7 +280,7 @@ class SubscriptionsFeedFragment : MainFragment() {
override fun filterResults(results: List<IPlatformContent>): List<IPlatformContent> { override fun filterResults(results: List<IPlatformContent>): List<IPlatformContent> {
val nowSoon = OffsetDateTime.now().plusMinutes(5); val nowSoon = OffsetDateTime.now().plusMinutes(5);
return results.filter { return results.filter {
val allowedContentType = _filterSettings.allowContentTypes.contains(if(it.contentType == ContentType.NESTED_VIDEO) ContentType.MEDIA else it.contentType); val allowedContentType = _filterSettings.allowContentTypes.contains(if(it.contentType == ContentType.NESTED_VIDEO || it.contentType == ContentType.LOCKED) ContentType.MEDIA else it.contentType);
if(it.datetime?.isAfter(nowSoon) == true) { if(it.datetime?.isAfter(nowSoon) == true) {
if(!_filterSettings.allowPlanned) if(!_filterSettings.allowPlanned)

View file

@ -68,8 +68,6 @@ class VideoDetailFragment : MainFragment {
super.onShownWithView(parameter, isBack); super.onShownWithView(parameter, isBack);
Logger.i(TAG, "onShownWithView parameter=$parameter") Logger.i(TAG, "onShownWithView parameter=$parameter")
activity?.window?.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
if(parameter is IPlatformVideoDetails) if(parameter is IPlatformVideoDetails)
_viewDetail?.setVideoDetails(parameter, true); _viewDetail?.setVideoDetails(parameter, true);
else if (parameter is IPlatformVideo) else if (parameter is IPlatformVideo)
@ -176,7 +174,6 @@ class VideoDetailFragment : MainFragment {
_viewDetail?.onStop(); _viewDetail?.onStop();
close(); close();
activity?.window?.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
StatePlayer.instance.clearQueue(); StatePlayer.instance.clearQueue();
StatePlayer.instance.setPlayerClosed(); StatePlayer.instance.setPlayerClosed();
} }

View file

@ -22,6 +22,7 @@ import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.view.WindowManager
import android.widget.* import android.widget.*
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
@ -59,6 +60,7 @@ import com.futo.platformplayer.casting.StateCasting
import com.futo.platformplayer.constructs.Event0 import com.futo.platformplayer.constructs.Event0
import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.constructs.TaskHandler import com.futo.platformplayer.constructs.TaskHandler
import com.futo.platformplayer.dialogs.AutoUpdateDialog
import com.futo.platformplayer.downloads.VideoLocal import com.futo.platformplayer.downloads.VideoLocal
import com.futo.platformplayer.engine.exceptions.ScriptAgeException import com.futo.platformplayer.engine.exceptions.ScriptAgeException
import com.futo.platformplayer.engine.exceptions.ScriptException import com.futo.platformplayer.engine.exceptions.ScriptException
@ -216,6 +218,9 @@ class VideoDetailView : ConstraintLayout {
private var _lastAudioSource: IAudioSource? = null; private var _lastAudioSource: IAudioSource? = null;
private var _lastSubtitleSource: ISubtitleSource? = null; private var _lastSubtitleSource: ISubtitleSource? = null;
private var _isCasting: Boolean = false; private var _isCasting: Boolean = false;
var isPlaying: Boolean = false
private set;
var lastPositionMilliseconds: Long = 0 var lastPositionMilliseconds: Long = 0
private set; private set;
private var _historicalPosition: Long = 0; private var _historicalPosition: Long = 0;
@ -600,6 +605,8 @@ class VideoDetailView : ConstraintLayout {
_lastSubtitleSource = null; _lastSubtitleSource = null;
video = null; video = null;
_playbackTracker = null; _playbackTracker = null;
Logger.i(TAG, "Keep screen on unset onClose")
fragment.activity?.window?.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}; };
_layoutResume.setOnClickListener { _layoutResume.setOnClickListener {
@ -1087,7 +1094,7 @@ class VideoDetailView : ConstraintLayout {
_player.setMetadata(video.name, video.author.name); _player.setMetadata(video.name, video.author.name);
_toggleCommentType.setValue(false, false); _toggleCommentType.setValue(Settings.instance.comments.defaultCommentSection == 1, false);
updateCommentType(true); updateCommentType(true);
//UI //UI
@ -1680,14 +1687,28 @@ class VideoDetailView : ConstraintLayout {
if(playing) { if(playing) {
_minimize_controls_pause.visibility = View.VISIBLE; _minimize_controls_pause.visibility = View.VISIBLE;
_minimize_controls_play.visibility = View.GONE; _minimize_controls_play.visibility = View.GONE;
if (_isCasting) {
if (Settings.instance.casting.keepScreenOn) {
Logger.i(TAG, "Keep screen on set handlePlayChanged casting")
fragment.activity?.window?.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
} else {
Logger.i(TAG, "Keep screen on set handlePlayChanged player")
fragment.activity?.window?.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
} }
else { else {
_minimize_controls_pause.visibility = View.GONE; _minimize_controls_pause.visibility = View.GONE;
_minimize_controls_play.visibility = View.VISIBLE; _minimize_controls_play.visibility = View.VISIBLE;
Logger.i(TAG, "Keep screen on unset handlePlayChanged")
fragment.activity?.window?.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
} }
isPlaying = playing;
onPlayChanged.emit(playing); onPlayChanged.emit(playing);
updateTracker(_player.position, playing, true); updateTracker(lastPositionMilliseconds, playing, true);
} }
private fun handleSelectVideoTrack(videoSource: IVideoSource) { private fun handleSelectVideoTrack(videoSource: IVideoSource) {
@ -2031,7 +2052,8 @@ class VideoDetailView : ConstraintLayout {
StatePlaylists.instance.updateHistoryPosition(v, true, (positionMilliseconds.toFloat() / 1000.0f).toLong()); StatePlaylists.instance.updateHistoryPosition(v, true, (positionMilliseconds.toFloat() / 1000.0f).toLong());
_lastPositionSaveTime = currentTime; _lastPositionSaveTime = currentTime;
} }
updateTracker(positionMilliseconds, _player.playing, false);
updateTracker(positionMilliseconds, isPlaying, false);
} }
private fun updateTracker(positionMs: Long, isPlaying: Boolean, forceUpdate: Boolean = false) { private fun updateTracker(positionMs: Long, isPlaying: Boolean, forceUpdate: Boolean = false) {

View file

@ -15,6 +15,7 @@ class ChannelViewPagerAdapter(fragmentManager: FragmentManager, lifecycle: Lifec
private val _cache: Array<Fragment?> = arrayOfNulls(4); private val _cache: Array<Fragment?> = arrayOfNulls(4);
val onContentUrlClicked = Event2<String, ContentType>(); val onContentUrlClicked = Event2<String, ContentType>();
val onUrlClicked = Event1<String>();
val onContentClicked = Event2<IPlatformContent, Long>(); val onContentClicked = Event2<IPlatformContent, Long>();
val onChannelClicked = Event1<PlatformAuthorLink>(); val onChannelClicked = Event1<PlatformAuthorLink>();
val onAddToClicked = Event1<IPlatformContent>(); val onAddToClicked = Event1<IPlatformContent>();
@ -50,6 +51,7 @@ class ChannelViewPagerAdapter(fragmentManager: FragmentManager, lifecycle: Lifec
0 -> ChannelContentsFragment.newInstance().apply { 0 -> ChannelContentsFragment.newInstance().apply {
onContentClicked.subscribe(this@ChannelViewPagerAdapter.onContentClicked::emit); onContentClicked.subscribe(this@ChannelViewPagerAdapter.onContentClicked::emit);
onContentUrlClicked.subscribe(this@ChannelViewPagerAdapter.onContentUrlClicked::emit); onContentUrlClicked.subscribe(this@ChannelViewPagerAdapter.onContentUrlClicked::emit);
onUrlClicked.subscribe(this@ChannelViewPagerAdapter.onUrlClicked::emit);
onChannelClicked.subscribe(this@ChannelViewPagerAdapter.onChannelClicked::emit); onChannelClicked.subscribe(this@ChannelViewPagerAdapter.onChannelClicked::emit);
onAddToClicked.subscribe(this@ChannelViewPagerAdapter.onAddToClicked::emit); onAddToClicked.subscribe(this@ChannelViewPagerAdapter.onAddToClicked::emit);
onAddToQueueClicked.subscribe(this@ChannelViewPagerAdapter.onAddToQueueClicked::emit); onAddToQueueClicked.subscribe(this@ChannelViewPagerAdapter.onAddToQueueClicked::emit);

View file

@ -1,4 +1,4 @@
package com.futo.platformplayer.views.adapters package com.futo.platformplayer.views.adapters.feedtypes
import android.content.Context import android.content.Context
import android.util.Log import android.util.Log
@ -18,6 +18,9 @@ import com.futo.platformplayer.states.StateApp
import com.futo.platformplayer.states.StatePlatform import com.futo.platformplayer.states.StatePlatform
import com.futo.platformplayer.video.PlayerManager import com.futo.platformplayer.video.PlayerManager
import com.futo.platformplayer.views.FeedStyle import com.futo.platformplayer.views.FeedStyle
import com.futo.platformplayer.views.adapters.ContentPreviewViewHolder
import com.futo.platformplayer.views.adapters.EmptyPreviewViewHolder
import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader
class PreviewContentListAdapter : InsertedViewAdapterWithLoader<ContentPreviewViewHolder> { class PreviewContentListAdapter : InsertedViewAdapterWithLoader<ContentPreviewViewHolder> {
private var _initialPlay = true; private var _initialPlay = true;
@ -27,6 +30,7 @@ class PreviewContentListAdapter : InsertedViewAdapterWithLoader<ContentPreviewVi
private val _feedStyle : FeedStyle; private val _feedStyle : FeedStyle;
private var _paused: Boolean = false; private var _paused: Boolean = false;
val onUrlClicked = Event1<String>();
val onContentUrlClicked = Event2<String, ContentType>(); val onContentUrlClicked = Event2<String, ContentType>();
val onContentClicked = Event2<IPlatformContent, Long>(); val onContentClicked = Event2<IPlatformContent, Long>();
val onChannelClicked = Event1<PlatformAuthorLink>(); val onChannelClicked = Event1<PlatformAuthorLink>();
@ -72,6 +76,7 @@ class PreviewContentListAdapter : InsertedViewAdapterWithLoader<ContentPreviewVi
ContentType.POST -> createPostViewHolder(viewGroup); ContentType.POST -> createPostViewHolder(viewGroup);
ContentType.PLAYLIST -> createPlaylistViewHolder(viewGroup); ContentType.PLAYLIST -> createPlaylistViewHolder(viewGroup);
ContentType.NESTED_VIDEO -> createNestedViewHolder(viewGroup); ContentType.NESTED_VIDEO -> createNestedViewHolder(viewGroup);
ContentType.LOCKED -> createLockedViewHolder(viewGroup);
else -> EmptyPreviewViewHolder(viewGroup) else -> EmptyPreviewViewHolder(viewGroup)
} }
} }
@ -87,6 +92,9 @@ class PreviewContentListAdapter : InsertedViewAdapterWithLoader<ContentPreviewVi
this.onAddToClicked.subscribe(this@PreviewContentListAdapter.onAddToClicked::emit); this.onAddToClicked.subscribe(this@PreviewContentListAdapter.onAddToClicked::emit);
this.onAddToQueueClicked.subscribe(this@PreviewContentListAdapter.onAddToQueueClicked::emit); this.onAddToQueueClicked.subscribe(this@PreviewContentListAdapter.onAddToQueueClicked::emit);
}; };
private fun createLockedViewHolder(viewGroup: ViewGroup): PreviewLockedViewHolder = PreviewLockedViewHolder(viewGroup, _feedStyle).apply {
this.onLockedUrlClicked.subscribe(this@PreviewContentListAdapter.onUrlClicked::emit);
};
private fun createPlaceholderViewHolder(viewGroup: ViewGroup): PreviewPlaceholderViewHolder private fun createPlaceholderViewHolder(viewGroup: ViewGroup): PreviewPlaceholderViewHolder
= PreviewPlaceholderViewHolder(viewGroup, _feedStyle); = PreviewPlaceholderViewHolder(viewGroup, _feedStyle);
private fun createVideoPreviewViewHolder(viewGroup: ViewGroup): PreviewVideoViewHolder = PreviewVideoViewHolder(viewGroup, _feedStyle, _exoPlayer).apply { private fun createVideoPreviewViewHolder(viewGroup: ViewGroup): PreviewVideoViewHolder = PreviewVideoViewHolder(viewGroup, _feedStyle, _exoPlayer).apply {
@ -143,6 +151,7 @@ class PreviewContentListAdapter : InsertedViewAdapterWithLoader<ContentPreviewVi
fun release() { fun release() {
_taskLoadContent.dispose(); _taskLoadContent.dispose();
onContentUrlClicked.clear(); onContentUrlClicked.clear();
onUrlClicked.clear();
onContentClicked.clear(); onContentClicked.clear();
onChannelClicked.clear(); onChannelClicked.clear();
onAddToClicked.clear(); onAddToClicked.clear();

View file

@ -0,0 +1,134 @@
package com.futo.platformplayer.views.adapters.feedtypes
import android.animation.ObjectAnimator
import android.content.Context
import android.content.res.Resources
import android.util.Log
import android.util.TypedValue
import android.view.View
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import com.bumptech.glide.Glide
import com.futo.platformplayer.*
import com.futo.platformplayer.api.media.PlatformID
import com.futo.platformplayer.api.media.models.PlatformAuthorLink
import com.futo.platformplayer.api.media.models.contents.ContentType
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails
import com.futo.platformplayer.api.media.models.locked.IPlatformLockedContent
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails
import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.constructs.Event2
import com.futo.platformplayer.constructs.TaskHandler
import com.futo.platformplayer.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.states.StateApp
import com.futo.platformplayer.states.StateDownloads
import com.futo.platformplayer.video.PlayerManager
import com.futo.platformplayer.views.others.CreatorThumbnail
import com.futo.platformplayer.views.FeedStyle
import com.futo.platformplayer.views.platform.PlatformIndicator
import com.futo.platformplayer.views.video.FutoThumbnailPlayer
import com.futo.polycentric.core.toURLInfoSystemLinkUrl
import com.google.android.material.imageview.ShapeableImageView
class PreviewLockedView : LinearLayout {
protected val _feedStyle : FeedStyle;
private val _textChannelName: TextView;
private val _textVideoName: TextView;
//private val _imageNeoPassChannel: ImageView;
private val _imageChannelThumbnail: ImageView;
private val _imageVideoThumbnail: ImageView;
private val _platformIndicator: PlatformIndicator;
private val _textLockedDescription: TextView;
//private val _textBrowserOpen: TextView;
private val _textLockedUrl: TextView;
private val _textVideoMetadata: TextView;
val onLockedUrlClicked = Event1<String>();
constructor(context: Context, feedStyle : FeedStyle) : super(context) {
inflate(feedStyle);
_feedStyle = feedStyle;
_textVideoName = findViewById(R.id.text_video_name);
_textChannelName = findViewById(R.id.text_channel_name);
//_imageNeoPassChannel = findViewById(R.id.image_neopass_channel);
_imageChannelThumbnail = findViewById(R.id.image_channel_thumbnail);
_imageVideoThumbnail = findViewById(R.id.image_video_thumbnail);
_platformIndicator = findViewById(R.id.thumbnail_platform)
_textLockedDescription = findViewById(R.id.text_locked_description);
//_textBrowserOpen = findViewById(R.id.text_browser_open);
_textLockedUrl = findViewById(R.id.text_locked_url);
_textVideoMetadata = findViewById(R.id.text_video_metadata);
setOnClickListener {
if(!_textLockedUrl.text.isNullOrEmpty())
onLockedUrlClicked.emit(_textLockedUrl.text.toString());
}
}
protected open fun inflate(feedStyle: FeedStyle) {
inflate(context, when(feedStyle) {
FeedStyle.PREVIEW -> R.layout.list_locked_preview
else -> R.layout.list_locked_thumbnail
}, this)
}
open fun bind(content: IPlatformContent) {
_textVideoName.text = content.name;
_textChannelName.text = content.author.name;
_platformIndicator.setPlatformFromClientID(content.id.pluginId);
if(content is IPlatformLockedContent) {
_imageVideoThumbnail.loadThumbnails(content.contentThumbnails, false) {
it.placeholder(R.drawable.placeholder_video_thumbnail)
.into(_imageVideoThumbnail);
};
Glide.with(_imageChannelThumbnail)
.load(content.author.thumbnail)
.placeholder(R.drawable.placeholder_channel_thumbnail)
.into(_imageChannelThumbnail);
_textLockedDescription.text = content.lockDescription ?: "";
_textLockedUrl.text = content.unlockUrl ?: "";
}
else {
_imageChannelThumbnail.setImageResource(0);
_imageVideoThumbnail.setImageResource(0);
_textLockedDescription.text = "";
_textLockedUrl.text = "";
}
if(_textLockedUrl.text.isNullOrEmpty()) {
_textLockedUrl.visibility = GONE;
_textVideoMetadata.text = "";
}
else {
_textLockedUrl.visibility = VISIBLE;
_textVideoMetadata.text = "Tap to open in browser";
}
}
open fun preview(video: IPlatformContentDetails?, paused: Boolean) {
}
companion object {
private val TAG = "PreviewLockedView"
}
}

View file

@ -0,0 +1,46 @@
package com.futo.platformplayer.views.adapters.feedtypes
import android.content.Context
import android.graphics.drawable.Animatable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import com.futo.platformplayer.*
import com.futo.platformplayer.api.media.models.contents.ContentType
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails
import com.futo.platformplayer.api.media.models.contents.PlatformContentPlaceholder
import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.constructs.Event2
import com.futo.platformplayer.views.FeedStyle
import com.futo.platformplayer.views.adapters.ContentPreviewViewHolder
import com.futo.platformplayer.views.platform.PlatformIndicator
class PreviewLockedViewHolder : ContentPreviewViewHolder {
override var content: IPlatformContent? = null;
private val view: PreviewLockedView get() = itemView as PreviewLockedView;
val onLockedUrlClicked = Event1<String>();
val context: Context;
constructor(viewGroup: ViewGroup, feedStyle: FeedStyle) : super(PreviewLockedView(viewGroup.context, feedStyle)) {
context = itemView.context;
view.onLockedUrlClicked.subscribe(onLockedUrlClicked::emit);
}
override fun bind(content: IPlatformContent) = view.bind(content);
override fun preview(video: IPlatformContentDetails?, paused: Boolean) { }
override fun stopPreview() { }
override fun pausePreview() { }
override fun resumePreview() { }
companion object {
private val TAG = "PlaceholderPreviewViewHolder"
}
}

View file

@ -1,4 +1,4 @@
package com.futo.platformplayer.views.adapters package com.futo.platformplayer.views.adapters.feedtypes
import android.content.Context import android.content.Context
import android.view.View import android.view.View

View file

@ -1,9 +1,6 @@
package com.futo.platformplayer.views.adapters package com.futo.platformplayer.views.adapters.feedtypes
import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.FrameLayout
import com.futo.platformplayer.*
import com.futo.platformplayer.api.media.models.PlatformAuthorLink import com.futo.platformplayer.api.media.models.PlatformAuthorLink
import com.futo.platformplayer.api.media.models.contents.ContentType import com.futo.platformplayer.api.media.models.contents.ContentType
import com.futo.platformplayer.api.media.models.contents.IPlatformContent import com.futo.platformplayer.api.media.models.contents.IPlatformContent
@ -13,6 +10,7 @@ import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.constructs.Event2 import com.futo.platformplayer.constructs.Event2
import com.futo.platformplayer.video.PlayerManager import com.futo.platformplayer.video.PlayerManager
import com.futo.platformplayer.views.FeedStyle import com.futo.platformplayer.views.FeedStyle
import com.futo.platformplayer.views.adapters.ContentPreviewViewHolder
class PreviewNestedVideoViewHolder : ContentPreviewViewHolder { class PreviewNestedVideoViewHolder : ContentPreviewViewHolder {
@ -25,7 +23,9 @@ class PreviewNestedVideoViewHolder : ContentPreviewViewHolder {
override val content: IPlatformContent? get() = view.content; override val content: IPlatformContent? get() = view.content;
private val view: PreviewNestedVideoView get() = itemView as PreviewNestedVideoView; private val view: PreviewNestedVideoView get() = itemView as PreviewNestedVideoView;
constructor(viewGroup: ViewGroup, feedStyle : FeedStyle, exoPlayer: PlayerManager? = null): super(PreviewNestedVideoView(viewGroup.context, feedStyle, exoPlayer)) { constructor(viewGroup: ViewGroup, feedStyle : FeedStyle, exoPlayer: PlayerManager? = null): super(
PreviewNestedVideoView(viewGroup.context, feedStyle, exoPlayer)
) {
view.onContentUrlClicked.subscribe(onContentUrlClicked::emit); view.onContentUrlClicked.subscribe(onContentUrlClicked::emit);
view.onVideoClicked.subscribe(onVideoClicked::emit); view.onVideoClicked.subscribe(onVideoClicked::emit);
view.onChannelClicked.subscribe(onChannelClicked::emit); view.onChannelClicked.subscribe(onChannelClicked::emit);

View file

@ -1,4 +1,4 @@
package com.futo.platformplayer.views.adapters package com.futo.platformplayer.views.adapters.feedtypes
import android.content.Context import android.content.Context
import android.graphics.drawable.Animatable import android.graphics.drawable.Animatable
@ -12,6 +12,7 @@ import com.futo.platformplayer.api.media.models.contents.IPlatformContent
import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails
import com.futo.platformplayer.api.media.models.contents.PlatformContentPlaceholder import com.futo.platformplayer.api.media.models.contents.PlatformContentPlaceholder
import com.futo.platformplayer.views.FeedStyle import com.futo.platformplayer.views.FeedStyle
import com.futo.platformplayer.views.adapters.ContentPreviewViewHolder
import com.futo.platformplayer.views.platform.PlatformIndicator import com.futo.platformplayer.views.platform.PlatformIndicator

View file

@ -1,4 +1,4 @@
package com.futo.platformplayer.views.adapters package com.futo.platformplayer.views.adapters.feedtypes
import android.view.ViewGroup import android.view.ViewGroup
import com.futo.platformplayer.api.media.models.PlatformAuthorLink import com.futo.platformplayer.api.media.models.PlatformAuthorLink
@ -7,6 +7,8 @@ import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails
import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylist import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylist
import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.views.FeedStyle import com.futo.platformplayer.views.FeedStyle
import com.futo.platformplayer.views.adapters.ContentPreviewViewHolder
import com.futo.platformplayer.views.adapters.PlaylistView
class PreviewPlaylistViewHolder : ContentPreviewViewHolder { class PreviewPlaylistViewHolder : ContentPreviewViewHolder {

View file

@ -1,4 +1,4 @@
package com.futo.platformplayer.views.adapters package com.futo.platformplayer.views.adapters.feedtypes
import android.animation.ObjectAnimator import android.animation.ObjectAnimator
import android.content.Context import android.content.Context

View file

@ -1,4 +1,4 @@
package com.futo.platformplayer.views.adapters package com.futo.platformplayer.views.adapters.feedtypes
import android.view.ViewGroup import android.view.ViewGroup
import com.futo.platformplayer.api.media.models.PlatformAuthorLink import com.futo.platformplayer.api.media.models.PlatformAuthorLink
@ -7,6 +7,7 @@ import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails
import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.video.PlayerManager import com.futo.platformplayer.video.PlayerManager
import com.futo.platformplayer.views.FeedStyle import com.futo.platformplayer.views.FeedStyle
import com.futo.platformplayer.views.adapters.ContentPreviewViewHolder
class PreviewPostViewHolder : ContentPreviewViewHolder { class PreviewPostViewHolder : ContentPreviewViewHolder {
@ -18,7 +19,9 @@ class PreviewPostViewHolder : ContentPreviewViewHolder {
private val view: PreviewPostView get() = itemView as PreviewPostView; private val view: PreviewPostView get() = itemView as PreviewPostView;
constructor(viewGroup: ViewGroup, feedStyle : FeedStyle, exoPlayer: PlayerManager? = null): super(PreviewPostView(viewGroup.context, feedStyle)) { constructor(viewGroup: ViewGroup, feedStyle : FeedStyle, exoPlayer: PlayerManager? = null): super(
PreviewPostView(viewGroup.context, feedStyle)
) {
view.onContentClicked.subscribe(onContentClicked::emit); view.onContentClicked.subscribe(onContentClicked::emit);
view.onChannelClicked.subscribe(onChannelClicked::emit); view.onChannelClicked.subscribe(onChannelClicked::emit);
} }

View file

@ -1,4 +1,4 @@
package com.futo.platformplayer.views.adapters package com.futo.platformplayer.views.adapters.feedtypes
import android.animation.ObjectAnimator import android.animation.ObjectAnimator
import android.content.Context import android.content.Context

View file

@ -1,4 +1,4 @@
package com.futo.platformplayer.views.adapters package com.futo.platformplayer.views.adapters.feedtypes
import android.view.ViewGroup import android.view.ViewGroup
import com.futo.platformplayer.api.media.models.PlatformAuthorLink import com.futo.platformplayer.api.media.models.PlatformAuthorLink
@ -9,6 +9,7 @@ import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.constructs.Event2 import com.futo.platformplayer.constructs.Event2
import com.futo.platformplayer.video.PlayerManager import com.futo.platformplayer.video.PlayerManager
import com.futo.platformplayer.views.FeedStyle import com.futo.platformplayer.views.FeedStyle
import com.futo.platformplayer.views.adapters.ContentPreviewViewHolder
class PreviewVideoViewHolder : ContentPreviewViewHolder { class PreviewVideoViewHolder : ContentPreviewViewHolder {
@ -26,7 +27,9 @@ class PreviewVideoViewHolder : ContentPreviewViewHolder {
private val view: PreviewVideoView get() = itemView as PreviewVideoView; private val view: PreviewVideoView get() = itemView as PreviewVideoView;
constructor(viewGroup: ViewGroup, feedStyle : FeedStyle, exoPlayer: PlayerManager? = null): super(PreviewVideoView(viewGroup.context, feedStyle, exoPlayer)) { constructor(viewGroup: ViewGroup, feedStyle : FeedStyle, exoPlayer: PlayerManager? = null): super(
PreviewVideoView(viewGroup.context, feedStyle, exoPlayer)
) {
view.onVideoClicked.subscribe(onVideoClicked::emit); view.onVideoClicked.subscribe(onVideoClicked::emit);
view.onChannelClicked.subscribe(onChannelClicked::emit); view.onChannelClicked.subscribe(onChannelClicked::emit);
view.onAddToClicked.subscribe(onAddToClicked::emit); view.onAddToClicked.subscribe(onAddToClicked::emit);

View file

@ -295,7 +295,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
Logger.i(TAG, "Updated chapter to [${_currentChapter?.name}] with speed ${delta}ms (${pos - (_currentChapter?.timeStart?.times(1000)?.toLong() ?: 0)}ms late [${_currentChapter?.timeStart}s])"); Logger.i(TAG, "Updated chapter to [${_currentChapter?.name}] with speed ${delta}ms (${pos - (_currentChapter?.timeStart?.times(1000)?.toLong() ?: 0)}ms late [${_currentChapter?.timeStart}s])");
} }
} }
if(playingCached) if(playing)
updateChaptersLoop(loopId); updateChaptersLoop(loopId);
else else
_currentChapterLoopActive = false; _currentChapterLoopActive = false;

View file

@ -1,6 +1,7 @@
package com.futo.platformplayer.views.video package com.futo.platformplayer.views.video
import android.content.Context import android.content.Context
import android.media.session.PlaybackState
import android.net.Uri import android.net.Uri
import android.util.AttributeSet import android.util.AttributeSet
import android.widget.RelativeLayout import android.widget.RelativeLayout
@ -60,8 +61,7 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
private set; private set;
val exoPlayerStateName: String; val exoPlayerStateName: String;
protected var playingCached: Boolean = false; var playing: Boolean = false;
val playing: Boolean get() = exoPlayer?.player?.playWhenReady ?: false;
val position: Long get() = exoPlayer?.player?.currentPosition ?: 0; val position: Long get() = exoPlayer?.player?.currentPosition ?: 0;
val duration: Long get() = exoPlayer?.player?.duration ?: 0; val duration: Long get() = exoPlayer?.player?.duration ?: 0;
@ -99,12 +99,23 @@ abstract class FutoVideoPlayerBase : RelativeLayout {
} }
} }
} }
updatePlaying();
} }
override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) { override fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int) {
super.onPlayWhenReadyChanged(playWhenReady, reason) super.onPlayWhenReadyChanged(playWhenReady, reason)
onPlayChanged.emit(playWhenReady); updatePlaying();
playingCached = playWhenReady; }
fun updatePlaying() {
val newPlaying = exoPlayer?.let { it.player.playWhenReady && it.player.playbackState != Player.STATE_ENDED && it.player.playbackState != Player.STATE_IDLE } ?: false
if (newPlaying == playing) {
return;
}
playing = newPlaying;
onPlayChanged.emit(playing);
} }
override fun onVideoSizeChanged(videoSize: VideoSize) { override fun onVideoSizeChanged(videoSize: VideoSize) {

View file

@ -0,0 +1,333 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintDimensionRatio="H,16:13">
<FrameLayout
android:id="@+id/player_container"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintDimensionRatio="H,16:9">
<ImageView
android:id="@+id/image_video_thumbnail"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:contentDescription="@string/thumbnail"
android:scaleType="centerCrop"
android:layout_marginBottom="6dp"
tools:srcCompat="@drawable/placeholder_video_thumbnail" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--
<com.futo.platformplayer.views.platform.PlatformIndicator
android:id="@+id/thumbnail_platform_nested"
android:layout_width="25dp"
android:layout_height="25dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_margin="5dp"
android:scaleType="centerInside"
tools:src="@drawable/ic_peertube"/> -->
<LinearLayout
android:id="@+id/thumbnail_live_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:paddingStart="2dp"
android:paddingEnd="2dp"
android:layout_marginEnd="4dp"
android:layout_marginBottom="13dp"
android:paddingTop="0dp"
android:gravity="center_vertical"
android:orientation="horizontal"
android:background="@drawable/background_thumbnail_live"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent">
<TextView
android:id="@+id/thumbnail_live"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:includeFontPadding="false"
android:paddingLeft="2dp"
android:paddingRight="2dp"
android:textColor="#FFFFFF"
android:textSize="12dp"
android:text="@string/live"
android:layout_gravity="center"
android:textStyle="normal" />
</LinearLayout>
<LinearLayout
android:id="@+id/thumbnail_duration_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:paddingStart="2dp"
android:paddingEnd="2dp"
android:layout_marginEnd="4dp"
android:layout_marginBottom="13dp"
android:paddingTop="0dp"
android:gravity="center_vertical"
android:orientation="horizontal"
android:background="@drawable/background_thumbnail_duration"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent">
<TextView
android:id="@+id/thumbnail_duration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:includeFontPadding="false"
android:paddingLeft="2dp"
android:paddingRight="2dp"
android:textColor="#FFFFFF"
android:textSize="12dp"
tools:text="0:00"
android:layout_gravity="center"
android:textStyle="normal" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
android:id="@+id/container_loader"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="6dp"
android:background="#DD000000"
android:visibility="gone"
android:orientation="vertical">
</LinearLayout>
<LinearLayout
android:id="@+id/container_locked"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="6dp"
android:background="#DD000000"
android:visibility="visible"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#AAAA"
android:layout_marginTop="50dp"
android:textSize="12dp"
android:layout_weight="1"
android:text="@string/locked_content_description"
android:textAlignment="center" />
<TextView
android:id="@+id/text_locked_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="-10dp"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:textSize="16dp"
android:layout_weight="1"
android:text="Lorem ipsum something something, and something more perhaps"
android:textAlignment="center" />
<TextView
android:id="@+id/text_browser_open"
android:textColor="#AAAA"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="13dp"
android:text="@string/tap_to_open_in_browser"
android:textAlignment="center" />
<TextView
android:id="@+id/text_locked_url"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="30dp"
android:maxLines="1"
android:ellipsize="end"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:textSize="12dp"
android:textColor="#828EFF"
android:layout_weight="1"
android:text="@string/unknown"
android:textAlignment="center" />
</LinearLayout>
</FrameLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="-6dp"
app:layout_constraintTop_toBottomOf="@id/player_container"
app:layout_constraintBottom_toBottomOf="parent"
android:orientation="vertical"
android:gravity="center_vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:gravity="top"
android:orientation="horizontal"
android:layout_gravity="center_vertical"
android:paddingBottom="5dp">
<com.futo.platformplayer.views.others.CreatorThumbnail
android:id="@+id/creator_thumbnail"
android:layout_width="32dp"
android:layout_height="32dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginStart="10dp"
android:layout_marginTop="10dp" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="@id/creator_thumbnail"
app:layout_constraintLeft_toRightOf="@id/creator_thumbnail"
app:layout_constraintRight_toLeftOf="@id/container_info"
android:orientation="vertical"
android:paddingStart="10dp">
<TextView
android:id="@+id/text_video_name"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textAlignment="viewStart"
android:layout_marginTop="-3dp"
android:textSize="14dp"
android:textColor="@color/white"
android:fontFamily="@font/inter_regular"
tools:text="I Thought FSD is Terrible in SNOW | 8 inch SNOW | FSD Beta 10.69.2.4"
android:maxLines="2" />
<TextView
android:id="@+id/text_channel_name"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:maxLines="1"
android:textSize="12dp"
android:textColor="@color/gray_e0"
android:fontFamily="@font/inter_extra_light"
tools:text="Two Minute Papers" />
<TextView
android:id="@+id/text_video_metadata"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:maxLines="1"
android:gravity="center_vertical"
android:textSize="12dp"
android:textColor="@color/gray_e0"
android:fontFamily="@font/inter_extra_light"
tools:text="57K views • 1 day ago" />
</LinearLayout>
<LinearLayout
android:id="@+id/container_info"
android:layout_width="wrap_content"
android:layout_height="30dp"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginEnd="6dp"
android:paddingLeft="10dp"
android:orientation="horizontal"
android:gravity="center_vertical">
<!--
<FrameLayout android:id="@+id/layout_downloaded"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginEnd="8dp">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitXY"
app:srcCompat="@drawable/download_for_offline" />
</FrameLayout> -->
<com.futo.platformplayer.views.platform.PlatformIndicator
android:id="@+id/thumbnail_platform"
android:layout_width="25dp"
android:layout_height="25dp"
android:scaleType="centerInside"
tools:src="@drawable/ic_peertube"/>
</LinearLayout>
<LinearLayout
android:id="@+id/container_buttons"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:orientation="horizontal"
android:paddingEnd="6dp">
<!--
<ImageButton
android:id="@+id/button_add_to_queue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="1dp"
android:paddingTop="7dp"
android:paddingStart="6dp"
android:paddingEnd="5dp"
android:paddingBottom="3dp"
app:srcCompat="@drawable/ic_queue_16dp"
android:background="@drawable/edit_text_background"
android:contentDescription="@string/add_to_queue" />
<LinearLayout
android:id="@+id/button_add_to"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="@drawable/edit_text_background"
android:layout_marginStart="4dp"
android:gravity="center_vertical"
android:padding="4dp">
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="4dp"
app:srcCompat="@drawable/ic_add_white_8dp"
android:layout_marginStart="4dp"
android:contentDescription="@string/options" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/options"
android:background="@color/transparent"
android:textSize="12dp"
android:textColor="@color/white"
android:fontFamily="@font/inter_light"
android:layout_marginEnd="4dp"/>
</LinearLayout> -->
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,317 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="115dp"
android:orientation="vertical">
<LinearLayout
android:id="@+id/item_video"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp">
<FrameLayout
android:id="@+id/player_container"
android:layout_width="178dp"
android:layout_height="100dp">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/image_video_thumbnail"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:contentDescription="@string/thumbnail"
app:shapeAppearanceOverlay="@style/roundedCorners_4dp"
app:srcCompat="@drawable/placeholder_video_thumbnail"
android:background="@drawable/video_thumbnail_outline" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--
<LinearLayout
android:id="@+id/thumbnail_live_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
android:layout_gravity="end"
android:paddingStart="2dp"
android:paddingEnd="2dp"
android:layout_marginEnd="4dp"
android:layout_marginBottom="4dp"
android:paddingTop="0dp"
android:gravity="center_vertical"
android:orientation="horizontal"
android:background="@drawable/background_thumbnail_live">
<TextView
android:id="@+id/thumbnail_live"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:includeFontPadding="false"
android:paddingLeft="2dp"
android:paddingRight="2dp"
android:textColor="#FFFFFF"
android:textSize="12dp"
android:text="@string/live"
android:layout_gravity="center"
android:textStyle="normal" />
</LinearLayout> -->
<!--
<LinearLayout
android:id="@+id/thumbnail_duration_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
android:layout_gravity="end"
android:paddingStart="2dp"
android:paddingEnd="2dp"
android:layout_marginEnd="4dp"
android:layout_marginBottom="4dp"
android:paddingTop="0dp"
android:gravity="center_vertical"
android:orientation="horizontal"
android:background="@drawable/background_thumbnail_duration">
<TextView
android:id="@+id/thumbnail_duration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:includeFontPadding="false"
android:paddingLeft="2dp"
android:paddingRight="2dp"
android:textColor="#FFFFFF"
android:textSize="12dp"
tools:text="0:00"
android:layout_gravity="center"
android:textStyle="normal" />
</LinearLayout> -->
<!--
<FrameLayout android:id="@+id/layout_downloaded"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_alignParentEnd="true"
android:layout_alignParentTop="true"
android:background="@drawable/background_pill_black"
android:layout_marginTop="2dp"
android:layout_marginEnd="2dp">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="fitXY"
app:srcCompat="@drawable/download_for_offline" />
</FrameLayout> -->
</RelativeLayout>
<LinearLayout
android:id="@+id/container_loader"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#BB000000"
android:visibility="gone"
android:orientation="vertical" />
<LinearLayout
android:id="@+id/container_locked"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#BB000000"
android:visibility="visible"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginTop="10dp"
android:textSize="10dp"
android:text="@string/locked_content_description"
android:textAlignment="center" />
<TextView
android:id="@+id/text_locked_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="12dp"
android:maxLines="3"
android:ellipsize="end"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:layout_weight="1"
android:text="Lorem ipsum something something, and something more perhaps"
android:textAlignment="center" />
<!--
<TextView
android:id="@+id/text_browser_open"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="10dp"
android:layout_weight="1"
android:text="@string/tap_to_open_in_browser"
android:textAlignment="center" /> -->
<TextView
android:id="@+id/text_locked_url"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:maxLines="1"
android:ellipsize="end"
android:textColor="#828EFF"
android:layout_weight="1"
android:textSize="10dp"
android:text="@string/unknown"
android:textAlignment="center" />
</LinearLayout>
</FrameLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="6dp"
android:layout_marginEnd="6dp">
<!--
<ImageButton
android:id="@+id/button_add_to_queue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="1dp"
android:paddingTop="7dp"
android:paddingStart="6dp"
android:paddingEnd="5dp"
android:paddingBottom="3dp"
app:srcCompat="@drawable/ic_queue_16dp"
android:background="@drawable/edit_text_background"
android:contentDescription="@string/add_to_queue"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
<LinearLayout
android:id="@+id/button_add_to"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="@drawable/edit_text_background"
android:layout_marginStart="4dp"
android:gravity="center_vertical"
android:padding="4dp"
app:layout_constraintLeft_toRightOf="@id/button_add_to_queue"
app:layout_constraintBottom_toBottomOf="parent">
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="4dp"
app:srcCompat="@drawable/ic_add_white_8dp"
android:layout_marginStart="4dp"
android:contentDescription="@string/options" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/options"
android:background="@color/transparent"
android:textSize="12dp"
android:textColor="@color/white"
android:fontFamily="@font/inter_light"
android:layout_marginEnd="4dp"/>
</LinearLayout> -->
<TextView
android:id="@+id/text_video_name"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="2dp"
android:layout_weight="1"
android:gravity="center_vertical"
android:textSize="13dp"
android:textColor="@color/white"
android:fontFamily="@font/inter_light"
tools:text="Legendary grant recipient: Marvin Wißfeld of MicroG Very loong title"
android:maxLines="2"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent" />
<ImageView
android:id="@+id/image_channel_thumbnail"
android:layout_width="28dp"
android:layout_height="28dp"
android:background="@drawable/rounded_outline"
android:contentDescription="@string/channel_image"
tools:src="@drawable/placeholder_channel_thumbnail"
android:clipToOutline="true"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_video_name" />
<TextView
android:id="@+id/text_channel_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_vertical"
android:maxLines="1"
android:ellipsize="end"
android:textSize="10dp"
android:textColor="@color/gray_e0"
android:fontFamily="@font/inter_extra_light"
tools:text="Two Minute Papers"
app:layout_constrainedWidth="true"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintLeft_toRightOf="@id/image_channel_thumbnail"
app:layout_constraintRight_toLeftOf="@id/image_neopass_channel"
app:layout_constraintTop_toBottomOf="@id/text_video_name"
android:layout_marginStart="4dp" />
<ImageView
android:id="@+id/image_neopass_channel"
android:layout_width="10dp"
android:layout_height="10dp"
android:contentDescription="@string/neopass_channel"
app:srcCompat="@drawable/neopass"
app:layout_constraintLeft_toRightOf="@id/text_channel_name"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@id/text_channel_name"
app:layout_constraintBottom_toBottomOf="@id/text_channel_name"
android:layout_marginStart="4dp"
android:visibility="gone"/>
<TextView
android:id="@+id/text_video_metadata"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:maxLines="1"
android:gravity="center_vertical"
android:textSize="10dp"
android:textColor="@color/gray_e0"
android:fontFamily="@font/inter_extra_light"
tools:text="57K views • 1 day ago"
app:layout_constraintLeft_toRightOf="@id/image_channel_thumbnail"
app:layout_constraintTop_toBottomOf="@id/text_channel_name"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginStart="4dp"/>
<com.futo.platformplayer.views.platform.PlatformIndicator
android:id="@+id/thumbnail_platform"
android:layout_width="20dp"
android:layout_height="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_margin="4dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
</LinearLayout>

View file

@ -60,6 +60,8 @@
<string name="view_all">View all</string> <string name="view_all">View all</string>
<string name="creators">Creators</string> <string name="creators">Creators</string>
<string name="enabled">Enabled</string> <string name="enabled">Enabled</string>
<string name="keep_screen_on">Keep screen on</string>
<string name="keep_screen_on_while_casting">Keep screen on while casting</string>
<string name="discover">Discover</string> <string name="discover">Discover</string>
<string name="find_new_video_sources_to_add">Find new video sources to add</string> <string name="find_new_video_sources_to_add">Find new video sources to add</string>
<string name="these_sources_have_been_disabled">These sources have been disabled</string> <string name="these_sources_have_been_disabled">These sources have been disabled</string>
@ -80,6 +82,7 @@
<string name="developer">Developer</string> <string name="developer">Developer</string>
<string name="remove_historical_suggestion">Remove historical suggestion</string> <string name="remove_historical_suggestion">Remove historical suggestion</string>
<string name="comments">Comments</string> <string name="comments">Comments</string>
<string name="comments_description">The comment section underneath content</string>
<string name="merchandise">Merchandise</string> <string name="merchandise">Merchandise</string>
<string name="reached_the_end_of_the_playlist">Reached the end of the playlist</string> <string name="reached_the_end_of_the_playlist">Reached the end of the playlist</string>
<string name="the_playlist_will_restart_after_the_video_is_finished">The playlist will restart after the video is finished</string> <string name="the_playlist_will_restart_after_the_video_is_finished">The playlist will restart after the video is finished</string>
@ -219,6 +222,7 @@
<string name="construction">CONSTRUCTION</string> <string name="construction">CONSTRUCTION</string>
<string name="disable">Disable</string> <string name="disable">Disable</string>
<string name="the_following_content_cannot_be_opened_in_grayjay_due_to_a_missing_plugin">The following content cannot be opened in Grayjay due to a missing plugin.</string> <string name="the_following_content_cannot_be_opened_in_grayjay_due_to_a_missing_plugin">The following content cannot be opened in Grayjay due to a missing plugin.</string>
<string name="locked_content_description">This content is locked</string>
<string name="unknown">Unknown</string> <string name="unknown">Unknown</string>
<string name="tap_to_open_in_browser">Tap to open in browser</string> <string name="tap_to_open_in_browser">Tap to open in browser</string>
<string name="missing_plugin">Missing Plugin</string> <string name="missing_plugin">Missing Plugin</string>
@ -349,6 +353,7 @@
<string name="preferred_metered_quality">Preferred Metered Quality</string> <string name="preferred_metered_quality">Preferred Metered Quality</string>
<string name="preferred_preview_quality">Preferred Preview Quality</string> <string name="preferred_preview_quality">Preferred Preview Quality</string>
<string name="primary_language">Primary Language</string> <string name="primary_language">Primary Language</string>
<string name="default_comment_section">Default Comment Section</string>
<string name="reinstall_embedded_plugins">Reinstall Embedded Plugins</string> <string name="reinstall_embedded_plugins">Reinstall Embedded Plugins</string>
<string name="remove_cached_version">Remove Cached Version</string> <string name="remove_cached_version">Remove Cached Version</string>
<string name="remove_the_last_downloaded_version">Remove the last downloaded version</string> <string name="remove_the_last_downloaded_version">Remove the last downloaded version</string>
@ -789,6 +794,10 @@
<item>60</item> <item>60</item>
<item>120</item> <item>120</item>
</string-array> </string-array>
<string-array name="comment_sections">
<item>Polycentric</item>
<item>Platform</item>
</string-array>
<string-array name="audio_languages"> <string-array name="audio_languages">
<item>English</item> <item>English</item>
<item>Spanish</item> <item>Spanish</item>

@ -1 +1 @@
Subproject commit 4f89b4072f4473ff0ffac1711023dffd20f0a868 Subproject commit 8f10daba1ef9cbcd99f3c640d86808f8c94aa84a

@ -1 +1 @@
Subproject commit 9e26b7032e64ed03315a8e75d2174cb4253030d1 Subproject commit 339b44e9f00521ab4cfe755a343fd9e6e5338d04