Reduced amount of warnings.

This commit is contained in:
Koen 2023-12-11 11:51:43 +01:00
parent 150a7d5006
commit fa12f8277c
172 changed files with 1502 additions and 1603 deletions

View File

@ -7,8 +7,8 @@
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:name="com.android.alarm.permission.SET_ALARM"/>
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"/>

View File

@ -169,7 +169,7 @@ private fun parseHextet(ipString: String, start: Int, end: Int): Short {
var hextet = 0
for (i in start until end) {
hextet = hextet shl 4
hextet = hextet or ipString[i].digitToIntOrNull(16)!! ?: -1
hextet = hextet or ipString[i].digitToIntOrNull(16)!!
}
return hextet.toShort()
}

View File

@ -27,7 +27,7 @@ fun <R> V8Value?.orDefault(default: R, handler: (V8Value)->R): R {
inline fun <reified T> V8Value.expectOrThrow(config: IV8PluginConfig, contextName: String): T {
if(this !is T)
throw ScriptImplementationException(config, "Expected ${contextName} to be of type ${T::class.simpleName}, but found ${this::class.simpleName}");
return this as T;
return this;
}
//Singles

View File

@ -2,15 +2,9 @@ package com.futo.platformplayer
import android.content.Context
import android.webkit.CookieManager
import androidx.lifecycle.lifecycleScope
import androidx.work.Constraints
import androidx.work.Data
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.PeriodicWorkRequest
import androidx.work.WorkManager
import androidx.work.WorkerParameters
import com.caoccao.javet.values.primitive.V8ValueInteger
import com.caoccao.javet.values.primitive.V8ValueString
import com.futo.platformplayer.activities.DeveloperActivity
@ -36,7 +30,6 @@ import com.futo.platformplayer.states.StatePlatform
import com.futo.platformplayer.states.StateSubscriptions
import com.futo.platformplayer.stores.FragmentedStorage
import com.futo.platformplayer.stores.FragmentedStorageFileJson
import com.futo.platformplayer.stores.db.types.DBHistory
import com.futo.platformplayer.views.fields.ButtonField
import com.futo.platformplayer.views.fields.FieldForm
import com.futo.platformplayer.views.fields.FormField
@ -44,11 +37,12 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.*
import kotlinx.serialization.json.*
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.time.OffsetDateTime
import java.util.UUID
import java.util.concurrent.TimeUnit
import java.util.stream.IntStream.range
import kotlin.system.measureTimeMillis
@ -109,14 +103,14 @@ class SettingsDev : FragmentedStorageFileJson() {
StateApp.instance.scope.launch(Dispatchers.IO) {
try {
val subsCache =
StateSubscriptions.instance.getSubscriptionsFeedWithExceptions(cacheScope = this)?.first;
StateSubscriptions.instance.getSubscriptionsFeedWithExceptions(cacheScope = this).first;
var total = 0;
var page = 0;
var lastToast = System.currentTimeMillis();
while(subsCache!!.hasMorePages() && total < 5000) {
subsCache!!.nextPage();
total += subsCache!!.getResults().size;
while(subsCache.hasMorePages() && total < 5000) {
subsCache.nextPage();
total += subsCache.getResults().size;
page++;
if(page % 10 == 0)
@ -174,9 +168,9 @@ class SettingsDev : FragmentedStorageFileJson() {
var total = 0;
var page = 0;
var lastToast = System.currentTimeMillis();
while(subsCache!!.hasMorePages() && total < 5000) {
subsCache!!.nextPage();
total += subsCache!!.getResults().size;
while(subsCache.hasMorePages() && total < 5000) {
subsCache.nextPage();
total += subsCache.getResults().size;
page++;
for(item in subsCache.getResults().filterIsInstance<IPlatformVideo>()) {
@ -375,9 +369,9 @@ class SettingsDev : FragmentedStorageFileJson() {
@FormField(R.string.getHome, FieldForm.BUTTON, R.string.attempts_to_fetch_2_pages_from_getHome, 2)
fun testV8Home() {
runTestPlugin(_currentPlugin) {
var home: IPager<IPlatformContent>? = null;
var resultPage1: String = "";
var resultPage2: String = "";
var home: IPager<IPlatformContent>?;
val resultPage1: String;
val resultPage2: String;
val page1Time = measureTimeMillis {
home = it.getHome();
val results = home!!.getResults();

View File

@ -22,20 +22,25 @@ import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.models.Playlist
import com.futo.platformplayer.models.Subscription
import com.futo.platformplayer.parsers.HLS
import com.futo.platformplayer.states.*
import com.futo.platformplayer.states.StateApp
import com.futo.platformplayer.states.StateDownloads
import com.futo.platformplayer.states.StateMeta
import com.futo.platformplayer.states.StatePlatform
import com.futo.platformplayer.states.StatePlayer
import com.futo.platformplayer.states.StatePlaylists
import com.futo.platformplayer.views.LoaderView
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuFilters
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuGroup
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuItem
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuOverlay
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuTextInput
import com.futo.platformplayer.views.pills.RoundButton
import com.futo.platformplayer.views.pills.RoundButtonGroup
import com.futo.platformplayer.views.overlays.slideup.*
import com.futo.platformplayer.views.video.FutoVideoPlayerBase
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.lang.IllegalStateException
class UISlideOverlays {
companion object {
@ -242,7 +247,7 @@ class UISlideOverlays {
val audioSources = if(descriptor is VideoUnMuxedSourceDescriptor) descriptor.audioSources else null;
val subtitleSources = video.subtitles;
if(videoSources.size == 0 && (audioSources?.size ?: 0) == 0) {
if(videoSources.isEmpty() && (audioSources?.size ?: 0) == 0) {
UIDialogs.toast(container.context.getString(R.string.no_downloads_available), false);
return null;
}
@ -263,24 +268,30 @@ class UISlideOverlays {
videoSources
.filter { it.isDownloadable() }
.map {
if (it is IVideoUrlSource) {
when (it) {
is IVideoUrlSource -> {
SlideUpMenuItem(container.context, R.drawable.ic_movie, it.name, "${it.width}x${it.height}", it, {
selectedVideo = it
menu?.selectOption(videoSources, it);
if(selectedAudio != null || !requiresAudio)
menu?.setOk(container.context.getString(R.string.download));
}, false)
} else if (it is IHLSManifestSource) {
}
is IHLSManifestSource -> {
SlideUpMenuItem(container.context, R.drawable.ic_movie, it.name, "HLS", it, {
showHlsPicker(video, it, it.url, container)
}, false)
} else {
}
else -> {
throw Exception("Unhandled source type")
}
}
}).flatten().toList()
));
if(Settings.instance.downloads.getDefaultVideoQualityPixels() > 0 && videoSources.size > 0) {
if(Settings.instance.downloads.getDefaultVideoQualityPixels() > 0 && videoSources.isNotEmpty()) {
//TODO: Add HLS support here
selectedVideo = VideoHelper.selectBestVideoSource(
videoSources.filter { it is IVideoUrlSource && it.isDownloadable() }.asIterable(),
@ -289,30 +300,30 @@ class UISlideOverlays {
) as IVideoUrlSource;
}
audioSources?.let { audioSources ->
if (audioSources != null) {
items.add(SlideUpMenuGroup(container.context, container.context.getString(R.string.audio), audioSources, audioSources
.filter { VideoHelper.isDownloadable(it) }
.map {
if (it is IAudioUrlSource) {
when (it) {
is IAudioUrlSource -> {
SlideUpMenuItem(container.context, R.drawable.ic_music, it.name, "${it.bitrate}", it, {
selectedAudio = it
menu?.selectOption(audioSources, it);
menu?.setOk(container.context.getString(R.string.download));
}, false);
} else if (it is IHLSManifestAudioSource) {
}
is IHLSManifestAudioSource -> {
SlideUpMenuItem(container.context, R.drawable.ic_movie, it.name, "HLS Audio", it, {
showHlsPicker(video, it, it.url, container)
}, false)
} else {
}
else -> {
throw Exception("Unhandled source type")
}
}
}));
val asources = audioSources;
val preferredAudioSource = VideoHelper.selectBestAudioSource(asources.asIterable(),
FutoVideoPlayerBase.PREFERED_AUDIO_CONTAINERS,
Settings.instance.playback.getPrimaryLanguage(container.context),
if(Settings.instance.downloads.isHighBitrateDefault()) 99999999 else 1);
menu?.selectOption(asources, preferredAudioSource);
//TODO: Add HLS support here
selectedAudio = VideoHelper.selectBestAudioSource(audioSources.filter { it is IAudioUrlSource && it.isDownloadable() }.asIterable(),
@ -321,10 +332,8 @@ class UISlideOverlays {
if(Settings.instance.downloads.isHighBitrateDefault()) 9999999 else 1) as IAudioUrlSource?;
}
//ContentResolver is required for subtitles..
if(contentResolver != null && subtitleSources.isNotEmpty()) {
items.add(SlideUpMenuGroup(container.context, container.context.getString(R.string.subtitles), subtitleSources, subtitleSources
.map {
items.add(SlideUpMenuGroup(container.context, container.context.getString(R.string.subtitles), subtitleSources, subtitleSources.map {
SlideUpMenuItem(container.context, R.drawable.ic_edit, it.name, "", it, {
if (selectedSubtitle == it) {
selectedSubtitle = null;
@ -334,7 +343,8 @@ class UISlideOverlays {
menu?.selectOption(subtitleSources, it);
}
}, false);
}));
})
);
}
menu = SlideUpMenuOverlay(container.context, container, container.context.getString(R.string.download_video), null, true, items);
@ -645,10 +655,11 @@ class UISlideOverlays {
val visible = buttonGroup.getVisibleButtons().filter { !ignoreTags.contains(it.tagRef) };
val hidden = buttonGroup.getInvisibleButtons().filter { !ignoreTags.contains(it.tagRef) };
val views = arrayOf(hidden
val views = arrayOf(
hidden
.map { btn -> SlideUpMenuItem(container.context, btn.iconResource, btn.text.text.toString(), "", "", {
btn.handler?.invoke(btn);
}, true) as View }.toTypedArray() ?: arrayOf(),
}, true) as View }.toTypedArray(),
arrayOf(SlideUpMenuItem(container.context, R.drawable.ic_pin, container.context.getString(R.string.change_pins), container.context.getString(R.string.decide_which_buttons_should_be_pinned), "", {
showOrderOverlay(container, container.context.getString(R.string.select_your_pins_in_order), (visible + hidden).map { Pair(it.text.text.toString(), it.tagRef!!) }) {
val selected = it

View File

@ -143,6 +143,7 @@ fun InputStream.copyToOutputStream(inputStreamLength: Long, outputStream: Output
}
}
@Suppress("DEPRECATION")
fun Activity.setNavigationBarColorAndIcons() {
window.navigationBarColor = ContextCompat.getColor(this, android.R.color.black);

View File

@ -5,13 +5,20 @@ import android.content.Intent
import android.graphics.drawable.Animatable
import android.os.Bundle
import android.view.View
import android.widget.*
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.ScrollView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import com.futo.platformplayer.*
import com.futo.platformplayer.R
import com.futo.platformplayer.UIDialogs
import com.futo.platformplayer.api.http.ManagedHttpClient
import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.setNavigationBarColorAndIcons
import com.futo.platformplayer.states.StateApp
import com.futo.platformplayer.states.StatePlatform
import com.futo.platformplayer.states.StatePlugins
@ -194,7 +201,7 @@ class AddSourceActivity : AppCompatActivity() {
config.allowUrls, true)
)
val pastelRed = resources.getColor(R.color.pastel_red);
val pastelRed = ContextCompat.getColor(this, R.color.pastel_red);
for(warning in config.getWarnings(script))
_sourceWarnings.addView(

View File

@ -11,7 +11,6 @@ import com.futo.platformplayer.*
import com.futo.platformplayer.states.StateApp
import com.futo.platformplayer.views.buttons.BigButton
import com.google.zxing.integration.android.IntentIntegrator
import com.journeyapps.barcodescanner.CaptureActivity
class AddSourceOptionsActivity : AppCompatActivity() {
lateinit var _buttonBack: ImageButton;

View File

@ -26,7 +26,7 @@ class DeveloperActivity : AppCompatActivity() {
_form = findViewById(R.id.settings_form);
_form.fromObject(SettingsDev.instance);
_form.onChanged.subscribe { field, value ->
_form.onChanged.subscribe { _, _ ->
_form.setObjectValues();
SettingsDev.instance.save();
};

View File

@ -2,7 +2,6 @@ package com.futo.platformplayer.activities
import android.content.Intent
import androidx.activity.result.ActivityResult
import androidx.activity.result.ActivityResultLauncher
interface IWithResultLauncher {
fun launchForResult(intent: Intent, code: Int, handler: (ActivityResult)->Unit);

View File

@ -3,24 +3,22 @@ package com.futo.platformplayer.activities
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.webkit.ConsoleMessage
import android.webkit.CookieManager
import android.webkit.WebChromeClient
import android.webkit.WebView
import android.widget.ImageButton
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import com.futo.platformplayer.*
import com.futo.platformplayer.R
import com.futo.platformplayer.api.media.platforms.js.SourceAuth
import com.futo.platformplayer.api.media.platforms.js.SourcePluginAuthConfig
import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.others.LoginWebViewClient
import com.futo.platformplayer.setNavigationBarColorAndIcons
import com.futo.platformplayer.states.StateApp
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
@ -102,7 +100,7 @@ class LoginActivity : AppCompatActivity() {
override fun finish() {
lifecycleScope.launch(Dispatchers.Main) {
_webView?.loadUrl("about:blank");
_webView.loadUrl("about:blank");
}
_callback?.let {
_callback = null;

View File

@ -26,7 +26,6 @@ import androidx.lifecycle.lifecycleScope
import com.futo.platformplayer.*
import com.futo.platformplayer.casting.StateCasting
import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.dialogs.ConnectCastingDialog
import com.futo.platformplayer.fragment.mainactivity.bottombar.MenuBottomBarFragment
import com.futo.platformplayer.fragment.mainactivity.main.*
import com.futo.platformplayer.fragment.mainactivity.topbar.AddTopBarFragment
@ -44,7 +43,6 @@ import com.futo.platformplayer.stores.v2.ManagedStore
import com.google.gson.JsonParser
import com.google.zxing.integration.android.IntentIntegrator
import kotlinx.coroutines.*
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import java.io.File
import java.io.PrintWriter
@ -649,7 +647,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
if(file.lowercase().endsWith(".json") || mime == "application/json") {
var recon = String(data);
if(!recon.trim().startsWith("["))
return handleUnknownJson(file, recon);
return handleUnknownJson(recon);
val reconLines = Json.decodeFromString<List<String>>(recon);
recon = reconLines.joinToString("\n");
@ -671,7 +669,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
if(file.lowercase().endsWith(".json")) {
val recon = String(readSharedFile(file));
if(!recon.startsWith("["))
return handleUnknownJson(file, recon);
return handleUnknownJson(recon);
Logger.i(TAG, "Opened shared playlist reconstruction\n${recon}");
handleReconstruction(recon);
@ -723,7 +721,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
}
return false;
}
fun handleUnknownJson(name: String?, json: String): Boolean {
fun handleUnknownJson(json: String): Boolean {
val context = this;
@ -832,7 +830,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
val isStop: Boolean = lifecycle.currentState == Lifecycle.State.CREATED;
Logger.v(TAG, "onPictureInPictureModeChanged isInPictureInPictureMode=$isInPictureInPictureMode isStop=$isStop")
_fragVideoDetail?.onPictureInPictureModeChanged(isInPictureInPictureMode, isStop, newConfig);
_fragVideoDetail.onPictureInPictureModeChanged(isInPictureInPictureMode, isStop, newConfig);
Logger.v(TAG, "onPictureInPictureModeChanged Ready");
}
@ -889,7 +887,6 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
transaction = transaction.replace(R.id.fragment_main, segment);
val extraBottomDP = if(_fragVideoDetail.state == VideoDetailFragment.State.MINIMIZED) HEIGHT_VIDEO_MINIMIZED_DP else 0f
if (segment.hasBottomBar) {
if (!fragCurrent.hasBottomBar)
transaction = transaction.show(_fragBotBarMenu);
@ -899,8 +896,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
transaction = transaction.hide(_fragBotBarMenu);
}
transaction.commitNow();
}
else {
} else {
//Special cases
if(segment is VideoDetailFragment) {
_fragContainerVideoDetail.visibility = View.VISIBLE;

View File

@ -8,20 +8,19 @@ import android.widget.ImageButton
import android.widget.LinearLayout
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import com.futo.platformplayer.R
import com.futo.platformplayer.UIDialogs
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.setNavigationBarColorAndIcons
import com.futo.platformplayer.states.StateApp
import com.futo.platformplayer.states.StatePolycentric
import com.futo.polycentric.core.*
import com.futo.polycentric.core.KeyPair
import com.futo.polycentric.core.Process
import com.futo.polycentric.core.ProcessSecret
import com.futo.polycentric.core.SignedEvent
import com.futo.polycentric.core.Store
import com.futo.polycentric.core.base64UrlToByteArray
import com.google.zxing.integration.android.IntentIntegrator
import com.journeyapps.barcodescanner.CaptureActivity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import userpackage.Protocol
import userpackage.Protocol.ExportBundle

View File

@ -17,7 +17,6 @@ import androidx.lifecycle.lifecycleScope
import com.bumptech.glide.Glide
import com.futo.platformplayer.R
import com.futo.platformplayer.UIDialogs
import com.futo.platformplayer.dialogs.CommentDialog
import com.futo.platformplayer.dp
import com.futo.platformplayer.fullyBackfillServersAnnounceExceptions
import com.futo.platformplayer.images.GlideHelper.Companion.crossfade
@ -30,7 +29,6 @@ import com.futo.platformplayer.views.buttons.BigButton
import com.futo.polycentric.core.Store
import com.futo.polycentric.core.Synchronization
import com.futo.polycentric.core.SystemState
import com.futo.polycentric.core.toURLInfoDataLink
import com.futo.polycentric.core.toURLInfoSystemLinkUrl
import com.github.dhaval2404.imagepicker.ImagePicker
import kotlinx.coroutines.Dispatchers
@ -250,7 +248,7 @@ class PolycentricProfileActivity : AppCompatActivity() {
}
private fun getMimeType(contentResolver: ContentResolver, uri: Uri): String? {
var mimeType: String? = null;
var mimeType: String?;
// Try to get MIME type from the content URI
mimeType = contentResolver.getType(uri);

View File

@ -49,7 +49,7 @@ class SettingsActivity : AppCompatActivity(), IWithResultLauncher {
_loaderView = findViewById(R.id.loader);
overlay = findViewById(R.id.overlay_container);
_form.onChanged.subscribe { field, value ->
_form.onChanged.subscribe { field, _ ->
Logger.i("SettingsActivity", "Setting [${field.field?.name}] changed, saving");
_form.setObjectValues();
Settings.instance.save();

View File

@ -13,8 +13,6 @@ import okhttp3.Response
import okhttp3.ResponseBody
import okhttp3.WebSocket
import okhttp3.WebSocketListener
import java.util.Dictionary
import java.util.concurrent.TimeUnit
import kotlin.system.measureTimeMillis
open class ManagedHttpClient {
@ -60,7 +58,7 @@ open class ManagedHttpClient {
val requestBuilder: okhttp3.Request.Builder = okhttp3.Request.Builder()
.url(url);
if(user_agent != null && !user_agent.isEmpty() && !headers.any { it.key.lowercase() == "user-agent" })
if(user_agent.isNotEmpty() && !headers.any { it.key.lowercase() == "user-agent" })
requestBuilder.addHeader("User-Agent", user_agent)
for (pair in headers.entries)
@ -137,7 +135,7 @@ open class ManagedHttpClient {
val requestBuilder: okhttp3.Request.Builder = okhttp3.Request.Builder()
.method(request.method, requestBody)
.url(request.url);
if(user_agent != null && !user_agent.isEmpty() && !request.headers.any { it.key.lowercase() == "user-agent" })
if(user_agent.isNotEmpty() && !request.headers.any { it.key.lowercase() == "user-agent" })
requestBuilder.addHeader("User-Agent", user_agent)
for (pair in request.headers.entries)
@ -148,7 +146,7 @@ open class ManagedHttpClient {
val time = measureTimeMillis {
val call = client.newCall(requestBuilder.build());
request.onCallCreated?.emit(call);
request.onCallCreated.emit(call);
response = call.execute()
resp = Response(
response.code,

View File

@ -1,11 +1,11 @@
package com.futo.platformplayer.api.http.server
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.api.http.ManagedHttpClient
import com.futo.platformplayer.api.http.server.exceptions.EmptyRequestException
import com.futo.platformplayer.api.http.server.handlers.HttpFuntionHandler
import com.futo.platformplayer.api.http.server.handlers.HttpHandler
import com.futo.platformplayer.api.http.server.handlers.HttpOptionsAllowHandler
import com.futo.platformplayer.logging.Logger
import java.io.BufferedInputStream
import java.io.OutputStream
import java.lang.reflect.Field
@ -14,11 +14,10 @@ import java.net.InetAddress
import java.net.NetworkInterface
import java.net.ServerSocket
import java.net.Socket
import java.util.*
import java.util.UUID
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.stream.IntStream.range
import kotlin.collections.HashMap
class ManagedHttpServer(private val _requestedPort: Int = 0) {
private val _client : ManagedHttpClient = ManagedHttpClient();
@ -212,13 +211,13 @@ class ManagedHttpServer(private val _requestedPort: Int = 0) {
addHandler(HttpFuntionHandler("GET", getMethod.second.path) { getMethod.first.invoke(obj, it) }).apply {
if(!getMethod.second.contentType.isEmpty())
this.withContentType(getMethod.second.contentType);
}.withContentType(getMethod.second.contentType ?: "");
}.withContentType(getMethod.second.contentType);
for(postMethod in postMethods)
if(postMethod.first.parameterTypes.firstOrNull() == HttpContext::class.java && postMethod.first.parameterCount == 1)
addHandler(HttpFuntionHandler("POST", postMethod.second.path) { postMethod.first.invoke(obj, it) }).apply {
if(!postMethod.second.contentType.isEmpty())
this.withContentType(postMethod.second.contentType);
}.withContentType(postMethod.second.contentType ?: "");
}.withContentType(postMethod.second.contentType);
for(getField in getFields) {
getField.first.isAccessible = true;
@ -232,13 +231,13 @@ class ManagedHttpServer(private val _requestedPort: Int = 0) {
}
else
it.respondCode(204);
}).withContentType(getField.second.contentType ?: "");
}).withContentType(getField.second.contentType);
}
}
private fun keepAliveLoop(requestReader: BufferedInputStream, responseStream: OutputStream, requestId: String, handler: (HttpContext)->Unit) {
val stopCount = _stopCount;
var keepAlive = false;
var keepAlive: Boolean;
var requestsMax = 0;
var requestsTotal = 0;
do {
@ -288,11 +287,13 @@ class ManagedHttpServer(private val _requestedPort: Int = 0) {
for (intf in NetworkInterface.getNetworkInterfaces()) {
for (addr in intf.inetAddresses) {
if (!addr.isLoopbackAddress) {
val ipString: String = addr.hostAddress;
val isIPv4 = ipString.indexOf(':') < 0;
if (!isIPv4)
continue;
addresses.add(addr);
val ipString: String = addr.hostAddress ?: continue
val isIPv4 = ipString.indexOf(':') < 0
if (!isIPv4) {
continue
}
addresses.add(addr)
}
}
}

View File

@ -1,6 +1,3 @@
package com.futo.platformplayer.api.http.server.exceptions
import java.net.SocketTimeoutException
import java.util.concurrent.TimeoutException
class EmptyRequestException(msg: String) : Exception(msg) {}

View File

@ -10,12 +10,9 @@ import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails
import com.futo.platformplayer.api.media.models.live.ILiveChatWindowDescriptor
import com.futo.platformplayer.api.media.models.live.IPlatformLiveEvent
import com.futo.platformplayer.api.media.models.playback.IPlaybackTracker
import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylist
import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylistDetails
import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails
import com.futo.platformplayer.api.media.structures.IPager
import com.futo.platformplayer.models.ImageVariable
import com.futo.platformplayer.models.Playlist
/**
* A temporary class that caches video results
@ -44,7 +41,6 @@ class CachedPlatformClient : IPlatformClient {
var result = _cache.get(url);
if(result == null) {
result = _client.getContentDetails(url);
if (result != null)
_cache.put(url, result);
}
return result;

View File

@ -10,11 +10,9 @@ import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails
import com.futo.platformplayer.api.media.models.live.ILiveChatWindowDescriptor
import com.futo.platformplayer.api.media.models.live.IPlatformLiveEvent
import com.futo.platformplayer.api.media.models.playback.IPlaybackTracker
import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylist
import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylistDetails
import com.futo.platformplayer.api.media.structures.IPager
import com.futo.platformplayer.models.ImageVariable
import com.futo.platformplayer.models.Playlist
/**
* A client for a specific platform

View File

@ -9,7 +9,6 @@ import com.caverock.androidsvg.SVG
import com.futo.platformplayer.api.http.ManagedHttpClient
import com.futo.platformplayer.api.media.models.live.IPlatformLiveEvent
import com.futo.platformplayer.api.media.models.live.LiveEventComment
import com.futo.platformplayer.api.media.models.live.LiveEventDonation
import com.futo.platformplayer.api.media.models.live.LiveEventEmojis
import com.futo.platformplayer.api.media.platforms.js.models.JSLiveEventPager
import com.futo.platformplayer.api.media.structures.IPager
@ -195,7 +194,7 @@ class LiveChatManager {
fun getEmojiDrawable(emoji: String, cb: (drawable: Drawable?)->Unit) {
var drawable: Drawable? = null;
var url: String? = null;
var url: String?;
synchronized(_cache_lock) {
url = _cache_urls[emoji];
if(url != null)

View File

@ -20,7 +20,7 @@ class PlatformMultiClientPool {
val pool = synchronized(_clientPools) {
if(!_clientPools.containsKey(parentClient))
_clientPools[parentClient] = PlatformClientPool(parentClient, _name).apply {
this.onDead.subscribe { client, pool ->
this.onDead.subscribe { _, pool ->
synchronized(_clientPools) {
if(_clientPools[parentClient] == pool)
_clientPools.remove(parentClient);

View File

@ -64,7 +64,6 @@ class FilterGroup(
val isMultiSelect: Boolean,
val id: String? = null
) {
@kotlinx.serialization.Transient
val idOrName: String get() = id ?: name;
companion object {

View File

@ -1,31 +1,23 @@
package com.futo.platformplayer.api.media.models.live
import com.caoccao.javet.values.V8Value
import com.caoccao.javet.values.reference.V8ValueObject
import com.futo.platformplayer.api.media.models.ratings.IRating
import com.futo.platformplayer.api.media.models.ratings.RatingLikeDislikes
import com.futo.platformplayer.api.media.models.ratings.RatingLikes
import com.futo.platformplayer.api.media.models.ratings.RatingScaler
import com.futo.platformplayer.api.media.models.ratings.RatingType
import com.futo.platformplayer.engine.IV8PluginConfig
import com.futo.platformplayer.getOrThrow
import com.futo.platformplayer.orDefault
interface IPlatformLiveEvent {
val type : LiveEventType;
companion object {
fun fromV8(config: IV8PluginConfig, obj: V8ValueObject, contextName: String = "Unknown") : IPlatformLiveEvent {
val contextName = "LiveEvent";
val type = LiveEventType.fromInt(obj.getOrThrow<Int>(config, "type", contextName));
return when(type) {
fun fromV8(config: IV8PluginConfig, obj: V8ValueObject, contextName: String = "LiveEvent") : IPlatformLiveEvent {
val t = LiveEventType.fromInt(obj.getOrThrow<Int>(config, "type", contextName));
return when(t) {
LiveEventType.COMMENT -> LiveEventComment.fromV8(config, obj);
LiveEventType.EMOJIS -> LiveEventEmojis.fromV8(config, obj);
LiveEventType.DONATION -> LiveEventDonation.fromV8(config, obj);
LiveEventType.VIEWCOUNT -> LiveEventViewCount.fromV8(config, obj);
LiveEventType.RAID -> LiveEventRaid.fromV8(config, obj);
else -> throw NotImplementedError("Unknown type ${type}");
else -> throw NotImplementedError("Unknown type $t");
}
}
}

View File

@ -1,9 +1,6 @@
package com.futo.platformplayer.api.media.models.playlists
import com.futo.platformplayer.api.media.models.Thumbnails
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
import com.futo.platformplayer.api.media.structures.IPager
interface IPlatformPlaylist : IPlatformContent {
val thumbnail: String?;

View File

@ -2,10 +2,6 @@ package com.futo.platformplayer.api.media.models.post
import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails
import com.futo.platformplayer.api.media.models.ratings.IRating
import com.futo.platformplayer.api.media.models.streams.IVideoSourceDescriptor
import com.futo.platformplayer.api.media.models.streams.sources.*
import com.futo.platformplayer.api.media.models.subtitles.ISubtitleSource
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
/**
* A detailed video model with data including video/audio sources

View File

@ -14,14 +14,13 @@ interface IRating {
companion object {
fun fromV8OrDefault(config: IV8PluginConfig, obj: V8Value?, default: IRating) = obj.orDefault(default) { fromV8(config, it as V8ValueObject) };
fun fromV8(config: IV8PluginConfig, obj: V8ValueObject, contextName: String = "Unknown") : IRating {
val contextName = "Rating";
val type = RatingType.fromInt(obj.getOrThrow<Int>(config, "type", contextName));
return when(type) {
fun fromV8(config: IV8PluginConfig, obj: V8ValueObject, contextName: String = "Rating") : IRating {
val t = RatingType.fromInt(obj.getOrThrow<Int>(config, "type", contextName));
return when(t) {
RatingType.LIKES -> RatingLikes.fromV8(config, obj);
RatingType.LIKEDISLIKES -> RatingLikeDislikes.fromV8(config, obj);
RatingType.SCALE -> RatingScaler.fromV8(config, obj);
else -> throw NotImplementedError("Unknown type ${type}");
else -> throw NotImplementedError("Unknown type $t");
}
}
}

View File

@ -1,8 +1,6 @@
package com.futo.platformplayer.api.media.models.subtitles
import android.net.Uri
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
interface ISubtitleSource {
val name: String;

View File

@ -1,13 +1,12 @@
package com.futo.platformplayer.api.media.models.video
import com.futo.platformplayer.api.media.IPlatformClient
import com.futo.platformplayer.api.media.models.comments.IPlatformComment
import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails
import com.futo.platformplayer.api.media.models.ratings.IRating
import com.futo.platformplayer.api.media.models.streams.IVideoSourceDescriptor
import com.futo.platformplayer.api.media.models.streams.sources.*
import com.futo.platformplayer.api.media.models.streams.sources.IDashManifestSource
import com.futo.platformplayer.api.media.models.streams.sources.IHLSManifestSource
import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource
import com.futo.platformplayer.api.media.models.subtitles.ISubtitleSource
import com.futo.platformplayer.api.media.structures.IPager
/**
* A detailed video model with data including video/audio sources

View File

@ -8,6 +8,5 @@ import com.futo.platformplayer.api.media.models.streams.sources.VideoUrlSource
class SerializedVideoMuxedSourceDescriptor(
val _videoSources: Array<VideoUrlSource>
): VideoMuxedSourceDescriptor(), ISerializedVideoSourceDescriptor {
@kotlinx.serialization.Transient
override val videoSources: Array<IVideoSource> get() = _videoSources.map { it }.toTypedArray();
};

View File

@ -1,15 +1,16 @@
package com.futo.platformplayer.api.media.models.video
import com.futo.platformplayer.api.media.models.streams.VideoUnMuxedSourceDescriptor
import com.futo.platformplayer.api.media.models.streams.sources.*
import com.futo.platformplayer.api.media.models.streams.sources.AudioUrlSource
import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource
import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource
import com.futo.platformplayer.api.media.models.streams.sources.VideoUrlSource
@kotlinx.serialization.Serializable
class SerializedVideoNonMuxedSourceDescriptor(
val _videoSources: Array<VideoUrlSource>,
val _audioSources: Array<AudioUrlSource>
): VideoUnMuxedSourceDescriptor(), ISerializedVideoSourceDescriptor {
@kotlinx.serialization.Transient
override val videoSources: Array<IVideoSource> get() = _videoSources.map { it }.toTypedArray();
@kotlinx.serialization.Transient
override val audioSources: Array<IAudioSource> get() = _audioSources.map { it }.toTypedArray();
};

View File

@ -1,14 +1,14 @@
package com.futo.platformplayer.api.media.platforms.js
import android.content.Context
import com.futo.platformplayer.states.StateDeveloper
import com.futo.platformplayer.api.media.models.channels.IPlatformChannel
import com.futo.platformplayer.api.media.models.comments.IPlatformComment
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails
import com.futo.platformplayer.api.media.structures.IPager
import com.futo.platformplayer.states.StateApp
import java.util.*
import com.futo.platformplayer.states.StateDeveloper
import java.util.UUID
class DevJSClient : JSClient {
override val id: String
@ -26,8 +26,8 @@ class DevJSClient : JSClient {
_captcha = captcha;
this.devID = devID ?: UUID.randomUUID().toString().substring(0, 5);
onCaptchaException.subscribe { client, captcha ->
StateApp.instance.handleCaptchaException(client, captcha);
onCaptchaException.subscribe { client, c ->
StateApp.instance.handleCaptchaException(client, c);
}
}
//TODO: Misisng auth/captcha pass on purpose?
@ -37,8 +37,8 @@ class DevJSClient : JSClient {
_captcha = captcha;
this.devID = devID ?: UUID.randomUUID().toString().substring(0, 5);
onCaptchaException.subscribe { client, captcha ->
StateApp.instance.handleCaptchaException(client, captcha);
onCaptchaException.subscribe { client, c ->
StateApp.instance.handleCaptchaException(client, c);
}
}

View File

@ -4,12 +4,9 @@ import android.content.Context
import com.caoccao.javet.values.V8Value
import com.caoccao.javet.values.primitive.V8ValueBoolean
import com.caoccao.javet.values.primitive.V8ValueInteger
import com.caoccao.javet.values.primitive.V8ValueNull
import com.caoccao.javet.values.primitive.V8ValueString
import com.caoccao.javet.values.reference.V8ValueArray
import com.caoccao.javet.values.reference.V8ValueObject
import com.futo.platformplayer.states.StatePlatform
import com.futo.platformplayer.states.StatePlugins
import com.futo.platformplayer.api.media.IPlatformClient
import com.futo.platformplayer.api.media.PlatformClientCapabilities
import com.futo.platformplayer.api.media.models.PlatformAuthorLink
@ -23,23 +20,38 @@ import com.futo.platformplayer.api.media.models.live.ILiveChatWindowDescriptor
import com.futo.platformplayer.api.media.models.live.IPlatformLiveEvent
import com.futo.platformplayer.api.media.models.playback.IPlaybackTracker
import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylistDetails
import com.futo.platformplayer.api.media.platforms.js.internal.*
import com.futo.platformplayer.api.media.platforms.js.models.*
import com.futo.platformplayer.api.media.platforms.js.internal.JSCallDocs
import com.futo.platformplayer.api.media.platforms.js.internal.JSDocs
import com.futo.platformplayer.api.media.platforms.js.internal.JSDocsParameter
import com.futo.platformplayer.api.media.platforms.js.internal.JSHttpClient
import com.futo.platformplayer.api.media.platforms.js.internal.JSOptional
import com.futo.platformplayer.api.media.platforms.js.internal.JSParameterDocs
import com.futo.platformplayer.api.media.platforms.js.models.IJSContentDetails
import com.futo.platformplayer.api.media.platforms.js.models.JSChannel
import com.futo.platformplayer.api.media.platforms.js.models.JSChannelPager
import com.futo.platformplayer.api.media.platforms.js.models.JSChapter
import com.futo.platformplayer.api.media.platforms.js.models.JSComment
import com.futo.platformplayer.api.media.platforms.js.models.JSCommentPager
import com.futo.platformplayer.api.media.platforms.js.models.JSContentPager
import com.futo.platformplayer.api.media.platforms.js.models.JSLiveChatWindowDescriptor
import com.futo.platformplayer.api.media.platforms.js.models.JSLiveEventPager
import com.futo.platformplayer.api.media.platforms.js.models.JSPlaybackTracker
import com.futo.platformplayer.api.media.platforms.js.models.JSPlaylistDetails
import com.futo.platformplayer.api.media.structures.EmptyPager
import com.futo.platformplayer.api.media.structures.IPager
import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.constructs.Event2
import com.futo.platformplayer.engine.V8Plugin
import com.futo.platformplayer.engine.exceptions.PluginEngineException
import com.futo.platformplayer.engine.exceptions.PluginEngineStoppedException
import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException
import com.futo.platformplayer.engine.exceptions.ScriptImplementationException
import com.futo.platformplayer.engine.exceptions.ScriptLoginRequiredException
import com.futo.platformplayer.engine.exceptions.ScriptValidationException
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.models.ImageVariable
import com.futo.platformplayer.states.AnnouncementType
import com.futo.platformplayer.states.StateAnnouncement
import com.futo.platformplayer.states.StatePlatform
import com.futo.platformplayer.states.StatePlugins
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.time.OffsetDateTime
@ -50,7 +62,7 @@ open class JSClient : IPlatformClient {
val config: SourcePluginConfig;
protected val _context: Context;
private val _plugin: V8Plugin;
private val plugin: V8Plugin get() = _plugin ?: throw IllegalStateException("Client not enabled");
private val plugin: V8Plugin get() = _plugin
var descriptor: SourcePluginDescriptor
private set;

View File

@ -5,9 +5,8 @@ import com.futo.platformplayer.SignatureProvider
import com.futo.platformplayer.api.media.Serializer
import com.futo.platformplayer.engine.IV8PluginConfig
import com.futo.platformplayer.states.StatePlugins
import kotlinx.serialization.decodeFromString
import java.net.URL
import java.util.*
import java.util.UUID
@kotlinx.serialization.Serializable
class SourcePluginConfig(
@ -149,7 +148,6 @@ class SourcePluginConfig(
val warningDialog: String? = null,
val options: List<String>? = null
) {
@kotlinx.serialization.Transient
val variableOrName: String get() = variable ?: name;
}
}

View File

@ -2,7 +2,6 @@ package com.futo.platformplayer.api.media.platforms.js
import com.futo.platformplayer.R
import com.futo.platformplayer.constructs.Event0
import com.futo.platformplayer.serializers.FlexibleBooleanSerializer
import com.futo.platformplayer.views.fields.DropdownFieldOptions
import com.futo.platformplayer.views.fields.FieldForm
import com.futo.platformplayer.views.fields.FormField
@ -107,9 +106,9 @@ class SourcePluginDescriptor {
fun loadDefaults(config: SourcePluginConfig) {
if(tabEnabled.enableHome == null)
tabEnabled.enableHome = config.enableInHome ?: true;
tabEnabled.enableHome = config.enableInHome
if(tabEnabled.enableSearch == null)
tabEnabled.enableSearch = config.enableInSearch ?: true;
tabEnabled.enableSearch = config.enableInSearch
}
}

View File

@ -1,7 +1,5 @@
package com.futo.platformplayer.api.media.platforms.js.internal
import android.net.Uri
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.api.http.ManagedHttpClient
import com.futo.platformplayer.api.media.platforms.js.JSClient
import com.futo.platformplayer.api.media.platforms.js.SourceAuth
@ -47,10 +45,7 @@ class JSHttpClient : ManagedHttpClient {
override fun clone(): ManagedHttpClient {
val newClient = JSHttpClient(_jsClient, _auth);
newClient._currentCookieMap = if(_currentCookieMap != null)
HashMap(_currentCookieMap.toList().associate { Pair(it.first, HashMap(it.second)) })
else
hashMapOf();
newClient._currentCookieMap = HashMap(_currentCookieMap.toList().associate { Pair(it.first, HashMap(it.second)) })
return newClient;
}
@ -69,10 +64,10 @@ class JSHttpClient : ManagedHttpClient {
}
if(doApplyCookies) {
if (!_currentCookieMap.isNullOrEmpty()) {
if (_currentCookieMap.isNotEmpty()) {
val cookiesToApply = hashMapOf<String, String>();
synchronized(_currentCookieMap!!) {
for(cookie in _currentCookieMap!!
synchronized(_currentCookieMap) {
for(cookie in _currentCookieMap
.filter { domain.matchesDomain(it.key) }
.flatMap { it.value.toList() })
cookiesToApply[cookie.first] = cookie.second;
@ -92,11 +87,11 @@ class JSHttpClient : ManagedHttpClient {
}
if(_jsClient != null)
_jsClient?.validateUrlOrThrow(request.url.toString());
_jsClient.validateUrlOrThrow(request.url.toString());
else if (_jsConfig != null && !_jsConfig.isUrlAllowed(request.url.toString()))
throw ScriptImplementationException(_jsConfig, "Attempted to access non-whitelisted url: ${request.url.toString()}\nAdd it to your config");
return newBuilder?.let { it.build() } ?: request;
return newBuilder?.build() ?: request;
}
override fun afterRequest(resp: okhttp3.Response): okhttp3.Response {
@ -107,13 +102,11 @@ class JSHttpClient : ManagedHttpClient {
"." + domainParts.drop(domainParts.size - 2).joinToString(".");
for (header in resp.headers) {
if ((_auth != null || _currentCookieMap.isNotEmpty()) && header.first.lowercase() == "set-cookie") {
//val newCookies = cookieStringToMap(header.second.split("; "));
val cookie = cookieStringToPair(header.second);
//for (cookie in newCookies) {
var cookieValue = cookie.second;
var domainToUse = domain;
if (!cookie.first.isNullOrEmpty() && !cookie.second.isNullOrEmpty()) {
if (cookie.first.isNotEmpty() && cookie.second.isNotEmpty()) {
val cookieParts = cookie.second.split(";");
if (cookieParts.size == 0)
continue;
@ -133,58 +126,24 @@ class JSHttpClient : ManagedHttpClient {
else defaultCookieDomain;
}
val cookieMap = if (_currentCookieMap!!.containsKey(domainToUse))
_currentCookieMap!![domainToUse]!!;
val cookieMap = if (_currentCookieMap.containsKey(domainToUse))
_currentCookieMap[domainToUse]!!;
else {
val newMap = hashMapOf<String, String>();
_currentCookieMap!!.put(domainToUse, newMap)
_currentCookieMap[domainToUse] = newMap
newMap;
}
if(cookieMap.containsKey(cookie.first) || doAllowNewCookies)
cookieMap.put(cookie.first, cookieValue);
//}
cookieMap[cookie.first] = cookieValue;
}
}
}
return resp;
}
private fun cookieStringToMap(parts: List<String>): Map<String, String> {
val map = hashMapOf<String, String>();
for(cookie in parts) {
val pair = cookieStringToPair(cookie)
map.put(pair.first, pair.second);
}
return map;
}
private fun cookieStringToPair(cookie: String): Pair<String, String> {
val cookieKey = cookie.substring(0, cookie.indexOf("="));
val cookieVal = cookie.substring(cookie.indexOf("=") + 1);
return Pair(cookieKey.trim(), cookieVal.trim());
}
//Prints out code for test reproduction..
fun printTestCode(url: String, body: ByteArray?, headers: Map<String, String>, cookieString: String, allHeaders: Map<String, String>? = null) {
var code = "Code: \n";
code += "\nurl = \"${url}\";";
if(body != null)
code += "\nbody = \"${String(body).replace("\"", "\\\"")}\";";
if(headers != null)
for(header in headers) {
code += "\nclient.Headers.Add(\"${header.key}\", \"${header.value}\");";
}
if(cookieString != null)
code += "\nclient.Headers.Add(\"Cookie\", \"${cookieString}\");";
if(allHeaders != null) {
code += "\n//OTHER HEADERS:"
for (header in allHeaders) {
code += "\nclient.Headers.Add(\"${header.key}\", \"${header.value}\");";
}
}
Logger.i("Testing", code);
}
}

View File

@ -1,3 +1,5 @@
@file:Suppress("DEPRECATION")
package com.futo.platformplayer.api.media.platforms.js.models.sources
import com.caoccao.javet.values.V8Value

View File

@ -1,7 +1,5 @@
package com.futo.platformplayer.api.media.structures
import kotlinx.coroutines.CoroutineScope
/**
* A Pager interface that implements a suspended manner of nextPage
*/

View File

@ -1,6 +1,5 @@
package com.futo.platformplayer.api.media.structures
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
import com.futo.platformplayer.constructs.Event1
/**

View File

@ -5,6 +5,7 @@ import com.futo.platformplayer.api.media.structures.ReusablePager.Companion.asRe
import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.logging.Logger
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking
/**
@ -25,6 +26,7 @@ abstract class MultiRefreshPager<T>: IRefreshPager<T>, IPager<T> {
private val _pending: MutableList<Deferred<IPager<T>?>>;
@OptIn(ExperimentalCoroutinesApi::class)
constructor(pagers: List<IPager<T>>, pendingPagers: List<Deferred<IPager<T>?>>, placeholderPagers: List<IPager<T>>? = null) {
_pagersReusable = pagers.map { ReusablePager(it) }.toMutableList();
_totalPagers = pagers.size + pendingPagers.size;
@ -100,7 +102,7 @@ abstract class MultiRefreshPager<T>: IRefreshPager<T>, IPager<T> {
}
private fun getCurrentSubPagers(): List<IPager<T>> {
val reusableWindows = _pagersReusable.map { it.getWindow() as IPager<T> };
val reusableWindows = _pagersReusable.map { it.getWindow() };
val placeholderWindows = synchronized(_pending) {
_placeHolderPagersPaired.filter { _pending.contains(it.key) }.values
}

View File

@ -100,7 +100,7 @@ class SingleAsyncItemPager<T> {
private fun fillDeferredUntil(i: Int): Int {
val startPos = _requestedPageItems.size;
for(i in _requestedPageItems.size..i) {
for(v in _requestedPageItems.size..i) {
_requestedPageItems.add(CompletableDeferred());
}
return startPos;

View File

@ -2,13 +2,17 @@ package com.futo.platformplayer.casting
import android.os.Looper
import android.util.Log
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.getConnectedSocket
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.models.CastingDeviceInfo
import com.futo.platformplayer.protos.ChromeCast
import com.futo.platformplayer.toHexString
import com.futo.platformplayer.toInetAddress
import kotlinx.coroutines.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import org.json.JSONObject
import java.io.DataInputStream
import java.io.DataOutputStream
@ -502,10 +506,10 @@ class ChromecastCastingDevice : CastingDevice {
}
val volume = status.getJSONObject("volume");
val volumeControlType = volume.getString("controlType");
//val volumeControlType = volume.getString("controlType");
val volumeLevel = volume.getString("level").toDouble();
val volumeMuted = volume.getBoolean("muted");
val volumeStepInterval = volume.getString("stepInterval").toFloat();
//val volumeStepInterval = volume.getString("stepInterval").toFloat();
this.volume = if (volumeMuted) 0.0 else volumeLevel;
Logger.i(TAG, "Status update received volume (level: $volumeLevel, muted: $volumeMuted)");

View File

@ -5,12 +5,23 @@ import android.content.Context
import android.net.Uri
import android.os.Looper
import android.util.Base64
import com.futo.platformplayer.*
import com.futo.platformplayer.activities.MainActivity
import com.futo.platformplayer.BuildConfig
import com.futo.platformplayer.UIDialogs
import com.futo.platformplayer.api.http.ManagedHttpClient
import com.futo.platformplayer.api.http.server.ManagedHttpServer
import com.futo.platformplayer.api.http.server.handlers.*
import com.futo.platformplayer.api.media.models.streams.sources.*
import com.futo.platformplayer.api.http.server.handlers.HttpConstantHandler
import com.futo.platformplayer.api.http.server.handlers.HttpFileHandler
import com.futo.platformplayer.api.http.server.handlers.HttpFuntionHandler
import com.futo.platformplayer.api.http.server.handlers.HttpProxyHandler
import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource
import com.futo.platformplayer.api.media.models.streams.sources.IAudioUrlSource
import com.futo.platformplayer.api.media.models.streams.sources.IHLSManifestAudioSource
import com.futo.platformplayer.api.media.models.streams.sources.IHLSManifestSource
import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource
import com.futo.platformplayer.api.media.models.streams.sources.IVideoUrlSource
import com.futo.platformplayer.api.media.models.streams.sources.LocalAudioSource
import com.futo.platformplayer.api.media.models.streams.sources.LocalSubtitleSource
import com.futo.platformplayer.api.media.models.streams.sources.LocalVideoSource
import com.futo.platformplayer.api.media.models.subtitles.ISubtitleSource
import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails
import com.futo.platformplayer.builders.DashBuilder
@ -21,18 +32,20 @@ import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.models.CastingDeviceInfo
import com.futo.platformplayer.parsers.HLS
import com.futo.platformplayer.states.StateApp
import kotlinx.coroutines.*
import com.futo.platformplayer.stores.CastingDeviceInfoStorage
import com.futo.platformplayer.stores.FragmentedStorage
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import java.net.InetAddress
import java.util.*
import java.util.UUID
import javax.jmdns.JmDNS
import javax.jmdns.ServiceEvent
import javax.jmdns.ServiceListener
import kotlin.collections.HashMap
import com.futo.platformplayer.stores.CastingDeviceInfoStorage
import com.futo.platformplayer.stores.FragmentedStorage
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import javax.jmdns.ServiceTypeListener
class StateCasting {
@ -213,7 +226,7 @@ class StateCasting {
}
}
_castServer.start();
enableDeveloper(context.contentResolver, true);
enableDeveloper(true);
Logger.i(TAG, "CastingService started.");
}
@ -1077,7 +1090,6 @@ class StateCasting {
CastProtocolType.FCAST -> {
FCastCastingDevice(deviceInfo);
}
else -> throw Exception("${deviceInfo.type} is not a valid casting protocol")
}
}
@ -1172,7 +1184,7 @@ class StateCasting {
invokeEvents?.let { _scopeMain.launch { it(); }; };
}
fun enableDeveloper(contentResolver: ContentResolver, enableDev: Boolean){
fun enableDeveloper(enableDev: Boolean){
_castServer.removeAllHandlers("dev");
if(enableDev) {
_castServer.addHandler(HttpFuntionHandler("GET", "/dashPlayer") { context ->

View File

@ -1,8 +1,10 @@
package com.futo.platformplayer.constructs
import android.provider.Settings.Global
import com.futo.platformplayer.states.StateApp
import kotlinx.coroutines.*
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
class BatchedTaskHandler<TParameter, TResult> {
@ -25,14 +27,14 @@ class BatchedTaskHandler<TParameter, TResult> {
}
fun execute(para : TParameter) : Deferred<TResult> {
var result: TResult? = null;
var result: TResult?;
var taskResult: Deferred<TResult>? = null;
synchronized(_batchLock) {
result = _taskGetCache?.invoke(para);
if(result == null) {
taskResult = _batchRequest[para];
if(taskResult?.isCancelled ?: false) {
if(taskResult?.isCancelled == true) {
_batchRequest.remove(para);
taskResult = null;
}

View File

@ -2,7 +2,11 @@ package com.futo.platformplayer.constructs
import android.util.Log
import com.futo.platformplayer.logging.Logger
import kotlinx.coroutines.*
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class TaskHandler<TParameter, TResult> {
private val TAG = "TaskHandler<TResult>"
@ -16,7 +20,7 @@ class TaskHandler<TParameter, TResult> {
private val _task: suspend ((parameter: TParameter) -> TResult);
constructor(claz : Class<TResult>, scope: ()->CoroutineScope) {
_task = { claz.newInstance() };
_task = { claz.getDeclaredConstructor().newInstance() };
_scope = scope;
_dispatcher = Dispatchers.IO;
}
@ -32,7 +36,7 @@ class TaskHandler<TParameter, TResult> {
}
inline fun <reified T : Throwable>exception(noinline cb : (T)->Unit) : TaskHandler<TParameter, TResult> {
onError.subscribeConditional { ex, para ->
onError.subscribeConditional { ex, _ ->
if(ex is T) {
cb(ex);
return@subscribeConditional true;
@ -76,8 +80,7 @@ class TaskHandler<TParameter, TResult> {
try {
onSuccess.emit(result);
handled = true;
}
catch (e: Throwable) {
} catch (e: Throwable) {
Logger.w(TAG, "Handled exception in TaskHandler onSuccess.", e);
onError.emit(e, parameter);
handled = true;

View File

@ -1,9 +1,10 @@
package com.futo.platformplayer.debug
import com.google.android.exoplayer2.util.Log
import com.futo.platformplayer.logging.Logger
class Stopwatch {
var startTime = System.nanoTime()
private var startTime = System.nanoTime()
val elapsedMs: Double get() {
val now = System.nanoTime()
@ -19,7 +20,7 @@ class Stopwatch {
val now = System.nanoTime()
val diff = now - startTime
val diffMs = diff / 1000000.0
Log.d(tag, "STOPWATCH $message ${diffMs}ms")
Logger.i(tag, "STOPWATCH $message ${diffMs}ms")
startTime = now
return diff
}

View File

@ -80,11 +80,11 @@ class DeveloperEndpoints(private val context: Context) {
//Files
@HttpGET("/dev", "text/html")
val devTestHtml = StateAssets.readAsset(context, "devportal/index.html", true);
val devTestHtml = StateAssets.readAsset(context, "devportal/index.html");
@HttpGET("/source.js", "application/javascript")
val devSourceJS = StateAssets.readAsset(context, "scripts/source.js", true);
val devSourceJS = StateAssets.readAsset(context, "scripts/source.js");
@HttpGET("/dev_bridge.js", "application/javascript")
val devBridgeJS = StateAssets.readAsset(context, "devportal/dev_bridge.js", true);
val devBridgeJS = StateAssets.readAsset(context, "devportal/dev_bridge.js");
@HttpGET("/source_docs.json", "application/json")
val devSourceDocsJson = Json.encodeToString(JSClient.getJSDocs());
@HttpGET("/source_docs.js", "application/javascript")
@ -98,7 +98,7 @@ class DeveloperEndpoints(private val context: Context) {
//@HttpGET("/dependencies/vuetify.min.css", "text/css")
//val depVuetifyCss = StateAssets.readAsset(context, "devportal/dependencies/vuetify.min.css", true);
@HttpGET("/dependencies/FutoMainLogo.svg", "image/svg+xml")
val depFutoLogo = StateAssets.readAsset(context, "devportal/dependencies/FutoMainLogo.svg", true);
val depFutoLogo = StateAssets.readAsset(context, "devportal/dependencies/FutoMainLogo.svg");
@HttpGET("/reference_plugin.d.ts", "text/plain")
fun devSourceTSWithRefs(httpContext: HttpContext) {
@ -107,7 +107,7 @@ class DeveloperEndpoints(private val context: Context) {
builder.appendLine("//Reference Scriptfile");
builder.appendLine("//Intended exclusively for auto-complete in your IDE, not for execution");
builder.appendLine(StateAssets.readAsset(context, "devportal/plugin.d.ts", true));
builder.appendLine(StateAssets.readAsset(context, "devportal/plugin.d.ts"));
httpContext.respondCode(200, builder.toString(), "text/plain");
}
@ -119,7 +119,7 @@ class DeveloperEndpoints(private val context: Context) {
builder.appendLine("//Reference Scriptfile");
builder.appendLine("//Intended exclusively for auto-complete in your IDE, not for execution");
builder.appendLine(StateAssets.readAsset(context, "scripts/source.js", true));
builder.appendLine(StateAssets.readAsset(context, "scripts/source.js"));
for(pack in testPluginOrThrow.getPackages()) {
builder.appendLine();
@ -194,7 +194,7 @@ class DeveloperEndpoints(private val context: Context) {
context.respondJson(200, testPluginOrThrow.getPackageVariables());
}
catch(ex: Throwable) {
context.respondCode(500, (ex::class.simpleName + ":" + ex.message) ?: "", "text/plain")
context.respondCode(500, (ex::class.simpleName + ":" + ex.message), "text/plain")
}
}
@HttpPOST("/plugin/cleanTestPlugin")
@ -204,7 +204,7 @@ class DeveloperEndpoints(private val context: Context) {
context.respondCode(200);
}
catch(ex: Throwable) {
context.respondCode(500, (ex::class.simpleName + ":" + ex.message) ?: "", "text/plain")
context.respondCode(500, (ex::class.simpleName + ":" + ex.message), "text/plain")
}
}
@HttpPOST("/plugin/captchaTestPlugin")
@ -226,7 +226,7 @@ class DeveloperEndpoints(private val context: Context) {
context.respondCode(200, "Captcha started");
}
catch(ex: Throwable) {
context.respondCode(500, (ex::class.simpleName + ":" + ex.message) ?: "", "text/plain")
context.respondCode(500, (ex::class.simpleName + ":" + ex.message), "text/plain")
}
}
@HttpGET("/plugin/loginTestPlugin")
@ -246,7 +246,7 @@ class DeveloperEndpoints(private val context: Context) {
context.respondCode(200, "Login started");
}
catch(ex: Throwable) {
context.respondCode(500, (ex::class.simpleName + ":" + ex.message) ?: "", "text/plain")
context.respondCode(500, (ex::class.simpleName + ":" + ex.message), "text/plain")
}
}
@HttpGET("/plugin/logoutTestPlugin")
@ -258,7 +258,7 @@ class DeveloperEndpoints(private val context: Context) {
context.respondCode(200, "Logged out");
}
catch(ex: Throwable) {
context.respondCode(500, (ex::class.simpleName + ":" + ex.message) ?: "", "text/plain")
context.respondCode(500, (ex::class.simpleName + ":" + ex.message), "text/plain")
}
}
@ -269,7 +269,7 @@ class DeveloperEndpoints(private val context: Context) {
context.respondCode(200, if(isLoggedIn) "true" else "false", "application/json");
}
catch(ex: Throwable) {
context.respondCode(500, (ex::class.simpleName + ":" + ex.message) ?: "", "text/plain")
context.respondCode(500, (ex::class.simpleName + ":" + ex.message), "text/plain")
}
}
@ -319,7 +319,7 @@ class DeveloperEndpoints(private val context: Context) {
catch(invocation: InvocationTargetException) {
val innerException = invocation.targetException;
Logger.e("DeveloperEndpoints", innerException.message, innerException);
context.respondCode(500, innerException::class.simpleName + ":" + innerException.message ?: "", "text/plain")
context.respondCode(500, innerException::class.simpleName + ":" + innerException.message, "text/plain")
}
catch(ilEx: IllegalArgumentException) {
if(ilEx.message?.contains("does not exist") ?: false) {
@ -327,12 +327,12 @@ class DeveloperEndpoints(private val context: Context) {
}
else {
Logger.e("DeveloperEndpoints", ilEx.message, ilEx);
context.respondCode(500, ilEx::class.simpleName + ":" + ilEx.message ?: "", "text/plain")
context.respondCode(500, ilEx::class.simpleName + ":" + ilEx.message, "text/plain")
}
}
catch(ex: Throwable) {
Logger.e("DeveloperEndpoints", ex.message, ex);
context.respondCode(500, ex::class.simpleName + ":" + ex.message ?: "", "text/plain")
context.respondCode(500, ex::class.simpleName + ":" + ex.message, "text/plain")
}
}
@HttpGET("/plugin/remoteProp")
@ -362,12 +362,12 @@ class DeveloperEndpoints(private val context: Context) {
}
else {
Logger.e("DeveloperEndpoints", ilEx.message, ilEx);
context.respondCode(500, ilEx::class.simpleName + ":" + ilEx.message ?: "", "text/plain")
context.respondCode(500, ilEx::class.simpleName + ":" + ilEx.message, "text/plain")
}
}
catch(ex: Throwable) {
Logger.e("DeveloperEndpoints", ex.message, ex);
context.respondCode(500, ex::class.simpleName + ":" + ex.message ?: "", "text/plain")
context.respondCode(500, ex::class.simpleName + ":" + ex.message, "text/plain")
}
}
@ -398,7 +398,7 @@ class DeveloperEndpoints(private val context: Context) {
fun pluginLoadDevPlugin(context: HttpContext) {
val config = context.readContentJson<SourcePluginConfig>()
try {
val script = _client.get(config.absoluteScriptUrl!!);
val script = _client.get(config.absoluteScriptUrl);
if(!script.isOk)
throw IllegalStateException("URL ${config.scriptUrl} return code ${script.code}");
if(script.body == null)
@ -409,7 +409,7 @@ class DeveloperEndpoints(private val context: Context) {
}
catch(ex: Exception) {
Logger.e("DeveloperEndpoints", ex.message, ex);
context.respondCode(500, ex::class.simpleName + ":" + ex.message ?: "", "text/plain")
context.respondCode(500, ex::class.simpleName + ":" + ex.message, "text/plain")
}
}

View File

@ -1,7 +1,9 @@
package com.futo.platformplayer.dialogs
import android.app.AlertDialog
import android.app.PendingIntent.*
import android.app.PendingIntent.FLAG_MUTABLE
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
import android.app.PendingIntent.getBroadcast
import android.content.Context
import android.content.Intent
import android.content.pm.PackageInstaller
@ -14,12 +16,18 @@ import android.widget.Button
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import com.futo.platformplayer.*
import com.futo.platformplayer.receivers.InstallReceiver
import com.futo.platformplayer.R
import com.futo.platformplayer.Settings
import com.futo.platformplayer.UIDialogs
import com.futo.platformplayer.api.http.ManagedHttpClient
import com.futo.platformplayer.copyToOutputStream
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.receivers.InstallReceiver
import com.futo.platformplayer.states.StateUpdate
import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
import java.io.InputStream
@ -100,7 +108,7 @@ class AutoUpdateDialog(context: Context?) : AlertDialog(context) {
window?.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
_text.text = context.resources.getText(R.string.downloading_update);
(_updateSpinner?.drawable as Animatable?)?.start();
(_updateSpinner.drawable as Animatable?)?.start();
GlobalScope.launch(Dispatchers.IO) {
var inputStream: InputStream? = null;
@ -193,7 +201,7 @@ class AutoUpdateDialog(context: Context?) : AlertDialog(context) {
setCancelable(true);
setCanceledOnTouchOutside(true);
_buttonClose.visibility = View.VISIBLE;
(_updateSpinner?.drawable as Animatable?)?.stop();
(_updateSpinner.drawable as Animatable?)?.stop();
if (result == null || result.isBlank()) {
_updateSpinner.setImageResource(R.drawable.ic_update_success_251dp);

View File

@ -7,8 +7,8 @@ import android.graphics.drawable.Animatable
import android.os.Bundle
import android.text.Spannable
import android.text.SpannableString
import android.text.style.ForegroundColorSpan
import android.text.method.ScrollingMovementMethod
import android.text.style.ForegroundColorSpan
import android.view.LayoutInflater
import android.view.View
import android.view.WindowManager
@ -17,12 +17,16 @@ import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import com.futo.platformplayer.*
import com.futo.platformplayer.R
import com.futo.platformplayer.UIDialogs
import com.futo.platformplayer.api.media.exceptions.NoPlatformClientException
import com.futo.platformplayer.assume
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.states.StateApp
import com.futo.platformplayer.stores.v2.ManagedStore
import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class ImportDialog : AlertDialog {
companion object {
@ -99,7 +103,6 @@ class ImportDialog : AlertDialog {
_textProgress = findViewById(R.id.text_progress);
_updateSpinner = findViewById(R.id.update_spinner);
val toMigrateCount = _store.getMissingReconstructionCount();
_import_type_text.text = _store.name;
_import_name_text.text = _name;

View File

@ -1,29 +1,13 @@
package com.futo.platformplayer.dialogs
import android.app.AlertDialog
import android.app.PendingIntent.*
import android.content.Context
import android.content.Intent
import android.content.pm.PackageInstaller
import android.graphics.drawable.Animatable
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.widget.Button
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import com.futo.platformplayer.receivers.InstallReceiver
import com.futo.platformplayer.R
import com.futo.platformplayer.Settings
import com.futo.platformplayer.activities.MainActivity
import com.futo.platformplayer.api.http.ManagedHttpClient
import com.futo.platformplayer.states.StateApp
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class ProgressDialog : AlertDialog {
companion object {
@ -50,7 +34,7 @@ class ProgressDialog : AlertDialog {
setCancelable(false);
setCanceledOnTouchOutside(false);
_text.text = "";
(_updateSpinner?.drawable as Animatable?)?.start();
(_updateSpinner.drawable as Animatable?)?.start();
_handler(this);
}

View File

@ -6,13 +6,20 @@ import com.arthenica.ffmpegkit.FFmpegKit
import com.arthenica.ffmpegkit.ReturnCode
import com.arthenica.ffmpegkit.StatisticsCallback
import com.futo.platformplayer.Settings
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.states.StateDownloads
import com.futo.platformplayer.states.StatePlatform
import com.futo.platformplayer.api.http.ManagedHttpClient
import com.futo.platformplayer.api.media.PlatformID
import com.futo.platformplayer.api.media.models.streams.VideoUnMuxedSourceDescriptor
import com.futo.platformplayer.api.media.models.streams.sources.*
import com.futo.platformplayer.api.media.models.streams.sources.AudioUrlSource
import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource
import com.futo.platformplayer.api.media.models.streams.sources.IAudioUrlSource
import com.futo.platformplayer.api.media.models.streams.sources.IHLSManifestSource
import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource
import com.futo.platformplayer.api.media.models.streams.sources.IVideoUrlSource
import com.futo.platformplayer.api.media.models.streams.sources.LocalAudioSource
import com.futo.platformplayer.api.media.models.streams.sources.LocalSubtitleSource
import com.futo.platformplayer.api.media.models.streams.sources.LocalVideoSource
import com.futo.platformplayer.api.media.models.streams.sources.SubtitleRawSource
import com.futo.platformplayer.api.media.models.streams.sources.VideoUrlSource
import com.futo.platformplayer.api.media.models.streams.sources.other.IStreamMetaDataSource
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails
@ -24,8 +31,11 @@ import com.futo.platformplayer.hasAnySource
import com.futo.platformplayer.helpers.FileHelper.Companion.sanitizeFileName
import com.futo.platformplayer.helpers.VideoHelper
import com.futo.platformplayer.isDownloadable
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.parsers.HLS
import com.futo.platformplayer.serializers.OffsetDateTimeNullableSerializer
import com.futo.platformplayer.states.StateDownloads
import com.futo.platformplayer.states.StatePlatform
import com.futo.platformplayer.toHumanBitrate
import com.futo.platformplayer.toHumanBytesSpeed
import kotlinx.coroutines.CancellationException
@ -54,14 +64,9 @@ class VideoDownload {
var video: SerializedPlatformVideo? = null;
var videoDetails: SerializedPlatformVideoDetails? = null;
@kotlinx.serialization.Transient
val videoEither: IPlatformVideo get() = videoDetails ?: video ?: throw IllegalStateException("Missing video?");
@kotlinx.serialization.Transient
val id: PlatformID get() = videoEither.id
@kotlinx.serialization.Transient
val name: String get() = videoEither.name;
@kotlinx.serialization.Transient
val thumbnail: String? get() = videoDetails?.thumbnails?.getHQThumbnail() ?: video?.thumbnails?.getHQThumbnail();
var targetPixelCount: Long? = null;
@ -385,7 +390,7 @@ class VideoDownload {
Logger.i(TAG, "${name} downloadSource Finished");
}
catch(ioex: IOException) {
if(targetFile.exists() ?: false)
if(targetFile.exists())
targetFile.delete();
if(ioex.message?.contains("ENOSPC") ?: false)
throw Exception("Not enough space on device", ioex);
@ -393,7 +398,7 @@ class VideoDownload {
throw ioex;
}
catch(ex: Throwable) {
if(targetFile.exists() ?: false)
if(targetFile.exists())
targetFile.delete();
throw ex;
}
@ -412,7 +417,7 @@ class VideoDownload {
val cmd = "-f concat -safe 0 -i \"${fileList.absolutePath}\" -c copy \"${targetFile.absolutePath}\""
val statisticsCallback = StatisticsCallback { statistics ->
val statisticsCallback = StatisticsCallback { _ ->
//TODO: Show progress?
}
@ -449,8 +454,7 @@ class VideoDownload {
targetFile.createNewFile();
var sourceLength: Long? = null;
val sourceLength: Long?;
val fileStream = FileOutputStream(targetFile);
try{
@ -458,17 +462,17 @@ class VideoDownload {
if(Settings.instance.downloads.byteRangeDownload && head?.containsKey("accept-ranges") == true && head.containsKey("content-length"))
{
val concurrency = Settings.instance.downloads.getByteRangeThreadCount();
Logger.i(TAG, "Download ${name} ByteRange Parallel (${concurrency})");
Logger.i(TAG, "Download $name ByteRange Parallel (${concurrency})");
sourceLength = head["content-length"]!!.toLong();
onProgress(sourceLength, 0, 0);
downloadSource_Ranges(name, client, fileStream, videoUrl, sourceLength, 1024*512, concurrency, onProgress);
}
else {
Logger.i(TAG, "Download ${name} Sequential");
Logger.i(TAG, "Download $name Sequential");
sourceLength = downloadSource_Sequential(client, fileStream, videoUrl, onProgress);
}
Logger.i(TAG, "${name} downloadSource Finished");
Logger.i(TAG, "$name downloadSource Finished");
}
catch(ioex: IOException) {
if(targetFile.exists() ?: false)
@ -484,7 +488,7 @@ class VideoDownload {
throw ex;
}
finally {
fileStream?.close();
fileStream.close();
}
return sourceLength!!;
}
@ -507,7 +511,7 @@ class VideoDownload {
val sourceStream = result.body.byteStream();
var totalRead: Long = 0;
var read = 0;
var read: Int;
val buffer = ByteArray(4096);

View File

@ -11,7 +11,13 @@ import com.futo.platformplayer.api.media.models.ratings.IRating
import com.futo.platformplayer.api.media.models.streams.IVideoSourceDescriptor
import com.futo.platformplayer.api.media.models.streams.LocalVideoMuxedSourceDescriptor
import com.futo.platformplayer.api.media.models.streams.LocalVideoUnMuxedSourceDescriptor
import com.futo.platformplayer.api.media.models.streams.sources.*
import com.futo.platformplayer.api.media.models.streams.sources.IDashManifestSource
import com.futo.platformplayer.api.media.models.streams.sources.IHLSManifestSource
import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource
import com.futo.platformplayer.api.media.models.streams.sources.LocalAudioSource
import com.futo.platformplayer.api.media.models.streams.sources.LocalSubtitleSource
import com.futo.platformplayer.api.media.models.streams.sources.LocalVideoSource
import com.futo.platformplayer.api.media.models.streams.sources.SubtitleRawSource
import com.futo.platformplayer.api.media.models.subtitles.ISubtitleSource
import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails
import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideoDetails
@ -46,7 +52,7 @@ class VideoLocal: IPlatformVideoDetails, IStoreItem {
override val shareUrl: String get() = videoSerialized.shareUrl;
@kotlinx.serialization.Transient
override val video: IVideoSourceDescriptor get() = if(!audioSource.isEmpty())
override val video: IVideoSourceDescriptor get() = if(audioSource.isNotEmpty())
LocalVideoUnMuxedSourceDescriptor(this)
else
LocalVideoMuxedSourceDescriptor(this);

View File

@ -10,14 +10,30 @@ import com.caoccao.javet.values.primitive.V8ValueBoolean
import com.caoccao.javet.values.primitive.V8ValueInteger
import com.caoccao.javet.values.primitive.V8ValueString
import com.caoccao.javet.values.reference.V8ValueObject
import com.futo.platformplayer.*
import com.futo.platformplayer.api.http.ManagedHttpClient
import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.engine.exceptions.*
import com.futo.platformplayer.engine.exceptions.NoInternetException
import com.futo.platformplayer.engine.exceptions.PluginEngineStoppedException
import com.futo.platformplayer.engine.exceptions.ScriptAgeException
import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException
import com.futo.platformplayer.engine.exceptions.ScriptCompilationException
import com.futo.platformplayer.engine.exceptions.ScriptCriticalException
import com.futo.platformplayer.engine.exceptions.ScriptException
import com.futo.platformplayer.engine.exceptions.ScriptExecutionException
import com.futo.platformplayer.engine.exceptions.ScriptImplementationException
import com.futo.platformplayer.engine.exceptions.ScriptLoginRequiredException
import com.futo.platformplayer.engine.exceptions.ScriptTimeoutException
import com.futo.platformplayer.engine.exceptions.ScriptUnavailableException
import com.futo.platformplayer.engine.internal.V8Converter
import com.futo.platformplayer.engine.packages.*
import com.futo.platformplayer.engine.packages.PackageBridge
import com.futo.platformplayer.engine.packages.PackageDOMParser
import com.futo.platformplayer.engine.packages.PackageHttp
import com.futo.platformplayer.engine.packages.PackageUtilities
import com.futo.platformplayer.engine.packages.V8Package
import com.futo.platformplayer.getOrThrow
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.states.StateAssets
import com.futo.platformplayer.warnIfMainThread
class V8Plugin {
val config: IV8PluginConfig;
@ -61,7 +77,7 @@ class V8Plugin {
withDependency(PackageBridge(this, config));
for(pack in config.packages)
withDependency(getPackage(context, pack));
withDependency(getPackage(pack));
}
fun withDependency(context: Context, assetPath: String) : V8Plugin {
@ -208,10 +224,10 @@ class V8Plugin {
}
}
private fun getPackage(context: Context, packageName: String): V8Package {
private fun getPackage(packageName: String): V8Package {
//TODO: Auto get all package types?
return when(packageName) {
"DOMParser" -> PackageDOMParser(context, this)
"DOMParser" -> PackageDOMParser(this)
"Http" -> PackageHttp(this, config)
"Utilities" -> PackageUtilities(this, config)
else -> throw ScriptCompilationException(config, "Unknown package [${packageName}] required for plugin ${config.name}");

View File

@ -1,9 +1,6 @@
package com.futo.platformplayer.engine.exceptions
import com.caoccao.javet.values.reference.V8ValueObject
import com.futo.platformplayer.engine.IV8PluginConfig
import com.futo.platformplayer.getOrThrow
import java.lang.Exception
open class PluginEngineException(config: IV8PluginConfig, error: String, code: String? = null) : PluginException(config, error, null, code) {

View File

@ -1,9 +1,6 @@
package com.futo.platformplayer.engine.exceptions
import com.caoccao.javet.values.reference.V8ValueObject
import com.futo.platformplayer.engine.IV8PluginConfig
import com.futo.platformplayer.getOrThrow
import java.lang.Exception
class PluginEngineStoppedException(config: IV8PluginConfig, error: String, code: String? = null) : PluginEngineException(config, error, code) {

View File

@ -1,12 +1,8 @@
package com.futo.platformplayer.engine.internal
import com.caoccao.javet.annotations.V8Convert
import com.caoccao.javet.interop.V8Runtime
import com.caoccao.javet.interop.converters.JavetObjectConverter
import com.caoccao.javet.interop.converters.JavetProxyConverter
import com.caoccao.javet.values.V8Value
import com.caoccao.javet.values.reference.V8ValueObject
import com.futo.platformplayer.engine.V8Plugin
class V8Converter : JavetObjectConverter() {
@ -17,11 +13,11 @@ class V8Converter : JavetObjectConverter() {
return null;
val value: V8Value? = super.toV8Value(v8Runtime, obj, depth)
if (value != null && !value.isUndefined)
if (value != null && !value.isUndefined) {
return value as T;
if (obj != null) {
if (obj is IV8Convertable)
return obj.toV8(v8Runtime) as T;
}
if (obj is IV8Convertable) {
return obj.toV8(v8Runtime) as T
}
return null;
}

View File

@ -1,16 +1,11 @@
package com.futo.platformplayer.engine.packages
import android.content.Context
import android.util.Log
import com.caoccao.javet.annotations.V8Allow
import com.caoccao.javet.annotations.V8Convert
import com.caoccao.javet.annotations.V8Function
import com.caoccao.javet.annotations.V8Property
import com.caoccao.javet.enums.V8ConversionMode
import com.caoccao.javet.enums.V8ProxyMode
import com.caoccao.javet.values.reference.V8ValueObject
import com.futo.platformplayer.engine.V8Plugin
import com.futo.platformplayer.engine.dev.V8RemoteObject
import com.futo.platformplayer.engine.internal.V8BindObject
import org.jsoup.Jsoup
import org.jsoup.nodes.Element
@ -20,8 +15,8 @@ class PackageDOMParser : V8Package {
override val name: String get() = "DOMParser";
override val variableName: String = "domParser";
constructor(context: Context, v8Plugin: V8Plugin): super(v8Plugin) {
//v8Plugin.withDependency(context, "/scripts/some/package/path");
constructor(v8Plugin: V8Plugin): super(v8Plugin) {
}
@V8Function
@ -45,7 +40,6 @@ class PackageDOMParser : V8Package {
@V8Property
fun childNodes(): List<DOMNode> {
val results = _element.children().map { DOMNode(_package, it) }.toList();
if(results != null)
_children.addAll(results);
return results;
}

View File

@ -8,18 +8,15 @@ import com.caoccao.javet.enums.V8ProxyMode
import com.caoccao.javet.interop.V8Runtime
import com.caoccao.javet.values.V8Value
import com.caoccao.javet.values.reference.V8ValueObject
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.api.http.ManagedHttpClient
import com.futo.platformplayer.api.media.platforms.js.internal.JSHttpClient
import com.futo.platformplayer.engine.IV8PluginConfig
import com.futo.platformplayer.engine.V8Plugin
import com.futo.platformplayer.engine.internal.IV8Convertable
import com.futo.platformplayer.engine.internal.V8BindObject
import com.futo.platformplayer.getOrThrow
import kotlinx.coroutines.CoroutineScope
import com.futo.platformplayer.logging.Logger
import java.net.SocketTimeoutException
import kotlin.streams.asSequence
import kotlin.streams.toList
class PackageHttp: V8Package {
@Transient
@ -227,10 +224,10 @@ class PackageHttp: V8Package {
return logExceptions {
return@logExceptions catchHttp {
val client = _client;
logRequest(method, url, headers, null);
//logRequest(method, url, headers, null);
val resp = client.requestMethod(method, url, headers);
val responseBody = resp.body?.string();
logResponse(method, url, resp.code, resp.headers, responseBody);
//logResponse(method, url, resp.code, resp.headers, responseBody);
return@catchHttp BridgeHttpResponse(resp.url, resp.code, responseBody, sanitizeResponseHeaders(resp.headers));
}
};
@ -241,10 +238,10 @@ class PackageHttp: V8Package {
return logExceptions {
catchHttp {
val client = _client;
logRequest(method, url, headers, body);
//logRequest(method, url, headers, body);
val resp = client.requestMethod(method, url, body, headers);
val responseBody = resp.body?.string();
logResponse(method, url, resp.code, resp.headers, responseBody);
//logResponse(method, url, resp.code, resp.headers, responseBody);
return@catchHttp BridgeHttpResponse(resp.url, resp.code, responseBody, sanitizeResponseHeaders(resp.headers));
}
};
@ -256,10 +253,10 @@ class PackageHttp: V8Package {
return logExceptions {
catchHttp {
val client = _client;
logRequest("GET", url, headers, null);
//logRequest("GET", url, headers, null);
val resp = client.get(url, headers);
val responseBody = resp.body?.string();
logResponse("GET", url, resp.code, resp.headers, responseBody);
//logResponse("GET", url, resp.code, resp.headers, responseBody);
return@catchHttp BridgeHttpResponse(resp.url, resp.code, responseBody, sanitizeResponseHeaders(resp.headers));
}
};
@ -270,10 +267,10 @@ class PackageHttp: V8Package {
return logExceptions {
catchHttp {
val client = _client;
logRequest("POST", url, headers, body);
//logRequest("POST", url, headers, body);
val resp = client.post(url, body, headers);
val responseBody = resp.body?.string();
logResponse("POST", url, resp.code, resp.headers, responseBody);
//logResponse("POST", url, resp.code, resp.headers, responseBody);
return@catchHttp BridgeHttpResponse(resp.url, resp.code, responseBody, sanitizeResponseHeaders(resp.headers));
}
};
@ -283,7 +280,7 @@ class PackageHttp: V8Package {
fun socket(url: String, headers: Map<String, String>? = null): SocketResult {
val socketHeaders = headers?.toMutableMap() ?: HashMap();
applyDefaultHeaders(socketHeaders);
return SocketResult(this, _client, url, socketHeaders ?: HashMap());
return SocketResult(this, _client, url, socketHeaders);
}
private fun applyDefaultHeaders(headerMap: MutableMap<String, String>) {
@ -305,9 +302,7 @@ class PackageHttp: V8Package {
return result
}
private fun logRequest(method: String, url: String, headers: Map<String, String> = HashMap(), body: String?) {
return;
/*private fun logRequest(method: String, url: String, headers: Map<String, String> = HashMap(), body: String?) {
Logger.v(TAG) {
val stringBuilder = StringBuilder();
stringBuilder.appendLine("HTTP request (useAuth = )");
@ -324,11 +319,9 @@ class PackageHttp: V8Package {
return@v stringBuilder.toString();
};
}
private fun logResponse(method: String, url: String, responseCode: Int? = null, responseHeaders: Map<String, List<String>> = HashMap(), responseBody: String? = null) {
return;
}*/
/*private fun logResponse(method: String, url: String, responseCode: Int? = null, responseHeaders: Map<String, List<String>> = HashMap(), responseBody: String? = null) {
Logger.v(TAG) {
val stringBuilder = StringBuilder();
if (responseCode != null) {
@ -353,7 +346,7 @@ class PackageHttp: V8Package {
return@v stringBuilder.toString();
};
}
}*/
fun <T> logExceptions(handle: ()->T): T {
try {

View File

@ -4,6 +4,6 @@ class RateLimitException : Throwable {
val pluginIds: List<String>;
constructor(pluginIds: List<String>): super() {
this.pluginIds = pluginIds ?: listOf();
this.pluginIds = pluginIds;
}
}

View File

@ -9,11 +9,17 @@ import android.widget.LinearLayout
import android.widget.TextView
import androidx.fragment.app.Fragment
import com.bumptech.glide.Glide
import com.futo.platformplayer.*
import com.futo.platformplayer.R
import com.futo.platformplayer.api.media.models.channels.IPlatformChannel
import com.futo.platformplayer.dp
import com.futo.platformplayer.fixHtmlLinks
import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.resolveChannelUrl
import com.futo.platformplayer.selectBestImage
import com.futo.platformplayer.setPlatformPlayerLinkMovementMethod
import com.futo.platformplayer.states.StateApp
import com.futo.platformplayer.toHumanNumber
import com.futo.platformplayer.views.platform.PlatformLinkView
import com.futo.polycentric.core.toName
import com.futo.polycentric.core.toURLInfoSystemLinkUrl
@ -48,7 +54,7 @@ class ChannelAboutFragment : Fragment, IChannelTabFragment {
setChannel(it);
};
_lastPolycentricProfile?.also {
setPolycentricProfile(it, animate = false);
setPolycentricProfile(it);
}
return view;
@ -108,7 +114,7 @@ class ChannelAboutFragment : Fragment, IChannelTabFragment {
}
fun setPolycentricProfile(polycentricProfile: PolycentricProfile?, animate: Boolean) {
fun setPolycentricProfile(polycentricProfile: PolycentricProfile?) {
_lastPolycentricProfile = polycentricProfile;
if (polycentricProfile == null) {

View File

@ -8,8 +8,6 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.states.StatePlatform
import com.futo.platformplayer.R
import com.futo.platformplayer.Settings
import com.futo.platformplayer.UIDialogs
@ -31,13 +29,15 @@ import com.futo.platformplayer.engine.exceptions.PluginException
import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException
import com.futo.platformplayer.fragment.mainactivity.main.FeedView
import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.states.StateCache
import com.futo.platformplayer.states.StatePlatform
import com.futo.platformplayer.states.StatePolycentric
import com.futo.platformplayer.states.StateSubscriptions
import com.futo.platformplayer.views.FeedStyle
import com.futo.platformplayer.views.adapters.feedtypes.PreviewContentListAdapter
import com.futo.platformplayer.views.adapters.ContentPreviewViewHolder
import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader
import com.futo.platformplayer.views.adapters.feedtypes.PreviewContentListAdapter
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -215,14 +215,14 @@ class ChannelContentsFragment : Fragment(), IChannelTabFragment {
fun setPager(pager: IPager<IPlatformContent>, cache: FeedView.ItemCache<IPlatformContent>? = null) {
if (_pager_parent != null && _pager_parent is IRefreshPager<*>) {
(_pager_parent as IRefreshPager<*>).onPagerError?.remove(this);
(_pager_parent as IRefreshPager<*>).onPagerChanged?.remove(this);
(_pager_parent as IRefreshPager<*>).onPagerError.remove(this);
(_pager_parent as IRefreshPager<*>).onPagerChanged.remove(this);
_pager_parent = null;
}
if(_pager is IReplacerPager<*>)
(_pager as IReplacerPager<*>).onReplaced.remove(this);
var pagerToSet: IPager<IPlatformContent>? = null;
var pagerToSet: IPager<IPlatformContent>?;
if(pager is IRefreshPager<*>) {
_pager_parent = pager;
pagerToSet = pager.getCurrentPager() as IPager<IPlatformContent>;
@ -305,7 +305,7 @@ class ChannelContentsFragment : Fragment(), IChannelTabFragment {
_adapterResults?.setLoading(loading);
}
fun setPolycentricProfile(polycentricProfile: PolycentricProfile?, animate: Boolean) {
fun setPolycentricProfile(polycentricProfile: PolycentricProfile?) {
val p = _lastPolycentricProfile;
if (p != null && polycentricProfile != null && p.system == polycentricProfile.system) {
Logger.i(TAG, "setPolycentricProfile skipped because previous was same");

View File

@ -1,7 +1,6 @@
package com.futo.platformplayer.fragment.channel.tab
import android.os.Bundle
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -9,7 +8,8 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.futo.platformplayer.*
import com.futo.platformplayer.R
import com.futo.platformplayer.UIDialogs
import com.futo.platformplayer.api.media.models.PlatformAuthorLink
import com.futo.platformplayer.api.media.models.channels.IPlatformChannel
import com.futo.platformplayer.constructs.Event1
@ -18,11 +18,10 @@ import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException
import com.futo.platformplayer.fragment.mainactivity.main.ChannelFragment
import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.resolveChannelUrl
import com.futo.platformplayer.states.StatePlatform
import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader
import com.futo.platformplayer.views.adapters.viewholders.CreatorViewHolder
import com.futo.polycentric.core.toUrl
import kotlinx.coroutines.runBlocking
class ChannelListFragment : Fragment, IChannelTabFragment {
private var _channels: ArrayList<IPlatformChannel> = arrayListOf();
@ -84,7 +83,7 @@ class ChannelListFragment : Fragment, IChannelTabFragment {
recyclerCreator.layoutManager = _lm;
_recyclerCreator = recyclerCreator;
_lastChannel?.also { setChannel(it); };
_lastPolycentricProfile?.also { setPolycentricProfile(it, animate = false); }
_lastPolycentricProfile?.also { setPolycentricProfile(it); }
return view;
}
@ -125,7 +124,7 @@ class ChannelListFragment : Fragment, IChannelTabFragment {
}
}
fun setPolycentricProfile(polycentricProfile: PolycentricProfile?, animate: Boolean) {
fun setPolycentricProfile(polycentricProfile: PolycentricProfile?) {
_taskLoadChannel.cancel();
_lastPolycentricProfile = polycentricProfile;

View File

@ -32,7 +32,7 @@ class ChannelMonetizationFragment : Fragment, IChannelTabFragment {
_supportView?.visibility = View.GONE;
_textMonetization?.visibility = View.GONE;
setPolycentricProfile(_lastPolycentricProfile, animate = false);
setPolycentricProfile(_lastPolycentricProfile);
return view;
}
@ -46,14 +46,14 @@ class ChannelMonetizationFragment : Fragment, IChannelTabFragment {
_lastChannel = channel;
}
fun setPolycentricProfile(polycentricProfile: PolycentricProfile?, animate: Boolean) {
fun setPolycentricProfile(polycentricProfile: PolycentricProfile?) {
_lastPolycentricProfile = polycentricProfile
if (polycentricProfile != null) {
_supportView?.setPolycentricProfile(polycentricProfile, animate)
_supportView?.setPolycentricProfile(polycentricProfile)
_supportView?.visibility = View.VISIBLE
_textMonetization?.visibility = View.GONE
} else {
_supportView?.setPolycentricProfile(null, animate)
_supportView?.setPolycentricProfile(null)
_supportView?.visibility = View.GONE
_textMonetization?.visibility = View.VISIBLE
}

View File

@ -7,11 +7,8 @@ import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.view.Gravity
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.*
@ -19,7 +16,6 @@ import androidx.core.animation.doOnEnd
import androidx.lifecycle.lifecycleScope
import com.futo.platformplayer.R
import com.futo.platformplayer.Settings
import com.futo.platformplayer.UIDialogs
import com.futo.platformplayer.activities.MainActivity
import com.futo.platformplayer.activities.SettingsActivity
import com.futo.platformplayer.dp
@ -31,7 +27,6 @@ import com.futo.platformplayer.states.StatePayment
import com.futo.platformplayer.states.StateSubscriptions
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.util.Collections
import kotlin.math.floor
import kotlin.math.roundToInt

View File

@ -63,7 +63,7 @@ class BuyFragment : MainFragment() {
_overlayLoading = findViewById(R.id.overlay_loading);
_overlayPaying = findViewById(R.id.overlay_paying);
_paymentManager = PaymentManager(StatePayment.instance, fragment, _overlayPaying) { success, purchaseId, exception ->
_paymentManager = PaymentManager(StatePayment.instance, fragment, _overlayPaying) { success, _, exception ->
if(success) {
UIDialogs.showDialog(context, R.drawable.ic_check, context.getString(R.string.payment_succeeded), context.getString(R.string.thanks_for_your_purchase_a_key_will_be_sent_to_your_email_after_your_payment_has_been_received), null, 0,
UIDialogs.Action("Ok", {}, UIDialogs.ActionStyle.PRIMARY));
@ -90,7 +90,7 @@ class BuyFragment : MainFragment() {
val currencies = StatePayment.instance.getAvailableCurrencies("grayjay");
val prices = StatePayment.instance.getAvailableCurrencyPrices("grayjay");
val country = StatePayment.instance.getPaymentCountryFromIP()?.let { c -> PaymentConfigurations.COUNTRIES.find { it.id.equals(c, ignoreCase = true) } };
val currency = country?.let { c -> PaymentConfigurations.CURRENCIES.find { it.id == c.defaultCurrencyId && (currencies.contains(it.id) ?: true) } };
val currency = country?.let { c -> PaymentConfigurations.CURRENCIES.find { it.id == c.defaultCurrencyId && (currencies.contains(it.id)) } };
if(currency != null && prices.containsKey(currency.id)) {
val price = prices[currency.id]!!;

View File

@ -3,8 +3,6 @@ package com.futo.platformplayer.fragment.mainactivity.main
import android.annotation.SuppressLint
import android.graphics.drawable.Animatable
import android.os.Bundle
import android.text.Html
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -30,9 +28,9 @@ import com.futo.platformplayer.api.media.models.post.IPlatformPost
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
import com.futo.platformplayer.constructs.TaskHandler
import com.futo.platformplayer.fragment.channel.tab.ChannelAboutFragment
import com.futo.platformplayer.fragment.channel.tab.ChannelContentsFragment
import com.futo.platformplayer.fragment.channel.tab.ChannelListFragment
import com.futo.platformplayer.fragment.channel.tab.ChannelMonetizationFragment
import com.futo.platformplayer.fragment.channel.tab.ChannelContentsFragment
import com.futo.platformplayer.fragment.mainactivity.topbar.NavigationTopBarFragment
import com.futo.platformplayer.images.GlideHelper.Companion.crossfade
import com.futo.platformplayer.logging.Logger
@ -43,10 +41,10 @@ import com.futo.platformplayer.states.StatePlatform
import com.futo.platformplayer.states.StatePlayer
import com.futo.platformplayer.states.StatePlaylists
import com.futo.platformplayer.states.StateSubscriptions
import com.futo.platformplayer.views.others.CreatorThumbnail
import com.futo.platformplayer.views.subscriptions.SubscribeButton
import com.futo.platformplayer.views.adapters.ChannelViewPagerAdapter
import com.futo.platformplayer.views.others.CreatorThumbnail
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuOverlay
import com.futo.platformplayer.views.subscriptions.SubscribeButton
import com.futo.polycentric.core.*
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
@ -54,7 +52,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable
import okhttp3.internal.platform.Platform
@Serializable
data class PolycentricProfile(val system: PublicKey, val systemState: SystemState, val ownedClaims: List<OwnedClaim>);
@ -450,7 +447,7 @@ class ChannelFragment : MainFragment() {
private fun setPolycentricProfileOr(url: String, or: () -> Unit) {
setPolycentricProfile(null, animate = false);
val cachedProfile = channel?.let { PolycentricCache.instance.getCachedProfile(it.url) };
val cachedProfile = channel?.let { PolycentricCache.instance.getCachedProfile(url) };
if (cachedProfile != null) {
setPolycentricProfile(cachedProfile, animate = false);
} else {
@ -492,10 +489,10 @@ class ChannelFragment : MainFragment() {
}
(_viewPager.adapter as ChannelViewPagerAdapter?)?.let {
it.getFragment<ChannelAboutFragment>().setPolycentricProfile(profile, animate);
it.getFragment<ChannelMonetizationFragment>().setPolycentricProfile(profile, animate);
it.getFragment<ChannelListFragment>().setPolycentricProfile(profile, animate);
it.getFragment<ChannelContentsFragment>().setPolycentricProfile(profile, animate);
it.getFragment<ChannelAboutFragment>().setPolycentricProfile(profile);
it.getFragment<ChannelMonetizationFragment>().setPolycentricProfile(profile);
it.getFragment<ChannelListFragment>().setPolycentricProfile(profile);
it.getFragment<ChannelContentsFragment>().setPolycentricProfile(profile);
//TODO: Call on other tabs as needed
}
}

View File

@ -6,10 +6,8 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.lifecycleScope
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.UIDialogs
import com.futo.platformplayer.states.StatePlatform
import com.futo.platformplayer.Settings
import com.futo.platformplayer.UIDialogs
import com.futo.platformplayer.UISlideOverlays
import com.futo.platformplayer.api.media.models.ResultCapabilities
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
@ -18,6 +16,8 @@ import com.futo.platformplayer.constructs.TaskHandler
import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException
import com.futo.platformplayer.fragment.mainactivity.topbar.SearchTopBarFragment
import com.futo.platformplayer.isHttpUrl
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.states.StatePlatform
import com.futo.platformplayer.views.FeedStyle
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -32,7 +32,7 @@ class ContentSearchResultsFragment : MainFragment() {
override fun onShownWithView(parameter: Any?, isBack: Boolean) {
super.onShownWithView(parameter, isBack);
_view?.onShown(parameter, isBack);
_view?.onShown(parameter);
}
override fun onHide() {
@ -110,7 +110,7 @@ class ContentSearchResultsFragment : MainFragment() {
_taskSearch.cancel();
}
fun onShown(parameter: Any?, isBack: Boolean) {
fun onShown(parameter: Any?) {
if(parameter is SuggestionsFragmentData) {
setQuery(parameter.query, false);
setChannelUrl(parameter.channelUrl, false);
@ -127,7 +127,7 @@ class ContentSearchResultsFragment : MainFragment() {
setFilterButtonVisible(true);
onFilterClick.subscribe(this) {
_overlayContainer?.let {
_overlayContainer.let {
val filterValuesCopy = HashMap(_filterValues);
val filtersOverlay = UISlideOverlays.showFiltersOverlay(lifecycleScope, it, _enabledClientIds!!, filterValuesCopy);
filtersOverlay.onOK.subscribe { enabledClientIds, changed ->

View File

@ -6,15 +6,15 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.lifecycleScope
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.UIDialogs
import com.futo.platformplayer.states.StatePlatform
import com.futo.platformplayer.Settings
import com.futo.platformplayer.UIDialogs
import com.futo.platformplayer.api.media.models.PlatformAuthorLink
import com.futo.platformplayer.api.media.structures.IPager
import com.futo.platformplayer.constructs.TaskHandler
import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException
import com.futo.platformplayer.fragment.mainactivity.topbar.SearchTopBarFragment
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.states.StatePlatform
import com.futo.platformplayer.views.FeedStyle
class CreatorSearchResultsFragment : MainFragment() {
@ -26,7 +26,7 @@ class CreatorSearchResultsFragment : MainFragment() {
override fun onShownWithView(parameter: Any?, isBack: Boolean) {
super.onShownWithView(parameter, isBack);
_view?.onShown(parameter, isBack);
_view?.onShown(parameter);
}
override fun onResume() {
@ -69,7 +69,7 @@ class CreatorSearchResultsFragment : MainFragment() {
_taskSearch.cancel();
}
fun onShown(parameter: Any?, isBack: Boolean) {
fun onShown(parameter: Any?) {
if(parameter is String) {
setQuery(parameter);

View File

@ -8,32 +8,24 @@ import android.view.ViewGroup
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.futo.platformplayer.*
import com.futo.platformplayer.activities.CaptchaActivity
import com.futo.platformplayer.api.media.IPlatformClient
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
import com.futo.platformplayer.api.media.platforms.js.JSClient
import com.futo.platformplayer.api.media.platforms.js.internal.JSHttpClient
import com.futo.platformplayer.api.media.structures.EmptyPager
import com.futo.platformplayer.api.media.structures.IPager
import com.futo.platformplayer.constructs.TaskHandler
import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException
import com.futo.platformplayer.engine.exceptions.ScriptExecutionException
import com.futo.platformplayer.engine.exceptions.ScriptImplementationException
import com.futo.platformplayer.fragment.mainactivity.topbar.ImportTopBarFragment
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.others.CaptchaWebViewClient
import com.futo.platformplayer.states.AnnouncementType
import com.futo.platformplayer.states.StateAnnouncement
import com.futo.platformplayer.states.StateMeta
import com.futo.platformplayer.states.StatePlatform
import com.futo.platformplayer.states.StatePlugins
import com.futo.platformplayer.states.StateSubscriptions
import com.futo.platformplayer.views.announcements.AnnouncementView
import com.futo.platformplayer.views.FeedStyle
import com.futo.platformplayer.views.adapters.ContentPreviewViewHolder
import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader
import com.futo.platformplayer.views.adapters.InsertedViewHolder
import com.futo.platformplayer.views.announcements.AnnouncementView
import java.time.OffsetDateTime
import java.util.UUID
@ -159,8 +151,8 @@ class HomeFragment : MainFragment() {
loadResults();
}
override fun filterResults(contents: List<IPlatformContent>): List<IPlatformContent> {
return contents.filter { !StateMeta.instance.isVideoHidden(it.url) && !StateMeta.instance.isCreatorHidden(it.author.url) };
override fun filterResults(results: List<IPlatformContent>): List<IPlatformContent> {
return results.filter { !StateMeta.instance.isVideoHidden(it.url) && !StateMeta.instance.isCreatorHidden(it.author.url) };
}
private fun loadResults() {

View File

@ -14,12 +14,12 @@ import androidx.recyclerview.widget.RecyclerView
import com.futo.platformplayer.*
import com.futo.platformplayer.constructs.TaskHandler
import com.futo.platformplayer.fragment.mainactivity.topbar.ImportTopBarFragment
import com.futo.platformplayer.views.AnyAdapterView
import com.futo.platformplayer.views.AnyAdapterView.Companion.asAny
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.models.Playlist
import com.futo.platformplayer.states.StatePlatform
import com.futo.platformplayer.states.StatePlaylists
import com.futo.platformplayer.views.AnyAdapterView
import com.futo.platformplayer.views.AnyAdapterView.Companion.asAny
import com.futo.platformplayer.views.adapters.viewholders.ImportPlaylistsViewHolder
import com.futo.platformplayer.views.adapters.viewholders.SelectablePlaylist
@ -32,7 +32,7 @@ class ImportPlaylistsFragment : MainFragment() {
override fun onShownWithView(parameter: Any?, isBack: Boolean) {
super.onShownWithView(parameter, isBack);
_view?.onShown(parameter, isBack);
_view?.onShown(parameter);
}
override fun onHide() {
@ -79,7 +79,7 @@ class ImportPlaylistsFragment : MainFragment() {
_spinner = findViewById(R.id.channel_loader);
_adapterView = findViewById<RecyclerView>(R.id.recycler_import).asAny( _items) {
it.onSelectedChange.subscribe { c ->
it.onSelectedChange.subscribe {
updateSelected();
};
};
@ -123,7 +123,7 @@ class ImportPlaylistsFragment : MainFragment() {
_taskLoadPlaylist.cancel();
}
fun onShown(parameter: Any ?, isBack: Boolean) {
fun onShown(parameter: Any?) {
updateSelected();
val itemsRemoved = _items.size;

View File

@ -15,13 +15,13 @@ import com.futo.platformplayer.*
import com.futo.platformplayer.api.media.models.channels.IPlatformChannel
import com.futo.platformplayer.constructs.TaskHandler
import com.futo.platformplayer.fragment.mainactivity.topbar.ImportTopBarFragment
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.states.StatePlatform
import com.futo.platformplayer.states.StateSubscriptions
import com.futo.platformplayer.views.AnyAdapterView
import com.futo.platformplayer.views.AnyAdapterView.Companion.asAny
import com.futo.platformplayer.views.adapters.viewholders.ImportSubscriptionViewHolder
import com.futo.platformplayer.views.adapters.viewholders.SelectableIPlatformChannel
import com.futo.platformplayer.states.StateSubscriptions
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.states.StatePlatform
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@ -37,7 +37,7 @@ class ImportSubscriptionsFragment : MainFragment() {
override fun onShownWithView(parameter: Any?, isBack: Boolean) {
super.onShownWithView(parameter, isBack);
_view?.onShown(parameter, isBack);
_view?.onShown(parameter);
}
override fun onHide() {
@ -93,7 +93,7 @@ class ImportSubscriptionsFragment : MainFragment() {
_loadProgress = findViewById(R.id.text_load_progress);
_adapterView = findViewById<RecyclerView>(R.id.recycler_import).asAny( _items) {
it.onSelectedChange.subscribe { c ->
it.onSelectedChange.subscribe {
updateSelected();
};
};
@ -152,14 +152,14 @@ class ImportSubscriptionsFragment : MainFragment() {
_taskLoadChannel.cancel();
}
fun onShown(parameter: Any ?, isBack: Boolean) {
fun onShown(parameter: Any?) {
_counter = 0;
_limitToastShown = false;
updateSelected();
val itemsRemoved = _items.size;
_items.clear();
_adapterView?.adapter?.notifyItemRangeRemoved(0, itemsRemoved);
_adapterView.adapter?.notifyItemRangeRemoved(0, itemsRemoved);
_links = (parameter as List<String>).filter { i -> !StateSubscriptions.instance.isSubscribed(i) }.toList();
_currentLoadIndex = 0;

View File

@ -41,7 +41,7 @@ class PlaylistFragment : MainFragment() {
override fun onShownWithView(parameter: Any?, isBack: Boolean) {
super.onShownWithView(parameter, isBack);
_view?.onShown(parameter, isBack);
_view?.onShown(parameter);
}
override fun onCreateMainView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
@ -150,7 +150,7 @@ class PlaylistFragment : MainFragment() {
};
}
fun onShown(parameter: Any ?, isBack: Boolean) {
fun onShown(parameter: Any?) {
_taskLoadPlaylist.cancel();
if (parameter is Playlist?) {

View File

@ -6,14 +6,14 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.lifecycleScope
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.UIDialogs
import com.futo.platformplayer.states.StatePlatform
import com.futo.platformplayer.Settings
import com.futo.platformplayer.UIDialogs
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
import com.futo.platformplayer.api.media.structures.IPager
import com.futo.platformplayer.constructs.TaskHandler
import com.futo.platformplayer.fragment.mainactivity.topbar.SearchTopBarFragment
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.states.StatePlatform
import com.futo.platformplayer.views.FeedStyle
class PlaylistSearchResultsFragment : MainFragment() {
@ -25,7 +25,7 @@ class PlaylistSearchResultsFragment : MainFragment() {
override fun onShownWithView(parameter: Any?, isBack: Boolean) {
super.onShownWithView(parameter, isBack);
_view?.onShown(parameter, isBack);
_view?.onShown(parameter);
}
override fun onResume() {
@ -78,7 +78,7 @@ class PlaylistSearchResultsFragment : MainFragment() {
_taskSearch.cancel();
}
fun onShown(parameter: Any?, isBack: Boolean) {
fun onShown(parameter: Any?) {
if(parameter is String) {
setQuery(parameter);

View File

@ -14,21 +14,15 @@ import androidx.constraintlayout.widget.ConstraintLayout
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.futo.platformplayer.states.StatePlayer
import com.futo.platformplayer.states.StatePlaylists
import com.futo.platformplayer.R
import com.futo.platformplayer.UISlideOverlays
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
import com.futo.platformplayer.assume
import com.futo.platformplayer.fragment.mainactivity.topbar.NavigationTopBarFragment
import com.futo.platformplayer.models.Playlist
import com.futo.platformplayer.models.SearchType
import com.futo.platformplayer.states.StatePlatform
import com.futo.platformplayer.states.StatePlayer
import com.futo.platformplayer.states.StatePlaylists
import com.futo.platformplayer.views.adapters.*
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuOverlay
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuTextInput
import com.google.android.material.appbar.AppBarLayout
import kotlin.collections.ArrayList
class PlaylistsFragment : MainFragment() {
@ -52,7 +46,7 @@ class PlaylistsFragment : MainFragment() {
override fun onShownWithView(parameter: Any?, isBack: Boolean) {
super.onShownWithView(parameter, isBack);
_view?.onShown(parameter, isBack);
_view?.onShown();
}
override fun onBackPressed(): Boolean {
@ -133,11 +127,12 @@ class PlaylistsFragment : MainFragment() {
StatePlaylists.instance.onWatchLaterChanged.remove(this);
}
fun onShown(parameter: Any?, isBack: Boolean) {
@SuppressLint("NotifyDataSetChanged")
fun onShown() {
playlists.clear()
playlists.addAll(
StatePlaylists.instance.getPlaylists()
.sortedByDescending { maxOf(it.datePlayed, it.dateUpdate, it.dateCreation) });
StatePlaylists.instance.getPlaylists().sortedByDescending { maxOf(it.datePlayed, it.dateUpdate, it.dateCreation) }
);
_adapterPlaylist.notifyDataSetChanged();
updateWatchLater();

View File

@ -40,15 +40,15 @@ import com.futo.platformplayer.states.StatePlatform
import com.futo.platformplayer.states.StatePolycentric
import com.futo.platformplayer.toHumanNowDiffString
import com.futo.platformplayer.toHumanNumber
import com.futo.platformplayer.views.comments.AddCommentView
import com.futo.platformplayer.views.segments.CommentsList
import com.futo.platformplayer.views.others.CreatorThumbnail
import com.futo.platformplayer.views.platform.PlatformIndicator
import com.futo.platformplayer.views.subscriptions.SubscribeButton
import com.futo.platformplayer.views.others.Toggle
import com.futo.platformplayer.views.adapters.feedtypes.PreviewPostView
import com.futo.platformplayer.views.comments.AddCommentView
import com.futo.platformplayer.views.others.CreatorThumbnail
import com.futo.platformplayer.views.others.Toggle
import com.futo.platformplayer.views.overlays.RepliesOverlay
import com.futo.platformplayer.views.pills.PillRatingLikesDislikes
import com.futo.platformplayer.views.platform.PlatformIndicator
import com.futo.platformplayer.views.segments.CommentsList
import com.futo.platformplayer.views.subscriptions.SubscribeButton
import com.futo.polycentric.core.ApiMethods
import com.futo.polycentric.core.ContentType
import com.futo.polycentric.core.Models
@ -220,7 +220,7 @@ class PostDetailFragment : MainFragment {
root.removeView(layoutTop);
_commentsList.setPrependedView(layoutTop);
_commentsList.onCommentsLoaded.subscribe { count ->
_commentsList.onCommentsLoaded.subscribe {
updateCommentType(false);
};

View File

@ -13,7 +13,9 @@ import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import androidx.lifecycle.lifecycleScope
import com.futo.platformplayer.*
import com.futo.platformplayer.R
import com.futo.platformplayer.Settings
import com.futo.platformplayer.UIDialogs
import com.futo.platformplayer.activities.AddSourceActivity
import com.futo.platformplayer.activities.LoginActivity
import com.futo.platformplayer.api.http.ManagedHttpClient
@ -25,8 +27,8 @@ import com.futo.platformplayer.states.StatePlatform
import com.futo.platformplayer.states.StatePlugins
import com.futo.platformplayer.views.buttons.BigButton
import com.futo.platformplayer.views.buttons.BigButtonGroup
import com.futo.platformplayer.views.sources.SourceHeaderView
import com.futo.platformplayer.views.fields.FieldForm
import com.futo.platformplayer.views.sources.SourceHeaderView
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ -40,7 +42,7 @@ class SourceDetailFragment : MainFragment() {
override fun onShownWithView(parameter: Any?, isBack: Boolean) {
super.onShownWithView(parameter, isBack);
_view?.onShown(parameter, isBack);
_view?.onShown(parameter);
}
override fun onHide() {
@ -92,7 +94,7 @@ class SourceDetailFragment : MainFragment() {
updateSourceViews();
}
fun onShown(parameter: Any?, isBack: Boolean) {
fun onShown(parameter: Any?) {
if (parameter is SourcePluginConfig) {
loadConfig(parameter);
updateSourceViews();
@ -135,7 +137,7 @@ class SourceDetailFragment : MainFragment() {
try {
_settingsAppForm.fromObject(source.descriptor.appSettings);
_settingsAppForm.onChanged.clear();
_settingsAppForm.onChanged.subscribe { field, value ->
_settingsAppForm.onChanged.subscribe { _, _ ->
_settingsAppChanged = true;
}
} catch (e: Throwable) {
@ -150,7 +152,7 @@ class SourceDetailFragment : MainFragment() {
context.getString(R.string.these_settings_are_defined_by_the_plugin)
);
_settingsForm.onChanged.clear();
_settingsForm.onChanged.subscribe { field, value ->
_settingsForm.onChanged.subscribe { _, _ ->
_settingsChanged = true;
}
} catch (e: Throwable) {
@ -478,8 +480,8 @@ class SourceDetailFragment : MainFragment() {
Logger.i(TAG, "Update is available (config.version=${config.version}, source.config.version=${c.version}).");
val c = context ?: return@launch;
val intent = Intent(c, AddSourceActivity::class.java).apply {
val ctx = context ?: return@launch;
val intent = Intent(ctx, AddSourceActivity::class.java).apply {
data = Uri.parse(sourceUrl)
};

View File

@ -1,5 +1,6 @@
package com.futo.platformplayer.fragment.mainactivity.main
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.os.Bundle
@ -10,23 +11,23 @@ import android.widget.LinearLayout
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.futo.platformplayer.UIDialogs
import com.futo.platformplayer.states.StatePlatform
import com.futo.platformplayer.R
import com.futo.platformplayer.UIDialogs
import com.futo.platformplayer.activities.AddSourceOptionsActivity
import com.futo.platformplayer.api.media.IPlatformClient
import com.futo.platformplayer.api.media.platforms.js.JSClient
import com.futo.platformplayer.fragment.mainactivity.topbar.AddTopBarFragment
import com.futo.platformplayer.states.StatePlatform
import com.futo.platformplayer.states.StatePlugins
import com.futo.platformplayer.stores.FragmentedStorage
import com.futo.platformplayer.stores.SubscriptionStorage
import com.futo.platformplayer.views.sources.SourceUnderConstructionView
import com.futo.platformplayer.views.adapters.DisabledSourceView
import com.futo.platformplayer.views.adapters.EnabledSourceAdapter
import com.futo.platformplayer.views.adapters.EnabledSourceViewHolder
import com.futo.platformplayer.views.adapters.ItemMoveCallback
import com.futo.platformplayer.views.sources.SourceUnderConstructionView
import kotlinx.coroutines.runBlocking
import java.util.*
import java.util.Collections
class SourcesFragment : MainFragment() {
override val isMainView : Boolean = true;
@ -159,15 +160,15 @@ class SourcesFragment : MainFragment() {
_didCreateView = true;
}
@SuppressLint("NotifyDataSetChanged")
fun reloadSources() {
enabledSources.clear();
disabledSources.clear();
enabledSources.addAll(StatePlatform.instance.getSortedEnabledClient());
disabledSources.addAll(StatePlatform.instance.getAvailableClients().filter { !enabledSources.contains(it) });
_adapterSourcesEnabled?.notifyDataSetChanged();
_adapterSourcesEnabled.notifyDataSetChanged();
setCanRemove(enabledSources.size > 1);
//_adapterSourcesDisabled?.notifyDataSetChanged();
updateDisabledSources();
if(_didCreateView) {
@ -207,18 +208,15 @@ class SourcesFragment : MainFragment() {
}
private fun setCanRemove(canRemove: Boolean) {
val recyclerSourcesEnabled = _recyclerSourcesEnabled ?: return;
var adapterSourcesEnabled = _adapterSourcesEnabled ?: return;
for (i in 0 until recyclerSourcesEnabled.childCount) {
val view: View = recyclerSourcesEnabled.getChildAt(i)
val viewHolder = recyclerSourcesEnabled.getChildViewHolder(view)
for (i in 0 until _recyclerSourcesEnabled.childCount) {
val view: View = _recyclerSourcesEnabled.getChildAt(i)
val viewHolder = _recyclerSourcesEnabled.getChildViewHolder(view)
if (viewHolder is EnabledSourceViewHolder) {
viewHolder.setCanRemove(canRemove);
}
}
adapterSourcesEnabled.canRemove = canRemove;
_adapterSourcesEnabled.canRemove = canRemove;
}
private fun onPrimaryChanged(client: IPlatformClient) {

View File

@ -27,12 +27,12 @@ import com.futo.platformplayer.states.StatePlatform
import com.futo.platformplayer.states.StateSubscriptions
import com.futo.platformplayer.stores.FragmentedStorage
import com.futo.platformplayer.stores.FragmentedStorageFileJson
import com.futo.platformplayer.views.announcements.AnnouncementView
import com.futo.platformplayer.views.FeedStyle
import com.futo.platformplayer.views.NoResultsView
import com.futo.platformplayer.views.adapters.ContentPreviewViewHolder
import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader
import com.futo.platformplayer.views.adapters.InsertedViewHolder
import com.futo.platformplayer.views.announcements.AnnouncementView
import com.futo.platformplayer.views.buttons.BigButton
import com.futo.platformplayer.views.subscriptions.SubscriptionBar
import kotlinx.coroutines.CancellationException
@ -111,7 +111,7 @@ class SubscriptionsFeedFragment : MainFragment() {
}
};
StateSubscriptions.instance.onSubscriptionsChanged.subscribe(this) { subs, added ->
StateSubscriptions.instance.onSubscriptionsChanged.subscribe(this) { _, added ->
if(!added)
StateSubscriptions.instance.clearSubscriptionFeed();
StateApp.instance.scopeOrNull?.let {
@ -146,7 +146,7 @@ class SubscriptionsFeedFragment : MainFragment() {
val homeTab = Settings.instance.tabs.find { it.id == 0 };
val isHomeEnabled = homeTab?.enabled == true;
if (announcementsView != null && isHomeEnabled) {
headerView?.removeView(announcementsView);
headerView.removeView(announcementsView);
_announcementsView = null;
}
@ -154,7 +154,7 @@ class SubscriptionsFeedFragment : MainFragment() {
val c = context;
if (c != null) {
_announcementsView = AnnouncementView(c, null).apply {
headerView?.addView(this)
headerView.addView(this)
};
}
}
@ -272,18 +272,19 @@ class SubscriptionsFeedFragment : MainFragment() {
}
private fun toggleFilterContentType(contentType: ContentType, isTrue: Boolean) {
synchronized(_filterLock) {
if(!isTrue)
if(!isTrue) {
_filterSettings.allowContentTypes.remove(contentType);
else if(!_filterSettings.allowContentTypes.contains(contentType))
} else if(!_filterSettings.allowContentTypes.contains(contentType)) {
_filterSettings.allowContentTypes.add(contentType)
else null;
}
_filterSettings.save();
};
if(Settings.instance.subscriptions.fetchOnTabOpen) //TODO: Do this different, temporary workaround
if(Settings.instance.subscriptions.fetchOnTabOpen) { //TODO: Do this different, temporary workaround
loadResults(false);
else
} else {
loadCache();
}
}
override fun filterResults(results: List<IPlatformContent>): List<IPlatformContent> {
val nowSoon = OffsetDateTime.now().plusMinutes(5);
@ -381,7 +382,7 @@ class SubscriptionsFeedFragment : MainFragment() {
context?.let {
fragment.lifecycleScope.launch(Dispatchers.Main) {
try {
if (exs!!.size <= 8) {
if (exs.size <= 8) {
for (ex in exs) {
var toShow = ex;
var channel: String? = null;

View File

@ -3,16 +3,17 @@ package com.futo.platformplayer.fragment.mainactivity.main
import android.content.pm.ActivityInfo
import android.content.res.Configuration
import android.os.Bundle
import android.view.*
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.constraintlayout.motion.widget.MotionLayout
import androidx.core.view.*
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.states.StatePlayer
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import com.futo.platformplayer.R
import com.futo.platformplayer.Settings
import com.futo.platformplayer.UIDialogs
import com.futo.platformplayer.activities.MainActivity
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails
import com.futo.platformplayer.casting.CastConnectionState
@ -20,8 +21,10 @@ import com.futo.platformplayer.casting.StateCasting
import com.futo.platformplayer.constructs.Event0
import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.listeners.OrientationManager
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.models.PlatformVideoWithTime
import com.futo.platformplayer.models.UrlVideoWithTime
import com.futo.platformplayer.states.StatePlayer
import com.futo.platformplayer.states.StateSaved
import com.futo.platformplayer.states.VideoToOpen
import com.futo.platformplayer.views.containers.SingleViewTouchableMotionLayout
@ -269,7 +272,7 @@ class VideoDetailFragment : MainFragment {
val viewDetail = _viewDetail;
Logger.i(TAG, "onUserLeaveHint preventPictureInPicture=${viewDetail?.preventPictureInPicture} isCasting=${StateCasting.instance.isCasting} isBackgroundPictureInPicture=${Settings.instance.playback.isBackgroundPictureInPicture()} allowBackground=${viewDetail?.allowBackground}");
if(viewDetail?.preventPictureInPicture == false && !StateCasting.instance.isCasting && Settings.instance.playback.isBackgroundPictureInPicture() && viewDetail?.allowBackground != true) {
if(viewDetail?.preventPictureInPicture == false && !StateCasting.instance.isCasting && Settings.instance.playback.isBackgroundPictureInPicture() && !viewDetail.allowBackground) {
_leavingPiP = false;
val params = _viewDetail?.getPictureInPictureParams();

View File

@ -1,3 +1,5 @@
@file:Suppress("DEPRECATION")
package com.futo.platformplayer.fragment.mainactivity.main
import android.app.PictureInPictureParams
@ -23,16 +25,21 @@ import android.view.View
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.view.WindowManager
import android.widget.*
import android.widget.FrameLayout
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.lifecycle.lifecycleScope
import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition
import com.futo.platformplayer.*
import com.futo.platformplayer.api.media.IPluginSourced
import com.futo.platformplayer.R
import com.futo.platformplayer.Settings
import com.futo.platformplayer.UIDialogs
import com.futo.platformplayer.UISlideOverlays
import com.futo.platformplayer.api.media.IPluginSourced
import com.futo.platformplayer.api.media.LiveChatManager
import com.futo.platformplayer.api.media.PlatformID
import com.futo.platformplayer.api.media.exceptions.ContentNotAvailableYetException
@ -46,12 +53,17 @@ import com.futo.platformplayer.api.media.models.playback.IPlaybackTracker
import com.futo.platformplayer.api.media.models.ratings.RatingLikeDislikes
import com.futo.platformplayer.api.media.models.ratings.RatingLikes
import com.futo.platformplayer.api.media.models.streams.VideoUnMuxedSourceDescriptor
import com.futo.platformplayer.api.media.models.streams.sources.*
import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource
import com.futo.platformplayer.api.media.models.streams.sources.IDashManifestSource
import com.futo.platformplayer.api.media.models.streams.sources.IHLSManifestSource
import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource
import com.futo.platformplayer.api.media.models.streams.sources.LocalAudioSource
import com.futo.platformplayer.api.media.models.streams.sources.LocalSubtitleSource
import com.futo.platformplayer.api.media.models.streams.sources.LocalVideoSource
import com.futo.platformplayer.api.media.models.subtitles.ISubtitleSource
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails
import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo
import com.futo.platformplayer.api.media.platforms.js.JSClient
import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig
import com.futo.platformplayer.api.media.platforms.js.models.JSVideoDetails
import com.futo.platformplayer.api.media.structures.IPager
@ -61,21 +73,41 @@ import com.futo.platformplayer.constructs.Event0
import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.constructs.TaskHandler
import com.futo.platformplayer.downloads.VideoLocal
import com.futo.platformplayer.dp
import com.futo.platformplayer.engine.exceptions.ScriptAgeException
import com.futo.platformplayer.engine.exceptions.ScriptException
import com.futo.platformplayer.engine.exceptions.ScriptImplementationException
import com.futo.platformplayer.engine.exceptions.ScriptLoginRequiredException
import com.futo.platformplayer.engine.exceptions.ScriptUnavailableException
import com.futo.platformplayer.exceptions.UnsupportedCastException
import com.futo.platformplayer.fixHtmlLinks
import com.futo.platformplayer.fixHtmlWhitespace
import com.futo.platformplayer.fullyBackfillServersAnnounceExceptions
import com.futo.platformplayer.getNowDiffSeconds
import com.futo.platformplayer.helpers.VideoHelper
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.models.Subscription
import com.futo.platformplayer.polycentric.PolycentricCache
import com.futo.platformplayer.receivers.MediaControlReceiver
import com.futo.platformplayer.states.*
import com.futo.platformplayer.selectBestImage
import com.futo.platformplayer.states.AnnouncementType
import com.futo.platformplayer.states.StateAnnouncement
import com.futo.platformplayer.states.StateApp
import com.futo.platformplayer.states.StateDownloads
import com.futo.platformplayer.states.StateHistory
import com.futo.platformplayer.states.StatePlatform
import com.futo.platformplayer.states.StatePlayer
import com.futo.platformplayer.states.StatePlaylists
import com.futo.platformplayer.states.StatePlugins
import com.futo.platformplayer.states.StatePolycentric
import com.futo.platformplayer.states.StateSubscriptions
import com.futo.platformplayer.stores.FragmentedStorage
import com.futo.platformplayer.stores.StringArrayStorage
import com.futo.platformplayer.stores.db.types.DBHistory
import com.futo.platformplayer.toHumanBitrate
import com.futo.platformplayer.toHumanNowDiffString
import com.futo.platformplayer.toHumanNumber
import com.futo.platformplayer.toHumanTime
import com.futo.platformplayer.views.MonetizationView
import com.futo.platformplayer.views.behavior.TouchInterceptFrameLayout
import com.futo.platformplayer.views.casting.CastView
@ -87,7 +119,11 @@ import com.futo.platformplayer.views.overlays.LiveChatOverlay
import com.futo.platformplayer.views.overlays.QueueEditorOverlay
import com.futo.platformplayer.views.overlays.RepliesOverlay
import com.futo.platformplayer.views.overlays.SupportOverlay
import com.futo.platformplayer.views.overlays.slideup.*
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuButtonList
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuGroup
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuItem
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuOverlay
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuTitle
import com.futo.platformplayer.views.pills.PillRatingLikesDislikes
import com.futo.platformplayer.views.pills.RoundButton
import com.futo.platformplayer.views.pills.RoundButtonGroup
@ -97,17 +133,25 @@ import com.futo.platformplayer.views.subscriptions.SubscribeButton
import com.futo.platformplayer.views.video.FutoVideoPlayer
import com.futo.platformplayer.views.video.FutoVideoPlayerBase
import com.futo.platformplayer.views.videometa.UpNextView
import com.futo.polycentric.core.*
import com.futo.polycentric.core.ApiMethods
import com.futo.polycentric.core.ContentType
import com.futo.polycentric.core.Models
import com.futo.polycentric.core.Opinion
import com.futo.polycentric.core.toURLInfoSystemLinkUrl
import com.google.android.exoplayer2.C
import com.google.android.exoplayer2.Format
import com.google.android.exoplayer2.ui.PlayerControlView
import com.google.android.exoplayer2.ui.TimeBar
import com.google.android.exoplayer2.upstream.HttpDataSource.InvalidResponseCodeException
import com.google.protobuf.ByteString
import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import userpackage.Protocol
import java.time.OffsetDateTime
import kotlin.collections.ArrayList
import kotlin.math.abs
import kotlin.math.roundToLong
@ -336,7 +380,7 @@ class VideoDetailView : ConstraintLayout {
};
_monetization.onSupportTap.subscribe {
_container_content_support.setPolycentricProfile(_polycentricProfile?.profile, false);
_container_content_support.setPolycentricProfile(_polycentricProfile?.profile);
switchContentView(_container_content_support);
};
@ -484,7 +528,7 @@ class VideoDetailView : ConstraintLayout {
};
if (!isInEditMode) {
StateCasting.instance.onActiveDeviceConnectionStateChanged.subscribe(this) { d, connectionState ->
StateCasting.instance.onActiveDeviceConnectionStateChanged.subscribe(this) { _, connectionState ->
if (_onPauseCalled) {
return@subscribe;
}
@ -530,7 +574,7 @@ class VideoDetailView : ConstraintLayout {
}
_playerProgress.player = _player.exoPlayer?.player;
_playerProgress.setProgressUpdateListener { position, bufferedPosition ->
_playerProgress.setProgressUpdateListener { position, _ ->
StatePlayer.instance.updateMediaSessionPlaybackState(_player.exoPlayer?.getPlaybackStateCompat() ?: PlaybackStateCompat.STATE_NONE, position);
}
@ -658,7 +702,7 @@ class VideoDetailView : ConstraintLayout {
_trackingLastVideoSubscription?.let {
Logger.i(TAG, "Subscription [${it.channel.name}] watch time delta [${delta}]" +
"(${"%.2f".format((_trackingTotalWatched / 1000) / currentVideo.duration.toDouble().coerceAtLeast(1.0))})");
it.updatePlayback(currentVideo, (delta / 1000).toInt());
it.updatePlayback((delta / 1000).toInt());
_trackingTotalWatched += delta;
if(!_trackingDidCountView && currentVideo.duration > 0) {
val percentage = (_trackingTotalWatched / 1000) / currentVideo.duration.toDouble();
@ -1048,6 +1092,7 @@ class VideoDetailView : ConstraintLayout {
switchContentView(_container_content_main);
}
@OptIn(ExperimentalCoroutinesApi::class)
fun setVideoDetails(videoDetail: IPlatformVideoDetails, newVideo: Boolean = false) {
Logger.i(TAG, "setVideoDetails (${videoDetail.name})")
@ -1064,8 +1109,8 @@ class VideoDetailView : ConstraintLayout {
_player.setPlaybackRate(Settings.instance.playback.getDefaultPlaybackSpeed());
}
var videoLocal: VideoLocal? = null;
var video: IPlatformVideoDetails? = null;
val videoLocal: VideoLocal?;
val video: IPlatformVideoDetails?;
if(videoDetail is VideoLocal) {
videoLocal = videoDetail;
@ -1077,7 +1122,7 @@ class VideoDetailView : ConstraintLayout {
return@invokeOnCompletion;
}
val result = videoTask.getCompleted();
if(this.video == videoDetail && result != null && result is IPlatformVideoDetails) {
if(this.video == videoDetail && result is IPlatformVideoDetails) {
this.video = result;
fragment.lifecycleScope.launch(Dispatchers.Main) {
updateQualitySourcesOverlay(result, videoLocal);
@ -1246,7 +1291,6 @@ class VideoDetailView : ConstraintLayout {
_rating.visibility = View.GONE;
}
if (video.rating != null) {
when (video.rating) {
is RatingLikeDislikes -> {
val r = video.rating as RatingLikeDislikes;
@ -1275,9 +1319,6 @@ class VideoDetailView : ConstraintLayout {
_layoutRating.visibility = View.GONE;
}
}
} else {
_layoutRating.visibility = View.GONE;
}
//Overlay
@ -1636,7 +1677,7 @@ class VideoDetailView : ConstraintLayout {
SlideUpMenuGroup(this.context, context.getString(R.string.offline_video), "video",
*localVideoSources
.map {
SlideUpMenuItem(this.context, R.drawable.ic_movie, it!!.name, "${it.width}x${it.height}", it,
SlideUpMenuItem(this.context, R.drawable.ic_movie, it.name, "${it.width}x${it.height}", it,
{ handleSelectVideoTrack(it) });
}.toList().toTypedArray())
else null,
@ -1660,7 +1701,7 @@ class VideoDetailView : ConstraintLayout {
SlideUpMenuGroup(this.context, context.getString(R.string.stream_video), "video",
*liveStreamVideoFormats
.map {
SlideUpMenuItem(this.context, R.drawable.ic_movie, it?.label ?: it.containerMimeType ?: it.bitrate.toString(), "${it.width}x${it.height}", it,
SlideUpMenuItem(this.context, R.drawable.ic_movie, it.label ?: it.containerMimeType ?: it.bitrate.toString(), "${it.width}x${it.height}", it,
{ _player.selectVideoTrack(it.height) });
}.toList().toTypedArray())
else null,
@ -1668,7 +1709,7 @@ class VideoDetailView : ConstraintLayout {
SlideUpMenuGroup(this.context, context.getString(R.string.stream_audio), "audio",
*liveStreamAudioFormats
.map {
SlideUpMenuItem(this.context, R.drawable.ic_music, "${it?.label ?: it.containerMimeType} ${it.bitrate}", "", it,
SlideUpMenuItem(this.context, R.drawable.ic_music, "${it.label ?: it.containerMimeType} ${it.bitrate}", "", it,
{ _player.selectAudioTrack(it.bitrate) });
}.toList().toTypedArray())
else null,
@ -2216,7 +2257,7 @@ class VideoDetailView : ConstraintLayout {
_channelName.text = username
}
_monetization.setPolycentricProfile(cachedPolycentricProfile, animate);
_monetization.setPolycentricProfile(cachedPolycentricProfile);
}
fun setProgressBarOverlayed(isOverlayed: Boolean?) {
@ -2292,13 +2333,13 @@ class VideoDetailView : ConstraintLayout {
StateAnnouncement.instance.registerAnnouncement(video?.id?.value + "_Q_NOSOURCES", context.getString(R.string.video_without_source), context.getString(R.string.there_was_a_in_your_queue_videoname_by_authorname_without_the_required_source_being_enabled_playback_was_skipped).replace("{videoName}", video?.name ?: "").replace("{authorName}", video?.author?.name ?: ""), AnnouncementType.SESSION)
}
}
.exception<ScriptLoginRequiredException> {
Logger.w(TAG, "exception<ScriptLoginRequiredException>", it);
.exception<ScriptLoginRequiredException> { e ->
Logger.w(TAG, "exception<ScriptLoginRequiredException>", e);
UIDialogs.showDialog(context, R.drawable.ic_security, "Authentication", it.message, null, 0,
UIDialogs.showDialog(context, R.drawable.ic_security, "Authentication", e.message, null, 0,
UIDialogs.Action("Cancel", {}),
UIDialogs.Action("Login", {
val id = it.config?.let { if(it is SourcePluginConfig) it.id else null };
val id = e.config.let { if(it is SourcePluginConfig) it.id else null };
val didLogin = if(id == null)
false
else StatePlugins.instance.loginPlugin(context, id) {

View File

@ -52,7 +52,7 @@ abstract class VideoListEditorView : LinearLayout {
_buttonShare.visibility = View.VISIBLE;
}
else
_buttonShare?.visibility = View.GONE;
_buttonShare.visibility = View.GONE;
buttonPlayAll.setOnClickListener { onPlayAllClick(); };
buttonShuffle.setOnClickListener { onShuffleClick(); };
@ -106,11 +106,9 @@ abstract class VideoListEditorView : LinearLayout {
};
} else {
_textMetadata.text = "0 " + context.getString(R.string.videos);
if(_imagePlaylistThumbnail != null) {
Glide.with(_imagePlaylistThumbnail)
.load(R.drawable.placeholder_video_thumbnail)
.into(_imagePlaylistThumbnail);
}
.into(_imagePlaylistThumbnail)
}
_videoListEditorView.setVideos(videos, canEdit);

View File

@ -5,10 +5,10 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.futo.platformplayer.states.StatePlayer
import com.futo.platformplayer.states.StatePlaylists
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo
import com.futo.platformplayer.states.StatePlayer
import com.futo.platformplayer.states.StatePlaylists
class WatchLaterFragment : MainFragment() {
override val isMainView : Boolean = true;
@ -19,7 +19,7 @@ class WatchLaterFragment : MainFragment() {
override fun onShownWithView(parameter: Any?, isBack: Boolean) {
super.onShownWithView(parameter, isBack);
_view?.onShown(parameter, isBack);
_view?.onShown();
}
override fun onCreateMainView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
@ -42,7 +42,7 @@ class WatchLaterFragment : MainFragment() {
}
fun onShown(parameter: Any ?, isBack: Boolean) {
fun onShown() {
setName("Watch Later");
setVideos(StatePlaylists.instance.getWatchLater(), true);
}

View File

@ -6,6 +6,7 @@ import android.view.View
import android.view.ViewGroup
import android.widget.ImageButton
import android.widget.TextView
import androidx.core.content.ContextCompat
import com.futo.platformplayer.R
import com.futo.platformplayer.api.media.IPlatformClient
import com.futo.platformplayer.constructs.Event0
@ -73,9 +74,9 @@ class ImportTopBarFragment : TopFragment() {
fun setImportEnabled(enabled: Boolean) {
if (enabled) {
_textImport?.setTextColor(resources.getColor(R.color.colorPrimary));
_textImport?.setTextColor(ContextCompat.getColor(requireContext(), R.color.colorPrimary));
} else {
_textImport?.setTextColor(resources.getColor(R.color.gray_67));
_textImport?.setTextColor(ContextCompat.getColor(requireContext(), R.color.gray_67));
}
_importEnabled = enabled;

View File

@ -1,9 +1,10 @@
@file:Suppress("DEPRECATION")
package com.futo.platformplayer.helpers
import android.net.Uri
import com.futo.platformplayer.api.media.models.streams.IVideoSourceDescriptor
import com.futo.platformplayer.api.media.models.streams.VideoUnMuxedSourceDescriptor
import com.futo.platformplayer.api.media.models.streams.sources.HLSManifestSource
import com.futo.platformplayer.api.media.models.streams.sources.IAudioSource
import com.futo.platformplayer.api.media.models.streams.sources.IAudioUrlSource
import com.futo.platformplayer.api.media.models.streams.sources.IHLSManifestAudioSource
@ -19,6 +20,7 @@ import com.google.android.exoplayer2.source.MediaSource
import com.google.android.exoplayer2.source.dash.DashMediaSource
import com.google.android.exoplayer2.source.dash.manifest.DashManifestParser
import com.google.android.exoplayer2.upstream.ResolvingDataSource
import kotlin.math.abs
class VideoHelper {
companion object {
@ -43,19 +45,17 @@ class VideoHelper {
fun selectBestVideoSource(desc: IVideoSourceDescriptor, desiredPixelCount : Int, prefContainers : Array<String>) : IVideoSource? = selectBestVideoSource(desc.videoSources.toList(), desiredPixelCount, prefContainers);
fun selectBestVideoSource(sources: Iterable<IVideoSource>, desiredPixelCount : Int, prefContainers : Array<String>) : IVideoSource? {
val targetVideo = if(desiredPixelCount > 0)
sources.toList()
.sortedBy { x -> Math.abs(x.height * x.width - desiredPixelCount) }
.firstOrNull();
else
sources.toList()
.lastOrNull();
val targetVideo = if(desiredPixelCount > 0) {
sources.toList().minByOrNull { x -> abs(x.height * x.width - desiredPixelCount) };
} else {
sources.toList().lastOrNull();
}
val hasPriority = sources.any { it.priority };
val targetPixelCount = if(targetVideo != null) targetVideo.width * targetVideo.height else desiredPixelCount;
val altSources = if(hasPriority) {
sources.filter { it.priority }.sortedBy { x -> Math.abs(x.height * x.width - targetPixelCount) };
sources.filter { it.priority }.sortedBy { x -> abs(x.height * x.width - targetPixelCount) };
} else {
sources.filter { it.height == (targetVideo?.height ?: 0) };
}
@ -76,33 +76,42 @@ class VideoHelper {
fun selectBestAudioSource(desc: IVideoSourceDescriptor, prefContainers : Array<String>, prefLanguage: String? = null, targetBitrate: Long? = null) : IAudioSource? {
if(!desc.isUnMuxed)
return null;
return selectBestAudioSource((desc as VideoUnMuxedSourceDescriptor).audioSources.toList(), prefContainers, prefLanguage);
return selectBestAudioSource((desc as VideoUnMuxedSourceDescriptor).audioSources.toList(), prefContainers, prefLanguage, targetBitrate);
}
fun selectBestAudioSource(altSources : Iterable<IAudioSource>, prefContainers : Array<String>, preferredLanguage: String? = null, targetBitrate: Long? = null) : IAudioSource? {
val languageToFilter = if(preferredLanguage != null && altSources.any { it.language == preferredLanguage })
val languageToFilter = if(preferredLanguage != null && altSources.any { it.language == preferredLanguage }) {
preferredLanguage
else if(preferredLanguage == null) null
else "Unknown";
} else if(preferredLanguage == null) {
null
} else {
"Unknown"
}
var usableSources = if(languageToFilter != null && altSources.any { it.language == languageToFilter })
var usableSources = if(languageToFilter != null && altSources.any { it.language == languageToFilter }) {
altSources.filter { it.language == languageToFilter }.sortedBy { it.bitrate }.toList();
else altSources.sortedBy { it.bitrate };
} else {
altSources.sortedBy { it.bitrate }
}
if(usableSources.any { it.priority })
if(usableSources.any { it.priority }) {
usableSources = usableSources.filter { it.priority };
}
var bestSource = if(targetBitrate != null)
usableSources.minByOrNull { Math.abs(it.bitrate - targetBitrate) };
else
var bestSource = if(targetBitrate != null) {
usableSources.minByOrNull { abs(it.bitrate - targetBitrate) };
} else {
usableSources.lastOrNull();
}
for (prefContainer in prefContainers) {
val betterSources = usableSources.filter { it.container == prefContainer };
val betterSource = if(targetBitrate != null)
betterSources.minByOrNull { Math.abs(it.bitrate - targetBitrate) };
else
val betterSource = if(targetBitrate != null) {
betterSources.minByOrNull { abs(it.bitrate - targetBitrate) };
} else {
betterSources.lastOrNull();
}
if(betterSource != null) {
bestSource = betterSource;
@ -112,17 +121,9 @@ class VideoHelper {
return bestSource;
}
var breakOnce = hashSetOf<String>()
@Suppress("DEPRECATION")
fun convertItagSourceToChunkedDashSource(videoSource: JSVideoUrlRangeSource) : MediaSource {
var urlToUse = videoSource.getVideoUrl();
/*
//TODO: REMOVE THIS, PURPOSELY 403s
if(urlToUse.contains("sig=") && !breakOnce.contains(urlToUse)) {
breakOnce.add(urlToUse);
val sigIndex = urlToUse.indexOf("sig=");
urlToUse = urlToUse.substring(0, sigIndex) + "sig=0" + urlToUse.substring(sigIndex + 4);
}*/
val urlToUse = videoSource.getVideoUrl();
val manifestConfig = ProgressiveDashManifestCreator.fromVideoProgressiveStreamingUrl(urlToUse,
videoSource.duration * 1000,
videoSource.container,
@ -143,13 +144,10 @@ class VideoHelper {
return DashMediaSource.Factory(ResolvingDataSource.Factory(videoSource.getHttpDataSourceFactory(), ResolvingDataSource.Resolver { dataSpec ->
Logger.v("PLAYBACK", "Video REQ Range [" + dataSpec.position + "-" + (dataSpec.position + dataSpec.length) + "](" + dataSpec.length + ")", null);
return@Resolver dataSpec;
}))
.createMediaSource(manifest,
MediaItem.Builder()
.setUri(Uri.parse(videoSource.getVideoUrl()))
.build())
})).createMediaSource(manifest, MediaItem.Builder().setUri(Uri.parse(videoSource.getVideoUrl())).build())
}
@Suppress("DEPRECATION")
fun convertItagSourceToChunkedDashSource(audioSource: JSAudioUrlRangeSource) : MediaSource {
val manifestConfig = ProgressiveDashManifestCreator.fromAudioProgressiveStreamingUrl(audioSource.getAudioUrl(),
audioSource.duration?.times(1000) ?: 0,
@ -170,11 +168,7 @@ class VideoHelper {
return DashMediaSource.Factory(ResolvingDataSource.Factory(audioSource.getHttpDataSourceFactory(), ResolvingDataSource.Resolver { dataSpec ->
Logger.v("PLAYBACK", "Audio REQ Range [" + dataSpec.position + "-" + (dataSpec.position + dataSpec.length) + "](" + dataSpec.length + ")", null);
return@Resolver dataSpec;
}))
.createMediaSource(manifest,
MediaItem.Builder()
.setUri(Uri.parse(audioSource.getAudioUrl()))
.build())
})).createMediaSource(manifest, MediaItem.Builder().setUri(Uri.parse(audioSource.getAudioUrl())).build())
}
}
}

View File

@ -4,7 +4,6 @@ import com.futo.platformplayer.api.media.models.ResultCapabilities
import com.futo.platformplayer.api.media.models.channels.IPlatformChannel
import com.futo.platformplayer.api.media.models.channels.SerializedChannel
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails
import com.futo.platformplayer.getNowDiffDays
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.serializers.OffsetDateTimeSerializer
@ -74,7 +73,7 @@ class Subscription {
this.channel = SerializedChannel.fromChannel(channel);
}
fun updatePlayback(content: IPlatformContentDetails, seconds: Int) {
fun updatePlayback(seconds: Int) {
playbackSeconds += seconds;
}
fun addPlaybackView() {

View File

@ -3,7 +3,6 @@ package com.futo.platformplayer.others
import android.webkit.*
import com.futo.platformplayer.api.media.Serializer
import com.futo.platformplayer.api.media.platforms.js.SourceCaptchaData
import com.futo.platformplayer.api.media.platforms.js.SourcePluginAuthConfig
import com.futo.platformplayer.api.media.platforms.js.SourcePluginCaptchaConfig
import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig
import com.futo.platformplayer.constructs.Event1
@ -58,7 +57,7 @@ class CaptchaWebViewClient : WebViewClient {
if(request == null)
return super.shouldInterceptRequest(view, request as WebResourceRequest?);
val extracted = _extractor.handleRequest(view, request);
val extracted = _extractor.handleRequest(request);
if(extracted != null && !_didNotify) {
_didNotify = true;
onCaptchaFinished.emit(SourceCaptchaData(

View File

@ -1,19 +1,22 @@
package com.futo.platformplayer.others
import android.net.Uri
import android.webkit.*
import android.webkit.CookieManager
import android.webkit.WebResourceRequest
import android.webkit.WebResourceResponse
import android.webkit.WebView
import android.webkit.WebViewClient
import com.futo.platformplayer.BuildConfig
import com.futo.platformplayer.api.http.ManagedHttpClient
import com.futo.platformplayer.api.media.Serializer
import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.api.media.platforms.js.SourceAuth
import com.futo.platformplayer.api.media.platforms.js.SourcePluginAuthConfig
import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig
import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.constructs.Event2
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.matchesDomain
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
class LoginWebViewClient : WebViewClient {
private val LOG_VERBOSE = false;
@ -30,9 +33,9 @@ class LoginWebViewClient : WebViewClient {
_pluginConfig = config;
_authConfig = config.authentication!!;
Logger.i(TAG, "Login [${config.name}]" +
"\nRequired Headers: ${config.authentication?.headersToFind?.joinToString(", ")}" +
"\nRequired Domain Headers: ${Serializer.json.encodeToString(config.authentication?.domainHeadersToFind)}" +
"\nRequired Cookies: ${Serializer.json.encodeToString(config.authentication?.cookiesToFind)}",);
"\nRequired Headers: ${config.authentication.headersToFind?.joinToString(", ")}" +
"\nRequired Domain Headers: ${Serializer.json.encodeToString(config.authentication.domainHeadersToFind)}" +
"\nRequired Cookies: ${Serializer.json.encodeToString(config.authentication.cookiesToFind)}",);
}
constructor(auth: SourcePluginAuthConfig) : super() {
_pluginConfig = null;

View File

@ -3,8 +3,6 @@ package com.futo.platformplayer.others
import android.net.Uri
import android.webkit.CookieManager
import android.webkit.WebResourceRequest
import android.webkit.WebView
import com.futo.platformplayer.api.media.platforms.js.SourceAuth
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.matchesDomain
@ -33,13 +31,15 @@ class WebViewRequirementExtractor {
}
fun handleRequest(view: WebView?, request: WebResourceRequest, logVerbose: Boolean = false): ExtractedData? {
fun handleRequest(request: WebResourceRequest, logVerbose: Boolean = false): ExtractedData? {
val domain = request.url.host;
val domainLower = request.url.host?.lowercase();
if(completionUrl == null)
if (completionUrl == null) {
urlFound = true;
else urlFound = urlFound || request.url == Uri.parse(completionUrl);
} else {
urlFound = urlFound || request.url == Uri.parse(completionUrl)
}
//HEADERS
if(domainLower != null) {

View File

@ -1,22 +1,12 @@
package com.futo.platformplayer.parsers
import android.view.View
import com.futo.platformplayer.R
import com.futo.platformplayer.UIDialogs
import com.futo.platformplayer.api.http.ManagedHttpClient
import com.futo.platformplayer.api.media.models.streams.sources.HLSVariantAudioUrlSource
import com.futo.platformplayer.api.media.models.streams.sources.HLSVariantSubtitleUrlSource
import com.futo.platformplayer.api.media.models.streams.sources.HLSVariantVideoUrlSource
import com.futo.platformplayer.api.media.models.streams.sources.IHLSManifestAudioSource
import com.futo.platformplayer.api.media.models.streams.sources.IHLSManifestSource
import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource
import com.futo.platformplayer.states.StateDownloads
import com.futo.platformplayer.toYesNo
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuGroup
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuItem
import com.futo.platformplayer.yesNoToBoolean
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.net.URI
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
@ -73,7 +63,7 @@ class HLS {
val segments = mutableListOf<Segment>()
var currentSegment: MediaSegment? = null
lines.forEachIndexed { index, line ->
lines.forEach { line ->
when {
line.startsWith("#EXTINF:") -> {
val duration = line.substringAfter(":").substringBefore(",").toDoubleOrNull()

View File

@ -1,7 +1,5 @@
package com.futo.platformplayer.polycentric
import com.futo.polycentric.core.*
import userpackage.Protocol
import com.futo.platformplayer.api.media.PlatformID
import com.futo.platformplayer.constructs.BatchedTaskHandler
import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile
@ -12,9 +10,25 @@ import com.futo.platformplayer.serializers.OffsetDateTimeSerializer
import com.futo.platformplayer.states.StatePolycentric
import com.futo.platformplayer.stores.CachedPolycentricProfileStorage
import com.futo.platformplayer.stores.FragmentedStorage
import com.futo.polycentric.core.ApiMethods
import com.futo.polycentric.core.ContentType
import com.futo.polycentric.core.OwnedClaim
import com.futo.polycentric.core.PublicKey
import com.futo.polycentric.core.SignedEvent
import com.futo.polycentric.core.StorageTypeSystemState
import com.futo.polycentric.core.SystemState
import com.futo.polycentric.core.base64ToByteArray
import com.futo.polycentric.core.base64UrlToByteArray
import com.futo.polycentric.core.getClaimIfValid
import com.futo.polycentric.core.getValidClaims
import com.google.protobuf.ByteString
import kotlinx.coroutines.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.cancel
import kotlinx.serialization.Serializable
import userpackage.Protocol
import java.nio.ByteBuffer
import java.time.OffsetDateTime
import kotlin.system.measureTimeMillis
@ -251,8 +265,11 @@ class PolycentricCache {
Logger.v(TAG, "getProfileAsync (id: $id) != null (with retrieved valid claims)")
return getProfileAsync(claims.ownedClaims.first().system).await()
} else {
if(urlNullCache != null)
_profileUrlCache.setAndSave(urlNullCache, PolycentricCache.CachedPolycentricProfile(null));
synchronized (_cache) {
if (urlNullCache != null) {
_profileUrlCache.setAndSave(urlNullCache, CachedPolycentricProfile(null))
}
}
return null;
}
}

View File

@ -4,9 +4,10 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.pm.PackageInstaller
import com.futo.platformplayer.logging.Logger
import android.os.Build
import com.futo.platformplayer.R
import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.logging.Logger
class InstallReceiver : BroadcastReceiver() {
@ -16,13 +17,19 @@ class InstallReceiver : BroadcastReceiver() {
val onReceiveResult = Event1<String?>();
}
@Suppress("DEPRECATION")
override fun onReceive(context: Context, intent: Intent) {
val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -1);
Logger.i(TAG, "Received status $status.");
when (status) {
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
val activityIntent = intent.getParcelableExtra<Intent>(Intent.EXTRA_INTENT)
val activityIntent: Intent? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java)
} else {
intent.getParcelableExtra(Intent.EXTRA_INTENT)
}
if (activityIntent == null) {
Logger.w(TAG, "Received STATUS_PENDING_USER_ACTION and activity intent is null.")
return;

View File

@ -1,39 +1,37 @@
package com.futo.platformplayer.serializers
import com.futo.platformplayer.api.media.models.ratings.*
import com.futo.platformplayer.api.media.models.ratings.IRating
import com.futo.platformplayer.api.media.models.ratings.RatingLikeDislikes
import com.futo.platformplayer.api.media.models.ratings.RatingLikes
import com.futo.platformplayer.api.media.models.ratings.RatingScaler
import com.futo.platformplayer.api.media.models.ratings.RatingType
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.KSerializer
import kotlinx.serialization.PolymorphicSerializer
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.encoding.decodeStructure
import kotlinx.serialization.json.*
import java.time.LocalDateTime
import java.time.OffsetDateTime
import java.time.ZoneOffset
import kotlin.reflect.KClass
import kotlinx.serialization.json.JsonContentPolymorphicSerializer
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.contentOrNull
import kotlinx.serialization.json.int
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
class IRatingSerializer() : JsonContentPolymorphicSerializer<IRating>(IRating::class) {
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<out IRating> {
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<IRating> {
val obj = element.jsonObject["type"];
if(obj?.jsonPrimitive?.isString ?: true)
return when(obj?.jsonPrimitive?.contentOrNull) {
return if(obj?.jsonPrimitive?.isString != false) {
when (obj?.jsonPrimitive?.contentOrNull) {
"LIKES" -> RatingLikes.serializer();
"LIKEDISLIKES" -> RatingLikeDislikes.serializer();
"SCALE" -> RatingScaler.serializer();
else -> throw NotImplementedError("Rating Value: ${obj?.jsonPrimitive?.contentOrNull}")
};
else
return when(element.jsonObject["type"]?.jsonPrimitive?.int) {
} else {
when (element.jsonObject["type"]?.jsonPrimitive?.int) {
RatingType.LIKES.value -> RatingLikes.serializer();
RatingType.LIKEDISLIKES.value -> RatingLikeDislikes.serializer();
RatingType.SCALE.value -> RatingScaler.serializer();
else -> throw NotImplementedError("Rating Value: ${obj?.jsonPrimitive?.int}")
else -> throw NotImplementedError("Rating Value: ${obj.jsonPrimitive.int}")
};
}
}
}

View File

@ -6,33 +6,40 @@ import com.futo.platformplayer.api.media.models.video.SerializedPlatformNestedCo
import com.futo.platformplayer.api.media.models.video.SerializedPlatformPost
import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.json.*
import kotlinx.serialization.json.JsonContentPolymorphicSerializer
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.booleanOrNull
import kotlinx.serialization.json.contentOrNull
import kotlinx.serialization.json.int
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
class PlatformContentSerializer() : JsonContentPolymorphicSerializer<SerializedPlatformContent>(SerializedPlatformContent::class) {
class PlatformContentSerializer : JsonContentPolymorphicSerializer<SerializedPlatformContent>(SerializedPlatformContent::class) {
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<out SerializedPlatformContent> {
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<SerializedPlatformContent> {
val obj = element.jsonObject["contentType"];
//TODO: Remove this temporary fallback..at some point
if(obj == null && element.jsonObject["isLive"]?.jsonPrimitive?.booleanOrNull != null)
return SerializedPlatformVideo.serializer();
if(obj?.jsonPrimitive?.isString ?: true)
return when(obj?.jsonPrimitive?.contentOrNull) {
if(obj?.jsonPrimitive?.isString != false) {
return when (obj?.jsonPrimitive?.contentOrNull) {
"MEDIA" -> SerializedPlatformVideo.serializer();
"NESTED_VIDEO" -> SerializedPlatformNestedContent.serializer();
"ARTICLE" -> throw NotImplementedError("Articles not yet implemented");
"POST" -> SerializedPlatformPost.serializer();
else -> throw NotImplementedError("Unknown Content Type Value: ${obj?.jsonPrimitive?.contentOrNull}")
};
else
return when(obj?.jsonPrimitive?.int) {
} else {
return when (obj.jsonPrimitive.int) {
ContentType.MEDIA.value -> SerializedPlatformVideo.serializer();
ContentType.NESTED_VIDEO.value -> SerializedPlatformNestedContent.serializer();
ContentType.ARTICLE.value -> throw NotImplementedError("Articles not yet implemented");
ContentType.POST.value -> SerializedPlatformPost.serializer();
else -> throw NotImplementedError("Unknown Content Type Value: ${obj?.jsonPrimitive?.int}")
else -> throw NotImplementedError("Unknown Content Type Value: ${obj.jsonPrimitive.int}")
};
}
}
}

View File

@ -4,12 +4,16 @@ import com.futo.platformplayer.api.media.models.video.ISerializedVideoSourceDesc
import com.futo.platformplayer.api.media.models.video.SerializedVideoMuxedSourceDescriptor
import com.futo.platformplayer.api.media.models.video.SerializedVideoNonMuxedSourceDescriptor
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.json.*
import kotlinx.serialization.json.JsonContentPolymorphicSerializer
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.boolean
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
class VideoDescriptorSerializer() : JsonContentPolymorphicSerializer<ISerializedVideoSourceDescriptor>(ISerializedVideoSourceDescriptor::class) {
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<out ISerializedVideoSourceDescriptor> {
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<ISerializedVideoSourceDescriptor> {
return when(element.jsonObject["isUnMuxed"]?.jsonPrimitive?.boolean) {
false -> SerializedVideoMuxedSourceDescriptor.serializer();
true -> SerializedVideoNonMuxedSourceDescriptor.serializer();

View File

@ -7,15 +7,16 @@ import android.app.Service
import android.content.Context
import android.content.Intent
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK
import android.os.Build
import android.os.IBinder
import androidx.core.app.NotificationCompat
import com.futo.platformplayer.*
import com.futo.platformplayer.R
import com.futo.platformplayer.Settings
import com.futo.platformplayer.activities.MainActivity
import com.futo.platformplayer.api.http.ManagedHttpClient
import com.futo.platformplayer.downloads.VideoDownload
import com.futo.platformplayer.exceptions.DownloadException
import com.futo.platformplayer.getNowDiffMinutes
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.states.Announcement
import com.futo.platformplayer.states.AnnouncementType
@ -150,10 +151,16 @@ class DownloadService : Service() {
currentVideo.changeState(VideoDownload.State.ERROR);
ignore.add(currentVideo);
if(ex !is CancellationException)
StateAnnouncement.instance.registerAnnouncement(currentVideo?.id?.value?:"" + currentVideo?.id?.pluginId?:"" + "_FailDownload",
if(ex !is CancellationException) {
StateAnnouncement.instance.registerAnnouncement(
currentVideo.id.value ?: ("" + currentVideo.id.pluginId),
"Download failed",
"Download for [${currentVideo.name}] failed.\nDownloads are automatically retried.\nReason: ${ex.message}", AnnouncementType.SESSION, null, "download");
"Download for [${currentVideo.name}] failed.\nDownloads are automatically retried.\nReason: ${ex.message}",
AnnouncementType.SESSION,
null,
"download"
);
}
//Give it a sec
Thread.sleep(500);
@ -262,7 +269,7 @@ class DownloadService : Service() {
fun closeDownloadSession() {
Logger.i(TAG, "closeDownloadSession");
stopForeground(true);
stopForeground(STOP_FOREGROUND_DETACH);
_notificationManager?.cancel(DOWNLOAD_NOTIF_ID);
stopService();
_started = false;

View File

@ -6,23 +6,26 @@ import android.app.PendingIntent
import android.app.Service
import android.content.Context
import android.content.Intent
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.content.pm.ServiceInfo
import android.os.Build
import android.os.IBinder
import androidx.core.app.NotificationCompat
import androidx.core.content.FileProvider
import com.futo.platformplayer.*
import com.futo.platformplayer.R
import com.futo.platformplayer.activities.MainActivity
import com.futo.platformplayer.api.http.ManagedHttpClient
import com.futo.platformplayer.downloads.VideoExport
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.share
import com.futo.platformplayer.states.Announcement
import com.futo.platformplayer.states.AnnouncementType
import com.futo.platformplayer.states.StateAnnouncement
import com.futo.platformplayer.states.StateDownloads
import com.futo.platformplayer.stores.FragmentedStorage
import kotlinx.coroutines.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.time.OffsetDateTime
import java.util.UUID
@ -184,7 +187,7 @@ class ExportingService : Service() {
fun closeExportSession() {
Logger.i(TAG, "closeExportSession");
stopForeground(true);
stopForeground(STOP_FOREGROUND_DETACH);
_notificationManager?.cancel(EXPORT_NOTIF_ID);
stopService();
_started = false;

View File

@ -1,6 +1,10 @@
package com.futo.platformplayer.services
import android.app.*
import android.app.ActivityManager
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.Context
import android.content.Intent
import android.content.pm.ServiceInfo
@ -21,14 +25,14 @@ import androidx.core.app.NotificationCompat
import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.R
import com.futo.platformplayer.Settings
import com.futo.platformplayer.states.StatePlatform
import com.futo.platformplayer.states.StatePlayer
import com.futo.platformplayer.activities.MainActivity
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.receivers.MediaControlReceiver
import com.futo.platformplayer.states.StatePlatform
import com.futo.platformplayer.states.StatePlayer
import com.futo.platformplayer.stores.FragmentedStorage
class MediaPlaybackService : Service() {
@ -148,7 +152,7 @@ class MediaPlaybackService : Service() {
fun closeMediaSession() {
Logger.v(TAG, "closeMediaSession");
stopForeground(true);
stopForeground(STOP_FOREGROUND_DETACH);
val focusRequest = _focusRequest;
if (focusRequest != null) {
@ -214,7 +218,7 @@ class MediaPlaybackService : Service() {
else
notifyMediaSession(video, null);
}
private fun generateMediaAction(context: Context, icon: Int, title: String, intent: PendingIntent) : NotificationCompat.Action {
private fun generateMediaAction(icon: Int, title: String, intent: PendingIntent) : NotificationCompat.Action {
return NotificationCompat.Action.Builder(icon, title, intent).build();
}
private fun notifyMediaSession(video: IPlatformVideo?, desiredBitmap: Bitmap?) {
@ -259,17 +263,37 @@ class MediaPlaybackService : Service() {
val playWhenReady = StatePlayer.instance.isPlaying;
if(hasQueue)
builder = builder.addAction(generateMediaAction(this, R.drawable.ic_fast_rewind_notif, "Back", MediaControlReceiver.getPrevIntent(this, 3)))
builder = builder.addAction(generateMediaAction(
R.drawable.ic_fast_rewind_notif,
"Back",
MediaControlReceiver.getPrevIntent(this, 3)
))
if(playWhenReady)
builder = builder.addAction(generateMediaAction(this, R.drawable.ic_pause_notif, "Pause", MediaControlReceiver.getPauseIntent(this, 2)));
builder = builder.addAction(generateMediaAction(
R.drawable.ic_pause_notif,
"Pause",
MediaControlReceiver.getPauseIntent(this, 2)
));
else
builder = builder.addAction(generateMediaAction(this, R.drawable.ic_play_notif, "Play", MediaControlReceiver.getPlayIntent(this, 1)));
builder = builder.addAction(generateMediaAction(
R.drawable.ic_play_notif,
"Play",
MediaControlReceiver.getPlayIntent(this, 1)
));
if(hasQueue)
builder = builder.addAction(generateMediaAction(this, R.drawable.ic_fast_forward_notif, "Forward", MediaControlReceiver.getNextIntent(this, 4)));
builder = builder.addAction(generateMediaAction(
R.drawable.ic_fast_forward_notif,
"Forward",
MediaControlReceiver.getNextIntent(this, 4)
));
builder = builder.addAction(generateMediaAction(this, R.drawable.ic_stop_notif, "Stop", MediaControlReceiver.getCloseIntent(this, 5)));
builder = builder.addAction(generateMediaAction(
R.drawable.ic_stop_notif,
"Stop",
MediaControlReceiver.getCloseIntent(this, 5)
));
if(bitmap?.isRecycled ?: false)
bitmap = null;

View File

@ -1,19 +1,15 @@
package com.futo.platformplayer.states
import android.content.Context
import com.futo.platformplayer.UIDialogs
import com.futo.platformplayer.api.http.ManagedHttpClient
import com.futo.platformplayer.constructs.Event0
import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.serializers.OffsetDateTimeNullableSerializer
import com.futo.platformplayer.stores.FragmentedStorage
import com.futo.platformplayer.stores.StringHashSetStorage
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import java.time.OffsetDateTime
import java.util.Random
@ -70,9 +66,7 @@ class StateAnnouncement {
synchronized(_lock) {
val idActual = id ?: UUID.randomUUID().toString();
val announcement = SessionAnnouncement(idActual, title, msg, announceType, time, category, actionButton, idActual);
if(action != null)
_sessionActions.put(idActual, action);
_sessionActions[idActual] = action;
registerAnnouncementSession(announcement);
}
}
@ -81,21 +75,22 @@ class StateAnnouncement {
val idActual = id ?: UUID.randomUUID().toString();
val announcement = SessionAnnouncement(idActual, title, msg, announceType, time, category, actionButton, idActual, cancelButton, if(cancelAction != null) idActual + "_cancel" else null);
if(action != null)
_sessionActions.put(idActual, action);
if(cancelAction != null)
if(cancelAction != null) {
_sessionActions.put(idActual + "_cancel", cancelAction);
}
registerAnnouncementSession(announcement);
}
}
fun registerAnnouncement(id: String?, title: String, msg: String, announceType: AnnouncementType = AnnouncementType.DELETABLE, time: OffsetDateTime? = null, category: String? = null, actionButton: String? = null, actionId: String? = null) {
val newAnnouncement = Announcement(if(id == null) UUID.randomUUID().toString() else id, title, msg, announceType, time, category, actionButton, actionId);
val newAnnouncement = Announcement(id ?: UUID.randomUUID().toString(), title, msg, announceType, time, category, actionButton, actionId);
if(announceType == AnnouncementType.SESSION || announceType == AnnouncementType.SESSION_RECURRING)
if(announceType == AnnouncementType.SESSION || announceType == AnnouncementType.SESSION_RECURRING) {
registerAnnouncementSession(newAnnouncement);
else
} else {
registerAnnouncement(newAnnouncement);
}
}
fun registerAnnouncementSession(announcement: Announcement) {
synchronized(_lock) {
_sessionAnnouncements.put(announcement.id, announcement);

View File

@ -13,7 +13,6 @@ import android.net.NetworkRequest
import android.net.Uri
import android.provider.DocumentsContract
import android.util.DisplayMetrics
import android.util.Xml
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
@ -23,33 +22,23 @@ import com.futo.platformplayer.R
import com.futo.platformplayer.activities.CaptchaActivity
import com.futo.platformplayer.activities.IWithResultLauncher
import com.futo.platformplayer.activities.MainActivity
import com.futo.platformplayer.api.media.models.video.SerializedPlatformContent
import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo
import com.futo.platformplayer.api.media.platforms.js.DevJSClient
import com.futo.platformplayer.api.media.platforms.js.JSClient
import com.futo.platformplayer.background.BackgroundWorker
import com.futo.platformplayer.casting.StateCasting
import com.futo.platformplayer.constructs.Event0
import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException
import com.futo.platformplayer.engine.exceptions.ScriptLoginRequiredException
import com.futo.platformplayer.fragment.mainactivity.main.HomeFragment
import com.futo.platformplayer.fragment.mainactivity.main.SourceDetailFragment
import com.futo.platformplayer.logging.AndroidLogConsumer
import com.futo.platformplayer.logging.FileLogConsumer
import com.futo.platformplayer.logging.LogLevel
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.models.HistoryVideo
import com.futo.platformplayer.receivers.AudioNoisyReceiver
import com.futo.platformplayer.serializers.PlatformContentSerializer
import com.futo.platformplayer.services.DownloadService
import com.futo.platformplayer.stores.FragmentedStorage
import com.futo.platformplayer.stores.db.ManagedDBStore
import com.futo.platformplayer.stores.db.types.DBHistory
import com.futo.platformplayer.stores.v2.JsonStoreSerializer
import com.futo.platformplayer.stores.v2.ManagedStore
import kotlinx.coroutines.*
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import java.io.File
import java.time.OffsetDateTime
import java.util.*
@ -393,7 +382,7 @@ class StateApp {
scopeOrNull?.launch(Dispatchers.Main) {
try {
if (it != null) {
UIDialogs.toast("Uploaded " + (it ?: "null"), true);
UIDialogs.toast("Uploaded $it", true);
} else {
UIDialogs.toast("Failed to upload");
}
@ -752,9 +741,6 @@ class StateApp {
})
}
}
fun handleLoginException(client: JSClient, exception: ScriptLoginRequiredException, onSuccess: (client: JSClient)->Unit) {
}
fun getLocaleContext(baseContext: Context?): Context? {
val locale = getLocaleSetting(baseContext);

View File

@ -2,7 +2,6 @@ package com.futo.platformplayer.states
import android.content.Context
import kotlin.streams.asSequence
import kotlin.streams.toList
/***
* Used to read assets
@ -33,28 +32,28 @@ class StateAssets {
/**
* Does basic asset resolving under certain conditions
*/
fun readAssetRelative(context: Context, base: String, path: String, cache: Boolean = false) : String? {
fun readAssetRelative(context: Context, base: String, path: String) : String? {
val finalPath = resolvePath(base, path);
return readAsset(context, finalPath, cache);
return readAsset(context, finalPath);
}
fun readAssetBinRelative(context: Context, base: String, path: String) : ByteArray? {
val finalPath = resolvePath(base, path);
return readAssetBin(context, finalPath);
}
fun readAsset(context: Context, path: String, cache: Boolean = false) : String? {
var text: String? = null;
fun readAsset(context: Context, path: String) : String? {
var text: String?;
synchronized(_cache) {
if (!_cache.containsKey(path)) {
text = context
?.assets
text = context.assets
?.open(path)
?.bufferedReader()
?.use { it.readText(); };
_cache.put(path, text);
} else {
text = _cache[path];
}
else
text = _cache.get(path);
}
return text;
}

Some files were not shown because too many files have changed in this diff Show More