mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2026-05-16 04:52:39 +02:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 76f5962232 | |||
| 30df22d225 | |||
| cd4295be59 | |||
| 7d366110b1 | |||
| 35c5045b3f | |||
| 4930ea8183 | |||
| 02292fed04 | |||
| bf6e61ed90 | |||
| 2ac8e0e621 | |||
| 0432f06eb3 | |||
| 7bfab8409f | |||
| 52d833d726 | |||
| 14d579eb1b | |||
| d3ab8ecf3a | |||
| 627b8c2b5d | |||
| 7f1cb22c12 | |||
| 5551bd31fe | |||
| 189d855c3f | |||
| 0ab52e8f4d | |||
| 27eb5aa6e1 | |||
| 49b5b16641 | |||
| 73dd52af28 | |||
| 3b8d256bad | |||
| 5d7dc1fdcb | |||
| f31b6c50e9 | |||
| fa12f8277c | |||
| 150a7d5006 |
+8
-7
@@ -172,15 +172,16 @@ dependencies {
|
||||
implementation("com.caoccao.javet:javet-android:2.2.1")
|
||||
|
||||
//Exoplayer
|
||||
implementation 'com.google.android.exoplayer:exoplayer-core:2.19.1'
|
||||
implementation 'com.google.android.exoplayer:exoplayer-dash:2.19.1'
|
||||
implementation 'com.google.android.exoplayer:exoplayer-ui:2.19.1'
|
||||
implementation 'com.google.android.exoplayer:exoplayer-hls:2.19.1'
|
||||
implementation 'com.google.android.exoplayer:exoplayer-rtsp:2.19.1'
|
||||
implementation 'com.google.android.exoplayer:exoplayer-smoothstreaming:2.19.1'
|
||||
implementation 'com.google.android.exoplayer:exoplayer-transformer:2.19.1'
|
||||
implementation 'androidx.media3:media3-exoplayer:1.2.0'
|
||||
implementation 'androidx.media3:media3-exoplayer-dash:1.2.0'
|
||||
implementation 'androidx.media3:media3-ui:1.2.0'
|
||||
implementation 'androidx.media3:media3-exoplayer-hls:1.2.0'
|
||||
implementation 'androidx.media3:media3-exoplayer-rtsp:1.2.0'
|
||||
implementation 'androidx.media3:media3-exoplayer-smoothstreaming:1.2.0'
|
||||
implementation 'androidx.media3:media3-transformer:1.2.0'
|
||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.7.5'
|
||||
implementation 'androidx.navigation:navigation-ui-ktx:2.7.5'
|
||||
implementation 'androidx.media:media:1.7.0'
|
||||
|
||||
//Other
|
||||
implementation 'org.jmdns:jmdns:3.5.1'
|
||||
|
||||
@@ -7,11 +7,12 @@
|
||||
<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"/>
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"/>
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
@@ -36,9 +37,11 @@
|
||||
android:enabled="true"
|
||||
android:foregroundServiceType="mediaPlayback" />
|
||||
<service android:name=".services.DownloadService"
|
||||
android:enabled="true" />
|
||||
android:enabled="true"
|
||||
android:foregroundServiceType="dataSync" />
|
||||
<service android:name=".services.ExportingService"
|
||||
android:enabled="true" />
|
||||
android:enabled="true"
|
||||
android:foregroundServiceType="dataSync" />
|
||||
|
||||
<receiver android:name=".receivers.MediaControlReceiver" />
|
||||
<receiver android:name=".receivers.AudioNoisyReceiver" />
|
||||
|
||||
@@ -43,18 +43,19 @@ let Type = {
|
||||
|
||||
let Language = {
|
||||
UNKNOWN: "Unknown",
|
||||
ARABIC: "Arabic",
|
||||
SPANISH: "Spanish",
|
||||
FRENCH: "French",
|
||||
HINDI: "Hindi",
|
||||
INDONESIAN: "Indonesian",
|
||||
KOREAN: "Korean",
|
||||
PORTBRAZIL: "Portuguese Brazilian",
|
||||
RUSSIAN: "Russian",
|
||||
THAI: "Thai",
|
||||
TURKISH: "Turkish",
|
||||
VIETNAMESE: "Vietnamese",
|
||||
ENGLISH: "English"
|
||||
ARABIC: "ar",
|
||||
SPANISH: "es",
|
||||
FRENCH: "fr",
|
||||
HINDI: "hi",
|
||||
INDONESIAN: "id",
|
||||
KOREAN: "ko",
|
||||
PORTUGUESE: "pt",
|
||||
PORTBRAZIL: "pt",
|
||||
RUSSIAN: "ru",
|
||||
THAI: "th",
|
||||
TURKISH: "tr",
|
||||
VIETNAMESE: "vi",
|
||||
ENGLISH: "en"
|
||||
}
|
||||
|
||||
class ScriptException extends Error {
|
||||
|
||||
@@ -1,15 +1,28 @@
|
||||
package com.futo.platformplayer
|
||||
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.datasource.DefaultHttpDataSource
|
||||
import androidx.media3.datasource.HttpDataSource
|
||||
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.IAudioSource
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource
|
||||
import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails
|
||||
import com.futo.platformplayer.api.media.platforms.js.models.sources.JSSource
|
||||
import com.futo.platformplayer.helpers.VideoHelper
|
||||
import com.futo.platformplayer.views.video.datasources.JSHttpDataSource
|
||||
|
||||
fun IPlatformVideoDetails.isDownloadable(): Boolean = VideoHelper.isDownloadable(this);
|
||||
fun IVideoSource.isDownloadable(): Boolean = VideoHelper.isDownloadable(this);
|
||||
fun IAudioSource.isDownloadable(): Boolean = VideoHelper.isDownloadable(this);
|
||||
|
||||
@UnstableApi
|
||||
fun JSSource.getHttpDataSourceFactory(): HttpDataSource.Factory {
|
||||
val requestModifier = getRequestModifier();
|
||||
return if (requestModifier != null) {
|
||||
JSHttpDataSource.Factory().setRequestModifier(requestModifier);
|
||||
} else {
|
||||
DefaultHttpDataSource.Factory();
|
||||
}
|
||||
}
|
||||
|
||||
fun IVideoSourceDescriptor.hasAnySource(): Boolean = this.videoSources.any() || (this is VideoUnMuxedSourceDescriptor && this.audioSources.any());
|
||||
@@ -13,7 +13,6 @@ import java.text.DecimalFormat
|
||||
import java.time.OffsetDateTime
|
||||
import java.time.temporal.ChronoUnit
|
||||
import kotlin.math.abs
|
||||
import kotlin.time.toDuration
|
||||
|
||||
|
||||
//Long
|
||||
@@ -228,6 +227,14 @@ fun String.fixHtmlWhitespace(): Spanned {
|
||||
return Html.fromHtml(replace("\n", "<br />"), HtmlCompat.FROM_HTML_MODE_LEGACY);
|
||||
}
|
||||
|
||||
fun Long.formatDuration(): String {
|
||||
val hours = this / 3600000
|
||||
val minutes = (this % 3600000) / 60000
|
||||
val seconds = (this % 60000) / 1000
|
||||
|
||||
return String.format("%02d:%02d:%02d", hours, minutes, seconds)
|
||||
}
|
||||
|
||||
fun String.fixHtmlLinks(): Spanned {
|
||||
//TODO: Properly fix whitespace handling.
|
||||
val doc = Jsoup.parse(replace("\n", "<br />"));
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.futo.platformplayer
|
||||
|
||||
class PresetImages {
|
||||
companion object {
|
||||
val images = mapOf<String, Int>(
|
||||
Pair("xp_book", R.drawable.xp_book),
|
||||
Pair("xp_forest", R.drawable.xp_forest),
|
||||
Pair("xp_code", R.drawable.xp_code),
|
||||
Pair("xp_controller", R.drawable.xp_controller),
|
||||
Pair("xp_laptop", R.drawable.xp_laptop)
|
||||
);
|
||||
|
||||
fun getPresetResIdByName(name: String): Int {
|
||||
return images[name] ?: -1;
|
||||
}
|
||||
fun getPresetNameByResId(id: Int): String? {
|
||||
return images.entries.find { it.value == id }?.key;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,28 +6,41 @@ import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.webkit.CookieManager
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.futo.platformplayer.activities.*
|
||||
import com.futo.platformplayer.activities.MainActivity
|
||||
import com.futo.platformplayer.activities.ManageTabsActivity
|
||||
import com.futo.platformplayer.activities.PolycentricHomeActivity
|
||||
import com.futo.platformplayer.activities.PolycentricProfileActivity
|
||||
import com.futo.platformplayer.activities.SettingsActivity
|
||||
import com.futo.platformplayer.api.http.ManagedHttpClient
|
||||
import com.futo.platformplayer.constructs.Event0
|
||||
import com.futo.platformplayer.fragment.mainactivity.bottombar.MenuBottomBarFragment
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
import com.futo.platformplayer.serializers.FlexibleBooleanSerializer
|
||||
import com.futo.platformplayer.serializers.OffsetDateTimeSerializer
|
||||
import com.futo.platformplayer.states.*
|
||||
import com.futo.platformplayer.states.StateAnnouncement
|
||||
import com.futo.platformplayer.states.StateApp
|
||||
import com.futo.platformplayer.states.StateBackup
|
||||
import com.futo.platformplayer.states.StateCache
|
||||
import com.futo.platformplayer.states.StateMeta
|
||||
import com.futo.platformplayer.states.StatePayment
|
||||
import com.futo.platformplayer.states.StatePolycentric
|
||||
import com.futo.platformplayer.states.StateUpdate
|
||||
import com.futo.platformplayer.stores.FragmentedStorage
|
||||
import com.futo.platformplayer.stores.FragmentedStorageFileJson
|
||||
import com.futo.platformplayer.views.FeedStyle
|
||||
import com.futo.platformplayer.views.fields.DropdownFieldOptionsId
|
||||
import com.futo.platformplayer.views.fields.FormField
|
||||
import com.futo.platformplayer.views.fields.FieldForm
|
||||
import com.futo.platformplayer.views.fields.FormField
|
||||
import com.futo.platformplayer.views.fields.FormFieldButton
|
||||
import com.futo.platformplayer.views.fields.FormFieldWarning
|
||||
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuItem
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.*
|
||||
import kotlinx.serialization.json.*
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.io.File
|
||||
import java.time.OffsetDateTime
|
||||
|
||||
@@ -248,20 +261,23 @@ class Settings : FragmentedStorageFileJson() {
|
||||
return FeedStyle.THUMBNAIL;
|
||||
}
|
||||
|
||||
@FormField(R.string.preview_feed_items, FieldForm.TOGGLE, R.string.preview_feed_items_description, 5)
|
||||
@FormField(R.string.show_subscription_group, FieldForm.TOGGLE, R.string.show_subscription_group_description, 5)
|
||||
var showSubscriptionGroups: Boolean = true;
|
||||
|
||||
@FormField(R.string.preview_feed_items, FieldForm.TOGGLE, R.string.preview_feed_items_description, 6)
|
||||
var previewFeedItems: Boolean = true;
|
||||
|
||||
@FormField(R.string.progress_bar, FieldForm.TOGGLE, R.string.progress_bar_description, 6)
|
||||
@FormField(R.string.progress_bar, FieldForm.TOGGLE, R.string.progress_bar_description, 7)
|
||||
var progressBar: Boolean = true;
|
||||
|
||||
@FormField(R.string.fetch_on_app_boot, FieldForm.TOGGLE, R.string.shortly_after_opening_the_app_start_fetching_subscriptions, 7)
|
||||
@FormField(R.string.fetch_on_app_boot, FieldForm.TOGGLE, R.string.shortly_after_opening_the_app_start_fetching_subscriptions, 8)
|
||||
@Serializable(with = FlexibleBooleanSerializer::class)
|
||||
var fetchOnAppBoot: Boolean = true;
|
||||
|
||||
@FormField(R.string.fetch_on_tab_opened, FieldForm.TOGGLE, R.string.fetch_on_tab_opened_description, 8)
|
||||
@FormField(R.string.fetch_on_tab_opened, FieldForm.TOGGLE, R.string.fetch_on_tab_opened_description, 9)
|
||||
var fetchOnTabOpen: Boolean = true;
|
||||
|
||||
@FormField(R.string.background_update, FieldForm.DROPDOWN, R.string.experimental_background_update_for_subscriptions_cache, 9)
|
||||
@FormField(R.string.background_update, FieldForm.DROPDOWN, R.string.experimental_background_update_for_subscriptions_cache, 10)
|
||||
@DropdownFieldOptionsId(R.array.background_interval)
|
||||
var subscriptionsBackgroundUpdateInterval: Int = 0;
|
||||
|
||||
@@ -277,7 +293,7 @@ class Settings : FragmentedStorageFileJson() {
|
||||
};
|
||||
|
||||
|
||||
@FormField(R.string.subscription_concurrency, FieldForm.DROPDOWN, R.string.specify_how_many_threads_are_used_to_fetch_channels, 10)
|
||||
@FormField(R.string.subscription_concurrency, FieldForm.DROPDOWN, R.string.specify_how_many_threads_are_used_to_fetch_channels, 11)
|
||||
@DropdownFieldOptionsId(R.array.thread_count)
|
||||
var subscriptionConcurrency: Int = 3;
|
||||
|
||||
@@ -285,17 +301,17 @@ class Settings : FragmentedStorageFileJson() {
|
||||
return threadIndexToCount(subscriptionConcurrency);
|
||||
}
|
||||
|
||||
@FormField(R.string.show_watch_metrics, FieldForm.TOGGLE, R.string.show_watch_metrics_description, 11)
|
||||
@FormField(R.string.show_watch_metrics, FieldForm.TOGGLE, R.string.show_watch_metrics_description, 12)
|
||||
var showWatchMetrics: Boolean = false;
|
||||
|
||||
@FormField(R.string.track_playtime_locally, FieldForm.TOGGLE, R.string.track_playtime_locally_description, 12)
|
||||
@FormField(R.string.track_playtime_locally, FieldForm.TOGGLE, R.string.track_playtime_locally_description, 13)
|
||||
var allowPlaytimeTracking: Boolean = true;
|
||||
|
||||
|
||||
@FormField(R.string.always_reload_from_cache, FieldForm.TOGGLE, R.string.always_reload_from_cache_description, 13)
|
||||
@FormField(R.string.always_reload_from_cache, FieldForm.TOGGLE, R.string.always_reload_from_cache_description, 14)
|
||||
var alwaysReloadFromCache: Boolean = false;
|
||||
|
||||
@FormField(R.string.clear_channel_cache, FieldForm.BUTTON, R.string.clear_channel_cache_description, 14)
|
||||
@FormField(R.string.clear_channel_cache, FieldForm.BUTTON, R.string.clear_channel_cache_description, 15)
|
||||
fun clearChannelCache() {
|
||||
UIDialogs.toast(SettingsActivity.getActivity()!!, "Started clearing..");
|
||||
StateCache.instance.clear();
|
||||
@@ -311,7 +327,28 @@ class Settings : FragmentedStorageFileJson() {
|
||||
@DropdownFieldOptionsId(R.array.audio_languages)
|
||||
var primaryLanguage: Int = 0;
|
||||
|
||||
fun getPrimaryLanguage(context: Context) = context.resources.getStringArray(R.array.audio_languages)[primaryLanguage];
|
||||
fun getPrimaryLanguage(context: Context): String? {
|
||||
return when(primaryLanguage) {
|
||||
0 -> "en";
|
||||
1 -> "es";
|
||||
2 -> "de";
|
||||
3 -> "fr";
|
||||
4 -> "ja";
|
||||
5 -> "ko";
|
||||
6 -> "th";
|
||||
7 -> "vi";
|
||||
8 -> "id";
|
||||
9 -> "hi";
|
||||
10 -> "ar";
|
||||
11 -> "tu";
|
||||
12 -> "ru";
|
||||
13 -> "pt";
|
||||
14 -> "zh";
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
//= context.resources.getStringArray(R.array.audio_languages)[primaryLanguage];
|
||||
|
||||
@FormField(R.string.default_playback_speed, FieldForm.DROPDOWN, -1, 1)
|
||||
@DropdownFieldOptionsId(R.array.playback_speeds)
|
||||
@@ -407,6 +444,9 @@ class Settings : FragmentedStorageFileJson() {
|
||||
@FormField(R.string.restart_after_connectivity_loss, FieldForm.DROPDOWN, R.string.restart_playback_when_gaining_connectivity_after_a_loss, 12)
|
||||
@DropdownFieldOptionsId(R.array.restart_playback_after_loss)
|
||||
var restartPlaybackAfterConnectivityLoss: Int = 1;
|
||||
|
||||
@FormField(R.string.full_screen_portrait, FieldForm.TOGGLE, R.string.allow_full_screen_portrait, 13)
|
||||
var fullscreenPortrait: Boolean = false;
|
||||
}
|
||||
|
||||
@FormField(R.string.comments, "group", R.string.comments_description, 6)
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -11,12 +11,27 @@ import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
import android.widget.*
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.futo.platformplayer.activities.MainActivity
|
||||
import com.futo.platformplayer.api.media.models.comments.IPlatformComment
|
||||
import com.futo.platformplayer.casting.StateCasting
|
||||
import com.futo.platformplayer.dialogs.*
|
||||
import com.futo.platformplayer.dialogs.AutoUpdateDialog
|
||||
import com.futo.platformplayer.dialogs.AutomaticBackupDialog
|
||||
import com.futo.platformplayer.dialogs.AutomaticRestoreDialog
|
||||
import com.futo.platformplayer.dialogs.CastingAddDialog
|
||||
import com.futo.platformplayer.dialogs.CastingHelpDialog
|
||||
import com.futo.platformplayer.dialogs.ChangelogDialog
|
||||
import com.futo.platformplayer.dialogs.CommentDialog
|
||||
import com.futo.platformplayer.dialogs.ConnectCastingDialog
|
||||
import com.futo.platformplayer.dialogs.ConnectedCastingDialog
|
||||
import com.futo.platformplayer.dialogs.ImportDialog
|
||||
import com.futo.platformplayer.dialogs.ImportOptionsDialog
|
||||
import com.futo.platformplayer.dialogs.MigrateDialog
|
||||
import com.futo.platformplayer.dialogs.ProgressDialog
|
||||
import com.futo.platformplayer.engine.exceptions.PluginException
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
import com.futo.platformplayer.states.StateApp
|
||||
@@ -341,11 +356,17 @@ class UIDialogs {
|
||||
val d = StateCasting.instance.activeDevice;
|
||||
if (d != null) {
|
||||
val dialog = ConnectedCastingDialog(context);
|
||||
if (context is Activity) {
|
||||
dialog.setOwnerActivity(context)
|
||||
}
|
||||
registerDialogOpened(dialog);
|
||||
dialog.setOnDismissListener { registerDialogClosed(dialog) };
|
||||
dialog.show();
|
||||
} else {
|
||||
val dialog = ConnectCastingDialog(context);
|
||||
if (context is Activity) {
|
||||
dialog.setOwnerActivity(context)
|
||||
}
|
||||
registerDialogOpened(dialog);
|
||||
val c = context
|
||||
if (c is Activity) {
|
||||
|
||||
@@ -3,8 +3,10 @@ package com.futo.platformplayer
|
||||
import android.content.ContentResolver
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.futo.platformplayer.activities.MainActivity
|
||||
import com.futo.platformplayer.api.http.ManagedHttpClient
|
||||
import com.futo.platformplayer.api.media.models.ResultCapabilities
|
||||
import com.futo.platformplayer.api.media.models.channels.IPlatformChannel
|
||||
import com.futo.platformplayer.api.media.models.streams.VideoUnMuxedSourceDescriptor
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.HLSVariantAudioUrlSource
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.HLSVariantVideoUrlSource
|
||||
@@ -17,31 +19,40 @@ 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.downloads.VideoLocal
|
||||
import com.futo.platformplayer.fragment.mainactivity.main.SubscriptionGroupFragment
|
||||
import com.futo.platformplayer.helpers.VideoHelper
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
import com.futo.platformplayer.models.ImageVariable
|
||||
import com.futo.platformplayer.models.Playlist
|
||||
import com.futo.platformplayer.models.Subscription
|
||||
import com.futo.platformplayer.models.SubscriptionGroup
|
||||
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 isDownloadable
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.lang.IllegalStateException
|
||||
|
||||
class UISlideOverlays {
|
||||
companion object {
|
||||
private const val TAG = "UISlideOverlays";
|
||||
|
||||
fun showOverlay(container: ViewGroup, title: String, okButton: String?, onOk: ()->Unit, vararg views: View) {
|
||||
fun showOverlay(container: ViewGroup, title: String, okButton: String?, onOk: ()->Unit, vararg views: View): SlideUpMenuOverlay {
|
||||
var menu = SlideUpMenuOverlay(container.context, container, title, okButton, true, *views);
|
||||
|
||||
menu.onOK.subscribe {
|
||||
@@ -49,6 +60,7 @@ class UISlideOverlays {
|
||||
onOk.invoke();
|
||||
};
|
||||
menu.show();
|
||||
return menu;
|
||||
}
|
||||
|
||||
fun showSubscriptionOptionsOverlay(subscription: Subscription, container: ViewGroup) {
|
||||
@@ -73,6 +85,7 @@ class UISlideOverlays {
|
||||
SlideUpMenuItem(container.context, R.drawable.ic_notifications, "Notifications", "", "notifications", {
|
||||
subscription.doNotifications = menu?.selectOption(null, "notifications", true, true) ?: subscription.doNotifications;
|
||||
}, false),
|
||||
|
||||
SlideUpMenuGroup(container.context, "Fetch Settings",
|
||||
"Depending on the platform you might not need to enable a type for it to be available.",
|
||||
-1, listOf()),
|
||||
@@ -91,7 +104,15 @@ class UISlideOverlays {
|
||||
}, false) else null,
|
||||
if(capabilities.hasType(ResultCapabilities.TYPE_POSTS)) SlideUpMenuItem(container.context, R.drawable.ic_chat, "Posts", "Check for posts", "fetchPosts", {
|
||||
subscription.doFetchPosts = menu?.selectOption(null, "fetchPosts", true, true) ?: subscription.doFetchPosts;
|
||||
}, false) else null).filterNotNull());
|
||||
}, false) else null/*,,
|
||||
|
||||
SlideUpMenuGroup(container.context, "Actions",
|
||||
"Various things you can do with this subscription",
|
||||
-1, listOf())
|
||||
SlideUpMenuItem(container.context, R.drawable.ic_list, "Add to Group", "", "btnAddToGroup", {
|
||||
showCreateSubscriptionGroup(container, subscription.channel);
|
||||
}, false)*/
|
||||
).filterNotNull());
|
||||
|
||||
menu = SlideUpMenuOverlay(container.context, container, "Subscription Settings", null, true, items);
|
||||
|
||||
@@ -129,6 +150,10 @@ class UISlideOverlays {
|
||||
}
|
||||
}
|
||||
|
||||
fun showAddToGroupOverlay(channel: IPlatformVideo, container: ViewGroup) {
|
||||
|
||||
}
|
||||
|
||||
fun showHlsPicker(video: IPlatformVideoDetails, source: Any, sourceUrl: String, container: ViewGroup): SlideUpMenuOverlay {
|
||||
val items = arrayListOf<View>(LoaderView(container.context))
|
||||
val slideUpMenuOverlay = SlideUpMenuOverlay(container.context, container, container.context.getString(R.string.download_video), null, true, items)
|
||||
@@ -242,7 +267,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 +288,30 @@ class UISlideOverlays {
|
||||
videoSources
|
||||
.filter { it.isDownloadable() }
|
||||
.map {
|
||||
if (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) {
|
||||
SlideUpMenuItem(container.context, R.drawable.ic_movie, it.name, "HLS", it, {
|
||||
showHlsPicker(video, it, it.url, container)
|
||||
}, false)
|
||||
} else {
|
||||
throw Exception("Unhandled source type")
|
||||
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)
|
||||
}
|
||||
|
||||
is IHLSManifestSource -> {
|
||||
SlideUpMenuItem(container.context, R.drawable.ic_movie, it.name, "HLS", it, {
|
||||
showHlsPicker(video, it, it.url, container)
|
||||
}, false)
|
||||
}
|
||||
|
||||
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 +320,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) {
|
||||
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) {
|
||||
SlideUpMenuItem(container.context, R.drawable.ic_movie, it.name, "HLS Audio", it, {
|
||||
showHlsPicker(video, it, it.url, container)
|
||||
}, false)
|
||||
} else {
|
||||
throw Exception("Unhandled source type")
|
||||
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);
|
||||
}
|
||||
|
||||
is IHLSManifestAudioSource -> {
|
||||
SlideUpMenuItem(container.context, R.drawable.ic_movie, it.name, "HLS Audio", it, {
|
||||
showHlsPicker(video, it, it.url, container)
|
||||
}, false)
|
||||
}
|
||||
|
||||
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 +352,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 +363,8 @@ class UISlideOverlays {
|
||||
menu?.selectOption(subtitleSources, it);
|
||||
}
|
||||
}, false);
|
||||
}));
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
menu = SlideUpMenuOverlay(container.context, container, container.context.getString(R.string.download_video), null, true, items);
|
||||
@@ -502,6 +532,48 @@ class UISlideOverlays {
|
||||
return overlay;
|
||||
}
|
||||
|
||||
fun showCreateSubscriptionGroup(container: ViewGroup, initialChannel: IPlatformChannel? = null, onCreate: ((String) -> Unit)? = null): SlideUpMenuOverlay {
|
||||
val nameInput = SlideUpMenuTextInput(container.context, container.context.getString(R.string.name));
|
||||
val addSubGroupOverlay = SlideUpMenuOverlay(container.context, container, container.context.getString(R.string.create_new_subgroup), container.context.getString(R.string.ok), false, nameInput);
|
||||
|
||||
addSubGroupOverlay.onOK.subscribe {
|
||||
val text = nameInput.text;
|
||||
if (text.isBlank()) {
|
||||
return@subscribe;
|
||||
}
|
||||
|
||||
addSubGroupOverlay.hide();
|
||||
nameInput.deactivate();
|
||||
nameInput.clear();
|
||||
if(onCreate == null)
|
||||
{
|
||||
//TODO: Do this better, temp
|
||||
StateApp.instance.contextOrNull?.let {
|
||||
if(it is MainActivity) {
|
||||
val subGroup = SubscriptionGroup(text);
|
||||
if(initialChannel != null) {
|
||||
subGroup.urls.add(initialChannel.url);
|
||||
if(initialChannel.thumbnail != null)
|
||||
subGroup.image = ImageVariable(initialChannel.thumbnail);
|
||||
}
|
||||
it.navigate(it.getFragment<SubscriptionGroupFragment>(), subGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
onCreate(text)
|
||||
};
|
||||
|
||||
addSubGroupOverlay.onCancel.subscribe {
|
||||
nameInput.deactivate();
|
||||
nameInput.clear();
|
||||
};
|
||||
|
||||
addSubGroupOverlay.show();
|
||||
nameInput.activate();
|
||||
|
||||
return addSubGroupOverlay
|
||||
}
|
||||
fun showCreatePlaylistOverlay(container: ViewGroup, onCreate: (String) -> Unit): SlideUpMenuOverlay {
|
||||
val nameInput = SlideUpMenuTextInput(container.context, container.context.getString(R.string.name));
|
||||
val addPlaylistOverlay = SlideUpMenuOverlay(container.context, container, container.context.getString(R.string.create_new_playlist), container.context.getString(R.string.ok), false, nameInput);
|
||||
@@ -645,10 +717,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
|
||||
.map { btn -> SlideUpMenuItem(container.context, btn.iconResource, btn.text.text.toString(), "", "", {
|
||||
btn.handler?.invoke(btn);
|
||||
}, true) as View }.toTypedArray() ?: arrayOf(),
|
||||
val views = arrayOf(
|
||||
hidden
|
||||
.map { btn -> SlideUpMenuItem(container.context, btn.iconResource, btn.text.text.toString(), "", "", {
|
||||
btn.handler?.invoke(btn);
|
||||
}, 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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.futo.platformplayer.activities
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
@@ -26,7 +27,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
|
||||
@@ -36,6 +36,7 @@ import com.futo.platformplayer.fragment.mainactivity.topbar.NavigationTopBarFrag
|
||||
import com.futo.platformplayer.fragment.mainactivity.topbar.SearchTopBarFragment
|
||||
import com.futo.platformplayer.listeners.OrientationManager
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
import com.futo.platformplayer.models.SubscriptionGroup
|
||||
import com.futo.platformplayer.models.UrlVideoWithTime
|
||||
import com.futo.platformplayer.states.*
|
||||
import com.futo.platformplayer.stores.FragmentedStorage
|
||||
@@ -44,7 +45,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
|
||||
@@ -101,6 +101,8 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
lateinit var _fragImportSubscriptions: ImportSubscriptionsFragment;
|
||||
lateinit var _fragImportPlaylists: ImportPlaylistsFragment;
|
||||
lateinit var _fragBuy: BuyFragment;
|
||||
lateinit var _fragSubGroup: SubscriptionGroupFragment;
|
||||
lateinit var _fragSubGroupList: SubscriptionGroupListFragment;
|
||||
|
||||
lateinit var _fragBrowser: BrowserFragment;
|
||||
|
||||
@@ -236,6 +238,8 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
_fragImportSubscriptions = ImportSubscriptionsFragment.newInstance();
|
||||
_fragImportPlaylists = ImportPlaylistsFragment.newInstance();
|
||||
_fragBuy = BuyFragment.newInstance();
|
||||
_fragSubGroup = SubscriptionGroupFragment.newInstance();
|
||||
_fragSubGroupList = SubscriptionGroupListFragment.newInstance();
|
||||
|
||||
_fragBrowser = BrowserFragment.newInstance();
|
||||
|
||||
@@ -317,6 +321,8 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
_fragDownloads.topBar = _fragTopBarGeneral;
|
||||
_fragImportSubscriptions.topBar = _fragTopBarImport;
|
||||
_fragImportPlaylists.topBar = _fragTopBarImport;
|
||||
_fragSubGroup.topBar = _fragTopBarNavigation;
|
||||
_fragSubGroupList.topBar = _fragTopBarAdd;
|
||||
|
||||
_fragBrowser.topBar = _fragTopBarNavigation;
|
||||
|
||||
@@ -649,7 +655,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 +677,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 +729,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
fun handleUnknownJson(name: String?, json: String): Boolean {
|
||||
fun handleUnknownJson(json: String): Boolean {
|
||||
|
||||
val context = this;
|
||||
|
||||
@@ -832,7 +838,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");
|
||||
}
|
||||
|
||||
@@ -854,6 +860,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
* Navigate takes a MainFragment, and makes them the current main visible view
|
||||
* A parameter can be provided which becomes available in the onShow of said fragment
|
||||
*/
|
||||
@SuppressLint("CommitTransaction")
|
||||
fun navigate(segment: MainFragment, parameter: Any? = null, withHistory: Boolean = true, isBack: Boolean = false) {
|
||||
Logger.i(TAG, "Navigate to $segment (parameter=$parameter, withHistory=$withHistory, isBack=$isBack)")
|
||||
|
||||
@@ -889,7 +896,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,13 +905,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
transaction = transaction.hide(_fragBotBarMenu);
|
||||
}
|
||||
transaction.commitNow();
|
||||
}
|
||||
else {
|
||||
//Special cases
|
||||
if(segment is VideoDetailFragment) {
|
||||
_fragContainerVideoDetail.visibility = View.VISIBLE;
|
||||
_fragVideoDetail.maximizeVideoDetail();
|
||||
}
|
||||
} else {
|
||||
|
||||
if(!segment.hasBottomBar) {
|
||||
supportFragmentManager.beginTransaction()
|
||||
@@ -989,6 +989,8 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
ImportPlaylistsFragment::class -> _fragImportPlaylists as T;
|
||||
BrowserFragment::class -> _fragBrowser as T;
|
||||
BuyFragment::class -> _fragBuy as T;
|
||||
SubscriptionGroupFragment::class -> _fragSubGroup as T;
|
||||
SubscriptionGroupListFragment::class -> _fragSubGroupList as T;
|
||||
else -> throw IllegalArgumentException("Fragment type ${T::class.java.name} is not available in MainActivity");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,10 +55,10 @@ class ManageTabsActivity : AppCompatActivity() {
|
||||
Settings.instance.save()
|
||||
}
|
||||
|
||||
val items = Settings.instance.tabs.mapNotNull {
|
||||
val items = ArrayList(Settings.instance.tabs.mapNotNull {
|
||||
val buttonDefinition = MenuBottomBarFragment.buttonDefinitions.find { d -> it.id == d.id } ?: return@mapNotNull null
|
||||
TabViewHolderData(buttonDefinition, it.enabled)
|
||||
};
|
||||
});
|
||||
|
||||
_listTabs = _recyclerTabs.asAny(items) {
|
||||
it.onDragDrop.subscribe { vh ->
|
||||
|
||||
+6
-7
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
@@ -192,7 +191,7 @@ class ManagedHttpServer(private val _requestedPort: Int = 0) {
|
||||
}
|
||||
}
|
||||
fun addBridgeHandlers(obj: Any, tag: String? = null) {
|
||||
val tagToUse = tag ?: obj.javaClass.name;
|
||||
//val tagToUse = tag ?: obj.javaClass.name;
|
||||
val getMethods = obj::class.java.declaredMethods
|
||||
.filter { it.getAnnotation(HttpGET::class.java) != null }
|
||||
.map { Pair<Method, HttpGET>(it, it.getAnnotation(HttpGET::class.java)!!) }
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-3
@@ -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) {}
|
||||
@@ -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,8 +41,7 @@ class CachedPlatformClient : IPlatformClient {
|
||||
var result = _cache.get(url);
|
||||
if(result == null) {
|
||||
result = _client.getContentDetails(url);
|
||||
if (result != null)
|
||||
_cache.put(url, result);
|
||||
_cache.put(url, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -64,7 +64,6 @@ class FilterGroup(
|
||||
val isMultiSelect: Boolean,
|
||||
val id: String? = null
|
||||
) {
|
||||
@kotlinx.serialization.Transient
|
||||
val idOrName: String get() = id ?: name;
|
||||
|
||||
companion object {
|
||||
|
||||
+4
-12
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package com.futo.platformplayer.api.media.models.modifier
|
||||
|
||||
class AdhocRequestModifier: IRequestModifier {
|
||||
val _handler: (String, Map<String,String>)->IRequest;
|
||||
override var allowByteSkip: Boolean = false;
|
||||
|
||||
constructor(modifyReq: (String, Map<String,String>)->IRequest) {
|
||||
_handler = modifyReq;
|
||||
}
|
||||
|
||||
override fun modifyRequest(url: String, headers: Map<String, String>): IRequest {
|
||||
return _handler(url, headers);
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package com.futo.platformplayer.api.media.models.modifier
|
||||
|
||||
interface IModifierOptions {
|
||||
val applyAuthClient: String?;
|
||||
val applyCookieClient: String?;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.futo.platformplayer.api.media.models.modifier
|
||||
|
||||
interface IRequest {
|
||||
val url: String?;
|
||||
val headers: Map<String, String>;
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package com.futo.platformplayer.api.media.models.modifier
|
||||
|
||||
|
||||
interface IRequestModifier {
|
||||
var allowByteSkip: Boolean;
|
||||
fun modifyRequest(url: String, headers: Map<String, String>): IRequest
|
||||
}
|
||||
-3
@@ -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?;
|
||||
|
||||
-4
@@ -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
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-2
@@ -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;
|
||||
|
||||
+3
-4
@@ -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
|
||||
|
||||
-1
@@ -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();
|
||||
};
|
||||
+4
-3
@@ -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();
|
||||
};
|
||||
@@ -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
|
||||
@@ -20,14 +20,14 @@ class DevJSClient : JSClient {
|
||||
|
||||
val devID: String;
|
||||
|
||||
constructor(context: Context, config: SourcePluginConfig, script: String, auth: SourceAuth? = null, captcha: SourceCaptchaData? = null, devID: String? = null): super(context, SourcePluginDescriptor(config, auth?.toEncrypted(), captcha?.toEncrypted(), listOf("DEV")), null, script) {
|
||||
constructor(context: Context, config: SourcePluginConfig, script: String, auth: SourceAuth? = null, captcha: SourceCaptchaData? = null, devID: String? = null, settings: HashMap<String, String?>? = null): super(context, SourcePluginDescriptor(config, auth?.toEncrypted(), captcha?.toEncrypted(), listOf("DEV"), settings), null, script) {
|
||||
_devScript = script;
|
||||
_auth = auth;
|
||||
_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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ class DevJSClient : JSClient {
|
||||
_auth = auth;
|
||||
}
|
||||
fun recreate(context: Context): DevJSClient {
|
||||
return DevJSClient(context, config, _devScript, _auth, _captcha, devID);
|
||||
return DevJSClient(context, config, _devScript, _auth, _captcha, devID, descriptor.settings);
|
||||
}
|
||||
|
||||
override fun getCopy(): JSClient {
|
||||
|
||||
@@ -4,12 +4,10 @@ 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.http.ManagedHttpClient
|
||||
import com.futo.platformplayer.api.media.IPlatformClient
|
||||
import com.futo.platformplayer.api.media.PlatformClientCapabilities
|
||||
import com.futo.platformplayer.api.media.models.PlatformAuthorLink
|
||||
@@ -23,23 +21,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,13 +63,13 @@ 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;
|
||||
|
||||
private val _client: JSHttpClient;
|
||||
private val _clientAuth: JSHttpClient?;
|
||||
private val _httpClient: JSHttpClient;
|
||||
private val _httpClientAuth: JSHttpClient?;
|
||||
private var _searchCapabilities: ResultCapabilities? = null;
|
||||
private var _searchChannelContentsCapabilities: ResultCapabilities? = null;
|
||||
private var _channelCapabilities: ResultCapabilities? = null;
|
||||
@@ -119,9 +132,9 @@ open class JSClient : IPlatformClient {
|
||||
_captcha = descriptor.getCaptchaData();
|
||||
flags = descriptor.flags.toTypedArray();
|
||||
|
||||
_client = JSHttpClient(this, null, _captcha);
|
||||
_clientAuth = JSHttpClient(this, _auth, _captcha);
|
||||
_plugin = V8Plugin(context, descriptor.config, null, _client, _clientAuth);
|
||||
_httpClient = JSHttpClient(this, null, _captcha);
|
||||
_httpClientAuth = JSHttpClient(this, _auth, _captcha);
|
||||
_plugin = V8Plugin(context, descriptor.config, null, _httpClient, _httpClientAuth);
|
||||
_plugin.withDependency(context, "scripts/polyfil.js");
|
||||
_plugin.withDependency(context, "scripts/source.js");
|
||||
|
||||
@@ -148,9 +161,9 @@ open class JSClient : IPlatformClient {
|
||||
_captcha = descriptor.getCaptchaData();
|
||||
flags = descriptor.flags.toTypedArray();
|
||||
|
||||
_client = JSHttpClient(this, null, _captcha);
|
||||
_clientAuth = JSHttpClient(this, _auth, _captcha);
|
||||
_plugin = V8Plugin(context, descriptor.config, script, _client, _clientAuth);
|
||||
_httpClient = JSHttpClient(this, null, _captcha);
|
||||
_httpClientAuth = JSHttpClient(this, _auth, _captcha);
|
||||
_plugin = V8Plugin(context, descriptor.config, script, _httpClient, _httpClientAuth);
|
||||
_plugin.withDependency(context, "scripts/polyfil.js");
|
||||
_plugin.withDependency(context, "scripts/source.js");
|
||||
_plugin.withScript(script);
|
||||
@@ -169,6 +182,13 @@ open class JSClient : IPlatformClient {
|
||||
fun getUnderlyingPlugin(): V8Plugin {
|
||||
return _plugin;
|
||||
}
|
||||
fun getHttpClientById(id: String): JSHttpClient? {
|
||||
if(_httpClient.clientId == id)
|
||||
return _httpClient;
|
||||
if(_httpClientAuth?.clientId == id)
|
||||
return _httpClientAuth;
|
||||
return plugin.httpClientOthers[id];
|
||||
}
|
||||
|
||||
override fun initialize() {
|
||||
Logger.i(TAG, "Plugin [${config.name}] initializing");
|
||||
@@ -242,7 +262,7 @@ open class JSClient : IPlatformClient {
|
||||
@JSDocs(2, "source.getHome()", "Gets the HomeFeed of the platform")
|
||||
override fun getHome(): IPager<IPlatformContent> = isBusyWith {
|
||||
ensureEnabled();
|
||||
return@isBusyWith JSContentPager(config, plugin,
|
||||
return@isBusyWith JSContentPager(config, this,
|
||||
plugin.executeTyped("source.getHome()"));
|
||||
}
|
||||
|
||||
@@ -280,7 +300,7 @@ open class JSClient : IPlatformClient {
|
||||
@JSDocsParameter("channelId", "(optional) Channel id to search in")
|
||||
override fun search(query: String, type: String?, order: String?, filters: Map<String, List<String>>?): IPager<IPlatformContent> = isBusyWith {
|
||||
ensureEnabled();
|
||||
return@isBusyWith JSContentPager(config, plugin,
|
||||
return@isBusyWith JSContentPager(config, this,
|
||||
plugin.executeTyped("source.search(${Json.encodeToString(query)}, ${Json.encodeToString(type)}, ${Json.encodeToString(order)}, ${Json.encodeToString(filters)})"));
|
||||
}
|
||||
|
||||
@@ -304,7 +324,7 @@ open class JSClient : IPlatformClient {
|
||||
if(!capabilities.hasSearchChannelContents)
|
||||
throw IllegalStateException("This plugin does not support channel search");
|
||||
|
||||
return@isBusyWith JSContentPager(config, plugin,
|
||||
return@isBusyWith JSContentPager(config, this,
|
||||
plugin.executeTyped("source.searchChannelContents(${Json.encodeToString(channelUrl)}, ${Json.encodeToString(query)}, ${Json.encodeToString(type)}, ${Json.encodeToString(order)}, ${Json.encodeToString(filters)})"));
|
||||
}
|
||||
|
||||
@@ -313,7 +333,7 @@ open class JSClient : IPlatformClient {
|
||||
@JSDocsParameter("query", "Query that channels should match")
|
||||
override fun searchChannels(query: String): IPager<PlatformAuthorLink> = isBusyWith {
|
||||
ensureEnabled();
|
||||
return@isBusyWith JSChannelPager(config, plugin,
|
||||
return@isBusyWith JSChannelPager(config, this,
|
||||
plugin.executeTyped("source.searchChannels(${Json.encodeToString(query)})"));
|
||||
}
|
||||
|
||||
@@ -360,7 +380,7 @@ open class JSClient : IPlatformClient {
|
||||
@JSDocsParameter("filters", "(optional) Filters to apply on contents")
|
||||
override fun getChannelContents(channelUrl: String, type: String?, order: String?, filters: Map<String, List<String>>?): IPager<IPlatformContent> = isBusyWith {
|
||||
ensureEnabled();
|
||||
return@isBusyWith JSContentPager(config, plugin,
|
||||
return@isBusyWith JSContentPager(config, this,
|
||||
plugin.executeTyped("source.getChannelContents(${Json.encodeToString(channelUrl)}, ${Json.encodeToString(type)}, ${Json.encodeToString(order)}, ${Json.encodeToString(filters)})"));
|
||||
}
|
||||
|
||||
@@ -426,7 +446,7 @@ open class JSClient : IPlatformClient {
|
||||
@JSDocsParameter("url", "A content url (this platform)")
|
||||
override fun getContentDetails(url: String): IPlatformContentDetails = isBusyWith {
|
||||
ensureEnabled();
|
||||
return@isBusyWith IJSContentDetails.fromV8(config,
|
||||
return@isBusyWith IJSContentDetails.fromV8(this,
|
||||
plugin.executeTyped("source.getContentDetails(${Json.encodeToString(url)})"));
|
||||
}
|
||||
|
||||
@@ -464,13 +484,13 @@ open class JSClient : IPlatformClient {
|
||||
if (pager !is V8ValueObject) { //TODO: Maybe solve this better
|
||||
return@isBusyWith EmptyPager<IPlatformComment>();
|
||||
}
|
||||
return@isBusyWith JSCommentPager(config, plugin, pager);
|
||||
return@isBusyWith JSCommentPager(config, this, pager);
|
||||
}
|
||||
@JSDocs(17, "source.getSubComments(comment)", "Gets replies for a given comment")
|
||||
@JSDocsParameter("comment", "Comment object that was returned by getComments")
|
||||
override fun getSubComments(comment: IPlatformComment): IPager<IPlatformComment> {
|
||||
ensureEnabled();
|
||||
return comment.getReplies(this) ?: JSCommentPager(config, plugin,
|
||||
return comment.getReplies(this) ?: JSCommentPager(config, this,
|
||||
plugin.executeTyped("source.getSubComments(${Json.encodeToString(comment as JSComment)})"));
|
||||
}
|
||||
|
||||
@@ -489,7 +509,7 @@ open class JSClient : IPlatformClient {
|
||||
if(!capabilities.hasGetLiveEvents)
|
||||
return@isBusyWith null;
|
||||
ensureEnabled();
|
||||
return@isBusyWith JSLiveEventPager(config, plugin,
|
||||
return@isBusyWith JSLiveEventPager(config, this,
|
||||
plugin.executeTyped("source.getLiveEvents(${Json.encodeToString(url)})"));
|
||||
}
|
||||
@JSDocs(19, "source.searchPlaylists(query)", "Searches for playlists on the platform")
|
||||
@@ -502,7 +522,7 @@ open class JSClient : IPlatformClient {
|
||||
ensureEnabled();
|
||||
if(!capabilities.hasSearchPlaylists)
|
||||
throw IllegalStateException("This plugin does not support playlist search");
|
||||
return@isBusyWith JSContentPager(config, plugin, plugin.executeTyped("source.searchPlaylists(${Json.encodeToString(query)}, ${Json.encodeToString(type)}, ${Json.encodeToString(order)}, ${Json.encodeToString(filters)})"));
|
||||
return@isBusyWith JSContentPager(config, this, plugin.executeTyped("source.searchPlaylists(${Json.encodeToString(query)}, ${Json.encodeToString(type)}, ${Json.encodeToString(order)}, ${Json.encodeToString(filters)})"));
|
||||
}
|
||||
@JSOptional
|
||||
@JSDocs(20, "source.isPlaylistUrl(url)", "Validates if a playlist url is for this platform")
|
||||
@@ -518,7 +538,7 @@ open class JSClient : IPlatformClient {
|
||||
@JSDocsParameter("url", "Url of playlist")
|
||||
override fun getPlaylist(url: String): IPlatformPlaylistDetails = isBusyWith {
|
||||
ensureEnabled();
|
||||
return@isBusyWith JSPlaylistDetails(plugin, plugin.config as SourcePluginConfig, plugin.executeTyped("source.getPlaylist(${Json.encodeToString(url)})"));
|
||||
return@isBusyWith JSPlaylistDetails(this, plugin.config as SourcePluginConfig, plugin.executeTyped("source.getPlaylist(${Json.encodeToString(url)})"));
|
||||
}
|
||||
|
||||
@JSOptional
|
||||
|
||||
+1
-3
@@ -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;
|
||||
}
|
||||
}
|
||||
+6
-5
@@ -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
|
||||
@@ -27,17 +26,19 @@ class SourcePluginDescriptor {
|
||||
@kotlinx.serialization.Transient
|
||||
val onCaptchaChanged = Event0();
|
||||
|
||||
constructor(config :SourcePluginConfig, authEncrypted: String? = null, captchaEncrypted: String? = null) {
|
||||
constructor(config :SourcePluginConfig, authEncrypted: String? = null, captchaEncrypted: String? = null, settings: HashMap<String, String?>? = null) {
|
||||
this.config = config;
|
||||
this.authEncrypted = authEncrypted;
|
||||
this.captchaEncrypted = captchaEncrypted;
|
||||
this.flags = listOf();
|
||||
this.settings = settings ?: hashMapOf();
|
||||
}
|
||||
constructor(config :SourcePluginConfig, authEncrypted: String? = null, captchaEncrypted: String? = null, flags: List<String>) {
|
||||
constructor(config :SourcePluginConfig, authEncrypted: String? = null, captchaEncrypted: String? = null, flags: List<String>, settings: HashMap<String, String?>? = null) {
|
||||
this.config = config;
|
||||
this.authEncrypted = authEncrypted;
|
||||
this.captchaEncrypted = captchaEncrypted;
|
||||
this.flags = flags;
|
||||
this.settings = settings ?: hashMapOf();
|
||||
}
|
||||
|
||||
fun getSettingsWithDefaults(): HashMap<String, String?> {
|
||||
@@ -107,9 +108,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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+94
-72
@@ -1,14 +1,16 @@
|
||||
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
|
||||
import com.futo.platformplayer.api.media.platforms.js.SourceCaptchaData
|
||||
import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig
|
||||
import com.futo.platformplayer.api.media.platforms.js.models.JSRequest
|
||||
import com.futo.platformplayer.api.media.platforms.js.models.JSRequestModifier
|
||||
import com.futo.platformplayer.engine.exceptions.ScriptImplementationException
|
||||
import com.futo.platformplayer.matchesDomain
|
||||
import java.util.UUID
|
||||
|
||||
class JSHttpClient : ManagedHttpClient {
|
||||
private val _jsClient: JSClient?;
|
||||
@@ -16,12 +18,15 @@ class JSHttpClient : ManagedHttpClient {
|
||||
private val _auth: SourceAuth?;
|
||||
private val _captcha: SourceCaptchaData?;
|
||||
|
||||
val clientId = UUID.randomUUID().toString();
|
||||
|
||||
var doUpdateCookies: Boolean = true;
|
||||
var doApplyCookies: Boolean = true;
|
||||
var doAllowNewCookies: Boolean = true;
|
||||
val isLoggedIn: Boolean get() = _auth != null;
|
||||
|
||||
private var _currentCookieMap: HashMap<String, HashMap<String, String>>;
|
||||
private var _otherCookieMap: HashMap<String, HashMap<String, String>>;
|
||||
|
||||
constructor(jsClient: JSClient?, auth: SourceAuth? = null, captcha: SourceCaptchaData? = null, config: SourcePluginConfig? = null) : super() {
|
||||
_jsClient = jsClient;
|
||||
@@ -30,6 +35,7 @@ class JSHttpClient : ManagedHttpClient {
|
||||
_captcha = captcha;
|
||||
|
||||
_currentCookieMap = hashMapOf();
|
||||
_otherCookieMap = hashMapOf();
|
||||
if(!auth?.cookieMap.isNullOrEmpty()) {
|
||||
for(domainCookies in auth!!.cookieMap!!)
|
||||
_currentCookieMap.put(domainCookies.key, HashMap(domainCookies.value));
|
||||
@@ -47,13 +53,49 @@ 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;
|
||||
}
|
||||
|
||||
//TODO: Use this in beforeRequest to remove dup code
|
||||
fun applyHeaders(url: Uri, headers: MutableMap<String, String>, applyAuth: Boolean = false, applyOtherCookies: Boolean = false) {
|
||||
val domain = url.host!!.lowercase();
|
||||
val auth = _auth;
|
||||
if (applyAuth && auth != null) {
|
||||
//TODO: Possibly add doApplyHeaders
|
||||
for (header in auth.headers.filter { domain.matchesDomain(it.key) }.flatMap { it.value.entries })
|
||||
headers.put(header.key, header.value);
|
||||
}
|
||||
|
||||
if(doApplyCookies && (applyAuth || applyOtherCookies)) {
|
||||
val cookiesToApply = hashMapOf<String, String>();
|
||||
if(applyOtherCookies)
|
||||
synchronized(_otherCookieMap) {
|
||||
for(cookie in _otherCookieMap
|
||||
.filter { domain.matchesDomain(it.key) }
|
||||
.flatMap { it.value.toList() })
|
||||
cookiesToApply[cookie.first] = cookie.second;
|
||||
}
|
||||
if(applyAuth)
|
||||
synchronized(_currentCookieMap) {
|
||||
for(cookie in _currentCookieMap
|
||||
.filter { domain.matchesDomain(it.key) }
|
||||
.flatMap { it.value.toList() })
|
||||
cookiesToApply[cookie.first] = cookie.second;
|
||||
};
|
||||
|
||||
if(cookiesToApply.size > 0) {
|
||||
val cookieString = cookiesToApply.map { it.key + "=" + it.value }.joinToString("; ");
|
||||
|
||||
val existingCookies = headers["Cookie"];
|
||||
if(!existingCookies.isNullOrEmpty())
|
||||
headers.put("Cookie", existingCookies.trim(';') + "; " + cookieString);
|
||||
else
|
||||
headers.put("Cookie", cookieString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun beforeRequest(request: okhttp3.Request): okhttp3.Request {
|
||||
val domain = request.url.host.lowercase();
|
||||
val auth = _auth;
|
||||
@@ -69,10 +111,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 +134,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 {
|
||||
@@ -106,85 +148,65 @@ class JSHttpClient : ManagedHttpClient {
|
||||
val defaultCookieDomain =
|
||||
"." + 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("; "));
|
||||
if(header.first.lowercase() == "set-cookie") {
|
||||
var domainToUse = domain;
|
||||
val cookie = cookieStringToPair(header.second);
|
||||
//for (cookie in newCookies) {
|
||||
var cookieValue = cookie.second;
|
||||
var domainToUse = domain;
|
||||
var cookieValue = cookie.second;
|
||||
|
||||
if (!cookie.first.isNullOrEmpty() && !cookie.second.isNullOrEmpty()) {
|
||||
val cookieParts = cookie.second.split(";");
|
||||
if (cookieParts.size == 0)
|
||||
continue;
|
||||
cookieValue = cookieParts[0].trim();
|
||||
if (cookie.first.isNotEmpty() && cookie.second.isNotEmpty()) {
|
||||
val cookieParts = cookie.second.split(";");
|
||||
if (cookieParts.size == 0)
|
||||
continue;
|
||||
cookieValue = cookieParts[0].trim();
|
||||
|
||||
val cookieVariables = cookieParts.drop(1).map {
|
||||
val splitIndex = it.indexOf("=");
|
||||
if (splitIndex < 0)
|
||||
return@map Pair(it.trim().lowercase(), "");
|
||||
return@map Pair<String, String>(
|
||||
it.substring(0, splitIndex).lowercase().trim(),
|
||||
it.substring(splitIndex + 1).trim()
|
||||
);
|
||||
}.toMap();
|
||||
domainToUse = if (cookieVariables.containsKey("domain"))
|
||||
cookieVariables["domain"]!!.lowercase();
|
||||
else defaultCookieDomain;
|
||||
}
|
||||
val cookieVariables = cookieParts.drop(1).map {
|
||||
val splitIndex = it.indexOf("=");
|
||||
if (splitIndex < 0)
|
||||
return@map Pair(it.trim().lowercase(), "");
|
||||
return@map Pair<String, String>(
|
||||
it.substring(0, splitIndex).lowercase().trim(),
|
||||
it.substring(splitIndex + 1).trim()
|
||||
);
|
||||
}.toMap();
|
||||
domainToUse = if (cookieVariables.containsKey("domain"))
|
||||
cookieVariables["domain"]!!.lowercase();
|
||||
else defaultCookieDomain;
|
||||
//TODO: Make sure this has no negative effect besides apply cookies to root domain
|
||||
if(!domainToUse.startsWith("."))
|
||||
domainToUse = ".${domainToUse}";
|
||||
}
|
||||
|
||||
val cookieMap = if (_currentCookieMap!!.containsKey(domainToUse))
|
||||
_currentCookieMap!![domainToUse]!!;
|
||||
if ((_auth != null || _currentCookieMap.isNotEmpty())) {
|
||||
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);
|
||||
//}
|
||||
if (cookieMap.containsKey(cookie.first) || doAllowNewCookies)
|
||||
cookieMap[cookie.first] = cookieValue;
|
||||
}
|
||||
else {
|
||||
val cookieMap = if (_otherCookieMap.containsKey(domainToUse))
|
||||
_otherCookieMap[domainToUse]!!;
|
||||
else {
|
||||
val newMap = hashMapOf<String, String>();
|
||||
_otherCookieMap[domainToUse] = newMap
|
||||
newMap;
|
||||
}
|
||||
if (cookieMap.containsKey(cookie.first) || doAllowNewCookies)
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
+4
-2
@@ -3,6 +3,7 @@ package com.futo.platformplayer.api.media.platforms.js.models
|
||||
import com.caoccao.javet.values.reference.V8ValueObject
|
||||
import com.futo.platformplayer.api.media.models.contents.ContentType
|
||||
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
|
||||
import com.futo.platformplayer.api.media.platforms.js.JSClient
|
||||
import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig
|
||||
import com.futo.platformplayer.getOrDefault
|
||||
import com.futo.platformplayer.getOrThrow
|
||||
@@ -10,13 +11,14 @@ import com.futo.platformplayer.getOrThrow
|
||||
interface IJSContent: IPlatformContent {
|
||||
|
||||
companion object {
|
||||
fun fromV8(config: SourcePluginConfig, obj: V8ValueObject): IPlatformContent {
|
||||
fun fromV8(plugin: JSClient, obj: V8ValueObject): IPlatformContent {
|
||||
val config = plugin.config;
|
||||
val type: Int = obj.getOrThrow(config, "contentType", "ContentItem");
|
||||
val pluginType: String? = obj.getOrDefault(config, "plugin_type", "ContentItem", null);
|
||||
|
||||
//TODO: Temporary workaround for intercepting details in lists
|
||||
if(pluginType != null && pluginType.endsWith("Details"))
|
||||
return IJSContentDetails.fromV8(config, obj);
|
||||
return IJSContentDetails.fromV8(plugin, obj);
|
||||
|
||||
return when(ContentType.fromInt(type)) {
|
||||
ContentType.MEDIA -> JSVideo(config, obj);
|
||||
|
||||
+5
-4
@@ -4,17 +4,18 @@ import com.caoccao.javet.values.reference.V8ValueObject
|
||||
import com.futo.platformplayer.api.media.models.contents.ContentType
|
||||
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
|
||||
import com.futo.platformplayer.api.media.models.contents.IPlatformContentDetails
|
||||
import com.futo.platformplayer.api.media.platforms.js.JSClient
|
||||
import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig
|
||||
import com.futo.platformplayer.getOrThrow
|
||||
|
||||
interface IJSContentDetails: IPlatformContent {
|
||||
|
||||
companion object {
|
||||
fun fromV8(config: SourcePluginConfig, obj: V8ValueObject): IPlatformContentDetails {
|
||||
val type: Int = obj.getOrThrow(config, "contentType", "ContentDetails");
|
||||
fun fromV8(plugin: JSClient, obj: V8ValueObject): IPlatformContentDetails {
|
||||
val type: Int = obj.getOrThrow(plugin.config, "contentType", "ContentDetails");
|
||||
return when(ContentType.fromInt(type)) {
|
||||
ContentType.MEDIA -> JSVideoDetails(config, obj);
|
||||
ContentType.POST -> JSPostDetails(config, obj);
|
||||
ContentType.MEDIA -> JSVideoDetails(plugin, obj);
|
||||
ContentType.POST -> JSPostDetails(plugin.config, obj);
|
||||
else -> throw NotImplementedError("Unknown content type ${type}");
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -2,13 +2,14 @@ package com.futo.platformplayer.api.media.platforms.js.models
|
||||
|
||||
import com.caoccao.javet.values.reference.V8ValueObject
|
||||
import com.futo.platformplayer.api.media.models.PlatformAuthorLink
|
||||
import com.futo.platformplayer.api.media.platforms.js.JSClient
|
||||
import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig
|
||||
import com.futo.platformplayer.api.media.structures.IPager
|
||||
import com.futo.platformplayer.engine.V8Plugin
|
||||
|
||||
class JSChannelPager : JSPager<PlatformAuthorLink>, IPager<PlatformAuthorLink> {
|
||||
|
||||
constructor(config: SourcePluginConfig, plugin: V8Plugin, pager: V8ValueObject) : super(config, plugin, pager) {}
|
||||
constructor(config: SourcePluginConfig, plugin: JSClient, pager: V8ValueObject) : super(config, plugin, pager) {}
|
||||
|
||||
override fun convertResult(obj: V8ValueObject): PlatformAuthorLink {
|
||||
return PlatformAuthorLink.fromV8(config, obj);
|
||||
|
||||
+3
-1
@@ -5,6 +5,7 @@ import com.futo.platformplayer.api.media.IPlatformClient
|
||||
import com.futo.platformplayer.api.media.models.PlatformAuthorLink
|
||||
import com.futo.platformplayer.api.media.models.comments.IPlatformComment
|
||||
import com.futo.platformplayer.api.media.models.ratings.IRating
|
||||
import com.futo.platformplayer.api.media.platforms.js.JSClient
|
||||
import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig
|
||||
import com.futo.platformplayer.api.media.structures.IPager
|
||||
import com.futo.platformplayer.engine.V8Plugin
|
||||
@@ -60,6 +61,7 @@ class JSComment : IPlatformComment {
|
||||
return null;
|
||||
|
||||
val obj = _comment!!.invoke<V8ValueObject>("getReplies", arrayOf<Any>());
|
||||
return JSCommentPager(_config!!, _plugin!!, obj);
|
||||
val plugin = if(client is JSClient) client else throw NotImplementedError("Only implemented for JSClient");
|
||||
return JSCommentPager(_config!!, plugin, obj);
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -2,15 +2,16 @@ package com.futo.platformplayer.api.media.platforms.js.models
|
||||
|
||||
import com.caoccao.javet.values.reference.V8ValueObject
|
||||
import com.futo.platformplayer.api.media.models.comments.IPlatformComment
|
||||
import com.futo.platformplayer.api.media.platforms.js.JSClient
|
||||
import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig
|
||||
import com.futo.platformplayer.api.media.structures.IPager
|
||||
import com.futo.platformplayer.engine.V8Plugin
|
||||
|
||||
class JSCommentPager : JSPager<IPlatformComment>, IPager<IPlatformComment> {
|
||||
|
||||
constructor(config: SourcePluginConfig, plugin: V8Plugin, pager: V8ValueObject) : super(config, plugin, pager) { }
|
||||
constructor(config: SourcePluginConfig, plugin: JSClient, pager: V8ValueObject) : super(config, plugin, pager) { }
|
||||
|
||||
override fun convertResult(obj: V8ValueObject): IPlatformComment {
|
||||
return JSComment(config, plugin, obj);
|
||||
return JSComment(config, plugin.getUnderlyingPlugin(), obj);
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -3,15 +3,16 @@ package com.futo.platformplayer.api.media.platforms.js.models
|
||||
import com.caoccao.javet.values.reference.V8ValueObject
|
||||
import com.futo.platformplayer.api.media.IPluginSourced
|
||||
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
|
||||
import com.futo.platformplayer.api.media.platforms.js.JSClient
|
||||
import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig
|
||||
import com.futo.platformplayer.engine.V8Plugin
|
||||
|
||||
class JSContentPager : JSPager<IPlatformContent>, IPluginSourced {
|
||||
override val sourceConfig: SourcePluginConfig get() = config;
|
||||
|
||||
constructor(config: SourcePluginConfig, plugin: V8Plugin, pager: V8ValueObject) : super(config, plugin, pager) {}
|
||||
constructor(config: SourcePluginConfig, plugin: JSClient, pager: V8ValueObject) : super(config, plugin, pager) {}
|
||||
|
||||
override fun convertResult(obj: V8ValueObject): IPlatformContent {
|
||||
return IJSContent.fromV8(config, obj);
|
||||
return IJSContent.fromV8(plugin, obj);
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -2,6 +2,7 @@ package com.futo.platformplayer.api.media.platforms.js.models
|
||||
|
||||
import com.caoccao.javet.values.reference.V8ValueObject
|
||||
import com.futo.platformplayer.api.media.models.live.IPlatformLiveEvent
|
||||
import com.futo.platformplayer.api.media.platforms.js.JSClient
|
||||
import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig
|
||||
import com.futo.platformplayer.api.media.structures.IPlatformLiveEventPager
|
||||
import com.futo.platformplayer.engine.V8Plugin
|
||||
@@ -10,7 +11,7 @@ import com.futo.platformplayer.getOrThrow
|
||||
class JSLiveEventPager : JSPager<IPlatformLiveEvent>, IPlatformLiveEventPager {
|
||||
override var nextRequest: Int;
|
||||
|
||||
constructor(config: SourcePluginConfig, plugin: V8Plugin, pager: V8ValueObject) : super(config, plugin, pager) {
|
||||
constructor(config: SourcePluginConfig, plugin: JSClient, pager: V8ValueObject) : super(config, plugin, pager) {
|
||||
nextRequest = pager.getOrThrow(config, "nextRequest", "LiveEventPager");
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.os.Looper
|
||||
import com.caoccao.javet.values.reference.V8ValueArray
|
||||
import com.caoccao.javet.values.reference.V8ValueObject
|
||||
import com.futo.platformplayer.BuildConfig
|
||||
import com.futo.platformplayer.api.media.platforms.js.JSClient
|
||||
import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig
|
||||
import com.futo.platformplayer.api.media.structures.IPager
|
||||
import com.futo.platformplayer.engine.V8Plugin
|
||||
@@ -12,7 +13,7 @@ import com.futo.platformplayer.getOrThrow
|
||||
import com.futo.platformplayer.warnIfMainThread
|
||||
|
||||
abstract class JSPager<T> : IPager<T> {
|
||||
protected val plugin: V8Plugin;
|
||||
protected val plugin: JSClient;
|
||||
protected val config: SourcePluginConfig;
|
||||
protected var pager: V8ValueObject;
|
||||
|
||||
@@ -21,9 +22,9 @@ abstract class JSPager<T> : IPager<T> {
|
||||
private var _hasMorePages: Boolean = false;
|
||||
//private var _morePagesWasFalse: Boolean = false;
|
||||
|
||||
val isAvailable get() = plugin._runtime?.let { !it.isClosed && !it.isDead } ?: false;
|
||||
val isAvailable get() = plugin.getUnderlyingPlugin()._runtime?.let { !it.isClosed && !it.isDead } ?: false;
|
||||
|
||||
constructor(config: SourcePluginConfig, plugin: V8Plugin, pager: V8ValueObject) {
|
||||
constructor(config: SourcePluginConfig, plugin: JSClient, pager: V8ValueObject) {
|
||||
this.plugin = plugin;
|
||||
this.pager = pager;
|
||||
this.config = config;
|
||||
@@ -43,7 +44,7 @@ abstract class JSPager<T> : IPager<T> {
|
||||
override fun nextPage() {
|
||||
warnIfMainThread("JSPager.nextPage");
|
||||
|
||||
pager = plugin.catchScriptErrors("[${plugin.config.name}] JSPager", "pager.nextPage()") {
|
||||
pager = plugin.getUnderlyingPlugin().catchScriptErrors("[${plugin.config.name}] JSPager", "pager.nextPage()") {
|
||||
pager.invoke("nextPage", arrayOf<Any>());
|
||||
};
|
||||
_hasMorePages = pager.getOrDefault(config, "hasMore", "Pager", false) ?: false;
|
||||
|
||||
+2
-1
@@ -4,6 +4,7 @@ import com.caoccao.javet.values.reference.V8ValueObject
|
||||
import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylistDetails
|
||||
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
|
||||
import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo
|
||||
import com.futo.platformplayer.api.media.platforms.js.JSClient
|
||||
import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig
|
||||
import com.futo.platformplayer.api.media.structures.IPager
|
||||
import com.futo.platformplayer.engine.V8Plugin
|
||||
@@ -13,7 +14,7 @@ import com.futo.platformplayer.models.Playlist
|
||||
class JSPlaylistDetails: JSPlaylist, IPlatformPlaylistDetails {
|
||||
override val contents: IPager<IPlatformVideo>;
|
||||
|
||||
constructor(plugin: V8Plugin, config: SourcePluginConfig, obj: V8ValueObject): super(config, obj) {
|
||||
constructor(plugin: JSClient, config: SourcePluginConfig, obj: V8ValueObject): super(config, obj) {
|
||||
contents = JSVideoPager(config, plugin, obj.getOrThrow(config, "contents", "PlaylistDetails"));
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -2,13 +2,14 @@ package com.futo.platformplayer.api.media.platforms.js.models
|
||||
|
||||
import com.caoccao.javet.values.reference.V8ValueObject
|
||||
import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylist
|
||||
import com.futo.platformplayer.api.media.platforms.js.JSClient
|
||||
import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig
|
||||
import com.futo.platformplayer.api.media.structures.IPager
|
||||
import com.futo.platformplayer.engine.V8Plugin
|
||||
|
||||
class JSPlaylistPager : JSPager<IPlatformPlaylist>, IPager<IPlatformPlaylist> {
|
||||
|
||||
constructor(config: SourcePluginConfig, plugin: V8Plugin, pager: V8ValueObject) : super(config, plugin, pager) {}
|
||||
constructor(config: SourcePluginConfig, plugin: JSClient, pager: V8ValueObject) : super(config, plugin, pager) {}
|
||||
|
||||
override fun convertResult(obj: V8ValueObject): IPlatformPlaylist {
|
||||
return JSPlaylist(config, obj);
|
||||
|
||||
+1
-1
@@ -54,6 +54,6 @@ class JSPostDetails : JSPost, IPlatformPost, IPlatformPostDetails {
|
||||
|
||||
private fun getCommentsJS(client: JSClient): JSCommentPager {
|
||||
val commentPager = _content.invoke<V8ValueObject>("getComments", arrayOf<Any>());
|
||||
return JSCommentPager(_pluginConfig, client.getUnderlyingPlugin(), commentPager);
|
||||
return JSCommentPager(_pluginConfig, client, commentPager);
|
||||
}
|
||||
}
|
||||
+71
-8
@@ -1,18 +1,81 @@
|
||||
package com.futo.platformplayer.api.media.platforms.js.models
|
||||
|
||||
import android.net.Uri
|
||||
import com.caoccao.javet.values.reference.V8ValueObject
|
||||
import com.futo.platformplayer.api.media.models.modifier.IModifierOptions
|
||||
import com.futo.platformplayer.api.media.models.modifier.IRequest
|
||||
import com.futo.platformplayer.api.media.platforms.js.JSClient
|
||||
import com.futo.platformplayer.engine.IV8PluginConfig
|
||||
import com.futo.platformplayer.getOrThrow
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
import com.futo.platformplayer.getOrDefault
|
||||
|
||||
@kotlinx.serialization.Serializable
|
||||
class JSRequest : JSRequestModifier.IRequest {
|
||||
override val url: String;
|
||||
override val headers: Map<String, String>;
|
||||
class JSRequest : IRequest {
|
||||
private val _v8Url: String?;
|
||||
private val _v8Headers: Map<String, String>?;
|
||||
private val _v8Options: Options?;
|
||||
|
||||
constructor(config: IV8PluginConfig, obj: V8ValueObject) {
|
||||
override var url: String? = null;
|
||||
override lateinit var headers: Map<String, String>;
|
||||
|
||||
constructor(plugin: JSClient, url: String?, headers: Map<String, String>?, options: Options?, originalUrl: String?, originalHeaders: Map<String, String>?) {
|
||||
_v8Url = url;
|
||||
_v8Headers = headers;
|
||||
_v8Options = options;
|
||||
initialize(plugin, originalUrl, originalHeaders);
|
||||
}
|
||||
constructor(plugin: JSClient, obj: V8ValueObject, originalUrl: String?, originalHeaders: Map<String, String>?) {
|
||||
val contextName = "ModifyRequestResponse";
|
||||
url = obj.getOrThrow(config, "url", contextName);
|
||||
headers = obj.getOrThrow(config, "headers", contextName);
|
||||
val config = plugin.config;
|
||||
_v8Url = obj.getOrDefault<String>(config, "url", contextName, null);
|
||||
_v8Headers = obj.getOrDefault<Map<String, String>>(config, "headers", contextName, null);
|
||||
_v8Options = obj.getOrDefault<V8ValueObject>(config, "options", "JSRequestModifier.options", null)?.let {
|
||||
Options(config, it);
|
||||
}
|
||||
initialize(plugin, originalUrl, originalHeaders);
|
||||
}
|
||||
|
||||
private fun initialize(plugin: JSClient, originalUrl: String?, originalHeaders: Map<String, String>?) {
|
||||
val config = plugin.config;
|
||||
url = _v8Url ?: originalUrl;
|
||||
headers = _v8Headers ?: originalHeaders ?: mapOf();
|
||||
|
||||
if(_v8Options != null) {
|
||||
if(_v8Options.applyCookieClient != null && url != null) {
|
||||
val client = plugin.getHttpClientById(_v8Options.applyCookieClient);
|
||||
if(client != null) {
|
||||
val toModifyHeaders = headers.toMutableMap();
|
||||
client.applyHeaders(Uri.parse(url), toModifyHeaders, false, true);
|
||||
headers = toModifyHeaders;
|
||||
}
|
||||
}
|
||||
if(_v8Options.applyAuthClient != null && url != null) {
|
||||
val client = plugin.getHttpClientById(_v8Options.applyAuthClient);
|
||||
if(client != null) {
|
||||
val toModifyHeaders = headers.toMutableMap();
|
||||
client.applyHeaders(Uri.parse(url), toModifyHeaders, true, false);
|
||||
headers = toModifyHeaders;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun modify(plugin: JSClient, originalUrl: String?, originalHeaders: Map<String, String>?): JSRequest {
|
||||
return JSRequest(plugin, _v8Url, _v8Headers, _v8Options, originalUrl, originalHeaders);
|
||||
}
|
||||
|
||||
|
||||
@kotlinx.serialization.Serializable
|
||||
class Options: IModifierOptions {
|
||||
override val applyAuthClient: String?;
|
||||
override val applyCookieClient: String?;
|
||||
|
||||
constructor(config: IV8PluginConfig, obj: V8ValueObject) {
|
||||
applyAuthClient = obj.getOrDefault(config, "applyAuthClient", "JSRequestModifier.options.applyAuthClient", null);
|
||||
applyCookieClient = obj.getOrDefault(config, "applyCookieClient", "JSRequestModifier.options.applyCookieClient", null);
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
}
|
||||
}
|
||||
+17
-11
@@ -1,19 +1,28 @@
|
||||
package com.futo.platformplayer.api.media.platforms.js.models
|
||||
|
||||
import android.net.Uri
|
||||
import com.caoccao.javet.values.reference.V8ValueObject
|
||||
import com.futo.platformplayer.api.media.models.modifier.IRequest
|
||||
import com.futo.platformplayer.api.media.models.modifier.IRequestModifier
|
||||
import com.futo.platformplayer.api.media.platforms.js.JSClient
|
||||
import com.futo.platformplayer.engine.IV8PluginConfig
|
||||
import com.futo.platformplayer.engine.V8Plugin
|
||||
import com.futo.platformplayer.engine.exceptions.ScriptImplementationException
|
||||
import com.futo.platformplayer.getOrDefault
|
||||
import com.futo.platformplayer.getOrNull
|
||||
import com.futo.platformplayer.getOrThrow
|
||||
|
||||
class JSRequestModifier {
|
||||
class JSRequestModifier: IRequestModifier {
|
||||
private val _plugin: JSClient;
|
||||
private val _config: IV8PluginConfig;
|
||||
private var _modifier: V8ValueObject;
|
||||
val allowByteSkip: Boolean;
|
||||
override var allowByteSkip: Boolean;
|
||||
|
||||
constructor(config: IV8PluginConfig, modifier: V8ValueObject) {
|
||||
constructor(plugin: JSClient, modifier: V8ValueObject) {
|
||||
this._plugin = plugin;
|
||||
this._modifier = modifier;
|
||||
this._config = config;
|
||||
this._config = plugin.config;
|
||||
val config = plugin.config;
|
||||
|
||||
allowByteSkip = modifier.getOrNull(config, "allowByteSkip", "JSRequestModifier") ?: true;
|
||||
|
||||
@@ -21,22 +30,19 @@ class JSRequestModifier {
|
||||
throw ScriptImplementationException(config, "RequestModifier is missing modifyRequest", null);
|
||||
}
|
||||
|
||||
fun modifyRequest(url: String, headers: Map<String, String>): IRequest {
|
||||
override fun modifyRequest(url: String, headers: Map<String, String>): IRequest {
|
||||
if (_modifier.isClosed) {
|
||||
return Request(url, headers);
|
||||
}
|
||||
|
||||
val result = V8Plugin.catchScriptErrors<Any>(_config, "[${_config.name}] JSRequestModifier", "builder.modifyRequest()") {
|
||||
_modifier.invoke("modifyRequest", url, headers);
|
||||
};
|
||||
} as V8ValueObject;
|
||||
|
||||
return JSRequest(_config, result as V8ValueObject);
|
||||
val req = JSRequest(_plugin, result, url, headers);
|
||||
return req;
|
||||
}
|
||||
|
||||
interface IRequest {
|
||||
val url: String;
|
||||
val headers: Map<String, String>;
|
||||
}
|
||||
|
||||
data class Request(override val url: String, override val headers: Map<String, String>) : IRequest;
|
||||
}
|
||||
+7
-6
@@ -44,13 +44,14 @@ class JSVideoDetails : JSVideo, IPlatformVideoDetails {
|
||||
override val subtitles: List<ISubtitleSource>;
|
||||
|
||||
|
||||
constructor(config: SourcePluginConfig, obj: V8ValueObject) : super(config, obj) {
|
||||
constructor(plugin: JSClient, obj: V8ValueObject) : super(plugin.config, obj) {
|
||||
val contextName = "VideoDetails";
|
||||
val config = plugin.config;
|
||||
description = _content.getOrThrow(config, "description", contextName);
|
||||
video = JSVideoSourceDescriptor.fromV8(config, _content.getOrThrow(config, "video", contextName));
|
||||
dash = JSSource.fromV8DashNullable(config, _content.getOrThrowNullable<V8ValueObject>(config, "dash", contextName));
|
||||
hls = JSSource.fromV8HLSNullable(config, _content.getOrThrowNullable<V8ValueObject>(config, "hls", contextName));
|
||||
live = JSSource.fromV8VideoNullable(config, _content.getOrThrowNullable<V8ValueObject>(config, "live", contextName));
|
||||
video = JSVideoSourceDescriptor.fromV8(plugin, _content.getOrThrow(config, "video", contextName));
|
||||
dash = JSSource.fromV8DashNullable(plugin, _content.getOrThrowNullable<V8ValueObject>(config, "dash", contextName));
|
||||
hls = JSSource.fromV8HLSNullable(plugin, _content.getOrThrowNullable<V8ValueObject>(config, "hls", contextName));
|
||||
live = JSSource.fromV8VideoNullable(plugin, _content.getOrThrowNullable<V8ValueObject>(config, "live", contextName));
|
||||
rating = IRating.fromV8OrDefault(config, _content.getOrDefault<V8ValueObject>(config, "rating", contextName, null), RatingLikes(0));
|
||||
|
||||
if(!_content.has("subtitles"))
|
||||
@@ -105,6 +106,6 @@ class JSVideoDetails : JSVideo, IPlatformVideoDetails {
|
||||
if (commentPager !is V8ValueObject) //TODO: Maybe handle this better?
|
||||
return null;
|
||||
|
||||
return JSCommentPager(_pluginConfig, client.getUnderlyingPlugin(), commentPager);
|
||||
return JSCommentPager(_pluginConfig, client, commentPager);
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -2,12 +2,13 @@ package com.futo.platformplayer.api.media.platforms.js.models
|
||||
|
||||
import com.caoccao.javet.values.reference.V8ValueObject
|
||||
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.SourcePluginConfig
|
||||
import com.futo.platformplayer.engine.V8Plugin
|
||||
|
||||
class JSVideoPager : JSPager<IPlatformVideo> {
|
||||
|
||||
constructor(config: SourcePluginConfig, plugin: V8Plugin, pager: V8ValueObject) : super(config, plugin, pager) {}
|
||||
constructor(config: SourcePluginConfig, plugin: JSClient, pager: V8ValueObject) : super(config, plugin, pager) {}
|
||||
|
||||
override fun convertResult(obj: V8ValueObject): IPlatformVideo {
|
||||
return JSVideo(config, obj);
|
||||
|
||||
+4
-1
@@ -2,7 +2,9 @@ package com.futo.platformplayer.api.media.platforms.js.models.sources
|
||||
|
||||
import com.caoccao.javet.values.reference.V8ValueObject
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.IAudioUrlSource
|
||||
import com.futo.platformplayer.api.media.platforms.js.JSClient
|
||||
import com.futo.platformplayer.engine.IV8PluginConfig
|
||||
import com.futo.platformplayer.engine.V8Plugin
|
||||
import com.futo.platformplayer.getOrDefault
|
||||
import com.futo.platformplayer.getOrThrow
|
||||
|
||||
@@ -19,8 +21,9 @@ open class JSAudioUrlSource : IAudioUrlSource, JSSource {
|
||||
|
||||
override var priority: Boolean = false;
|
||||
|
||||
constructor(config: IV8PluginConfig, obj: V8ValueObject) : super(TYPE_AUDIOURL, config, obj) {
|
||||
constructor(plugin: JSClient, obj: V8ValueObject) : super(TYPE_AUDIOURL, plugin, obj) {
|
||||
val contextName = "AudioUrlSource";
|
||||
val config = plugin.config;
|
||||
|
||||
bitrate = _obj.getOrThrow(config, "bitrate", contextName);
|
||||
container = _obj.getOrThrow(config, "container", contextName);
|
||||
|
||||
+4
-1
@@ -3,7 +3,9 @@ package com.futo.platformplayer.api.media.platforms.js.models.sources
|
||||
import com.caoccao.javet.values.reference.V8ValueObject
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.other.IStreamMetaDataSource
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.other.StreamMetaData
|
||||
import com.futo.platformplayer.api.media.platforms.js.JSClient
|
||||
import com.futo.platformplayer.engine.IV8PluginConfig
|
||||
import com.futo.platformplayer.engine.V8Plugin
|
||||
import com.futo.platformplayer.getOrDefault
|
||||
|
||||
class JSAudioUrlRangeSource : JSAudioUrlSource, IStreamMetaDataSource {
|
||||
@@ -22,8 +24,9 @@ class JSAudioUrlRangeSource : JSAudioUrlSource, IStreamMetaDataSource {
|
||||
&& indexEnd != null)
|
||||
StreamMetaData(initStart, initEnd, indexStart, indexEnd) else null;
|
||||
|
||||
constructor(config: IV8PluginConfig, obj: V8ValueObject) : super(config, obj) {
|
||||
constructor(plugin: JSClient, obj: V8ValueObject) : super(plugin, obj) {
|
||||
val contextName = "JSAudioUrlRangeSource";
|
||||
val config = plugin.config;
|
||||
|
||||
itagId = _obj.getOrDefault(config, "itagId", contextName, null);
|
||||
initStart = _obj.getOrDefault(config, "initStart", contextName, null);
|
||||
|
||||
+4
-2
@@ -3,7 +3,9 @@ package com.futo.platformplayer.api.media.platforms.js.models.sources
|
||||
import com.caoccao.javet.values.reference.V8ValueObject
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.IDashManifestSource
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.IVideoUrlSource
|
||||
import com.futo.platformplayer.api.media.platforms.js.JSClient
|
||||
import com.futo.platformplayer.engine.IV8PluginConfig
|
||||
import com.futo.platformplayer.engine.V8Plugin
|
||||
import com.futo.platformplayer.getOrNull
|
||||
import com.futo.platformplayer.getOrThrow
|
||||
|
||||
@@ -19,9 +21,9 @@ class JSDashManifestSource : IVideoUrlSource, IDashManifestSource, JSSource {
|
||||
|
||||
override var priority: Boolean = false;
|
||||
|
||||
constructor(config: IV8PluginConfig, obj: V8ValueObject) : super(TYPE_DASH, config, obj) {
|
||||
constructor(plugin: JSClient, obj: V8ValueObject) : super(TYPE_DASH, plugin, obj) {
|
||||
val contextName = "DashSource";
|
||||
|
||||
val config = plugin.config;
|
||||
name = _obj.getOrThrow(config, "name", contextName);
|
||||
url = _obj.getOrThrow(config, "url", contextName);
|
||||
duration = _obj.getOrThrow(config, "duration", contextName);
|
||||
|
||||
+6
-3
@@ -4,7 +4,9 @@ import com.caoccao.javet.values.V8Value
|
||||
import com.caoccao.javet.values.reference.V8ValueObject
|
||||
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.platforms.js.JSClient
|
||||
import com.futo.platformplayer.engine.IV8PluginConfig
|
||||
import com.futo.platformplayer.engine.V8Plugin
|
||||
import com.futo.platformplayer.getOrNull
|
||||
import com.futo.platformplayer.getOrThrow
|
||||
import com.futo.platformplayer.orNull
|
||||
@@ -20,8 +22,9 @@ class JSHLSManifestAudioSource : IHLSManifestAudioSource, JSSource {
|
||||
|
||||
override var priority: Boolean = false;
|
||||
|
||||
constructor(config: IV8PluginConfig, obj: V8ValueObject) : super(TYPE_HLS, config, obj) {
|
||||
constructor(plugin: JSClient, obj: V8ValueObject) : super(TYPE_HLS, plugin, obj) {
|
||||
val contextName = "HLSAudioSource";
|
||||
val config = plugin.config;
|
||||
|
||||
name = _obj.getOrThrow(config, "name", contextName);
|
||||
url = _obj.getOrThrow(config, "url", contextName);
|
||||
@@ -33,7 +36,7 @@ class JSHLSManifestAudioSource : IHLSManifestAudioSource, JSSource {
|
||||
|
||||
|
||||
companion object {
|
||||
fun fromV8HLSNullable(config: IV8PluginConfig, obj: V8Value?) : JSHLSManifestAudioSource? = obj.orNull { fromV8HLS(config, it as V8ValueObject) };
|
||||
fun fromV8HLS(config: IV8PluginConfig, obj: V8ValueObject) : JSHLSManifestAudioSource = JSHLSManifestAudioSource(config, obj);
|
||||
fun fromV8HLSNullable(plugin: JSClient, obj: V8Value?) : JSHLSManifestAudioSource? = obj.orNull { fromV8HLS(plugin, it as V8ValueObject) };
|
||||
fun fromV8HLS(plugin: JSClient, obj: V8ValueObject) : JSHLSManifestAudioSource = JSHLSManifestAudioSource(plugin, obj);
|
||||
}
|
||||
}
|
||||
+4
-1
@@ -3,7 +3,9 @@ package com.futo.platformplayer.api.media.platforms.js.models.sources
|
||||
import com.caoccao.javet.values.reference.V8ValueObject
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.IHLSManifestSource
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.IVideoUrlSource
|
||||
import com.futo.platformplayer.api.media.platforms.js.JSClient
|
||||
import com.futo.platformplayer.engine.IV8PluginConfig
|
||||
import com.futo.platformplayer.engine.V8Plugin
|
||||
import com.futo.platformplayer.getOrNull
|
||||
import com.futo.platformplayer.getOrThrow
|
||||
|
||||
@@ -19,8 +21,9 @@ class JSHLSManifestSource : IHLSManifestSource, JSSource {
|
||||
|
||||
override var priority: Boolean = false;
|
||||
|
||||
constructor(config: IV8PluginConfig, obj: V8ValueObject) : super(TYPE_HLS, config, obj) {
|
||||
constructor(plugin: JSClient, obj: V8ValueObject) : super(TYPE_HLS, plugin, obj) {
|
||||
val contextName = "HLSSource";
|
||||
val config = plugin.config;
|
||||
|
||||
name = _obj.getOrThrow(config, "name", contextName);
|
||||
url = _obj.getOrThrow(config, "url", contextName);
|
||||
|
||||
+39
-32
@@ -1,34 +1,50 @@
|
||||
package com.futo.platformplayer.api.media.platforms.js.models.sources
|
||||
|
||||
import androidx.media3.datasource.DefaultHttpDataSource
|
||||
import androidx.media3.datasource.HttpDataSource
|
||||
import com.caoccao.javet.values.V8Value
|
||||
import com.caoccao.javet.values.reference.V8ValueObject
|
||||
import com.futo.platformplayer.api.media.models.modifier.AdhocRequestModifier
|
||||
import com.futo.platformplayer.api.media.models.modifier.IRequestModifier
|
||||
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.platforms.js.JSClient
|
||||
import com.futo.platformplayer.api.media.platforms.js.models.JSRequest
|
||||
import com.futo.platformplayer.api.media.platforms.js.models.JSRequestModifier
|
||||
import com.futo.platformplayer.engine.IV8PluginConfig
|
||||
import com.futo.platformplayer.engine.V8Plugin
|
||||
import com.futo.platformplayer.getOrDefault
|
||||
import com.futo.platformplayer.orNull
|
||||
import com.futo.platformplayer.views.video.datasources.JSHttpDataSource
|
||||
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource
|
||||
import com.google.android.exoplayer2.upstream.HttpDataSource
|
||||
|
||||
abstract class JSSource {
|
||||
protected val _plugin: JSClient;
|
||||
protected val _config: IV8PluginConfig;
|
||||
protected val _obj: V8ValueObject;
|
||||
private val _hasRequestModifier: Boolean;
|
||||
val hasRequestModifier: Boolean;
|
||||
private val _requestModifier: JSRequest?;
|
||||
|
||||
val type : String;
|
||||
|
||||
constructor(type: String, config: IV8PluginConfig, obj: V8ValueObject) {
|
||||
this._config = config;
|
||||
constructor(type: String, plugin: JSClient, obj: V8ValueObject) {
|
||||
this._plugin = plugin;
|
||||
this._config = plugin.config;
|
||||
this._obj = obj;
|
||||
this.type = type;
|
||||
|
||||
_hasRequestModifier = obj.has("getRequestModifier");
|
||||
_requestModifier = obj.getOrDefault<V8ValueObject>(_config, "requestModifier", "JSSource.requestModifier", null)?.let {
|
||||
JSRequest(plugin, it, null, null);
|
||||
}
|
||||
hasRequestModifier = _requestModifier != null || obj.has("getRequestModifier");
|
||||
}
|
||||
|
||||
fun getRequestModifier(): JSRequestModifier? {
|
||||
if (!_hasRequestModifier || _obj.isClosed) {
|
||||
fun getRequestModifier(): IRequestModifier? {
|
||||
if(_requestModifier != null)
|
||||
return AdhocRequestModifier { url, headers ->
|
||||
return@AdhocRequestModifier _requestModifier.modify(_plugin, url, headers);
|
||||
};
|
||||
|
||||
if (!hasRequestModifier || _obj.isClosed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -40,16 +56,7 @@ abstract class JSSource {
|
||||
return null;
|
||||
}
|
||||
|
||||
return JSRequestModifier(_config, result)
|
||||
}
|
||||
|
||||
fun getHttpDataSourceFactory(): HttpDataSource.Factory {
|
||||
val requestModifier = getRequestModifier();
|
||||
return if (requestModifier != null) {
|
||||
JSHttpDataSource.Factory().setRequestModifier(requestModifier);
|
||||
} else {
|
||||
DefaultHttpDataSource.Factory();
|
||||
}
|
||||
return JSRequestModifier(_plugin, result)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -60,28 +67,28 @@ abstract class JSSource {
|
||||
const val TYPE_DASH = "DashSource";
|
||||
const val TYPE_HLS = "HLSSource";
|
||||
|
||||
fun fromV8VideoNullable(config: IV8PluginConfig, obj: V8Value?) : IVideoSource? = obj.orNull { fromV8Video(config, it as V8ValueObject) };
|
||||
fun fromV8Video(config: IV8PluginConfig, obj: V8ValueObject) : IVideoSource {
|
||||
fun fromV8VideoNullable(plugin: JSClient, obj: V8Value?) : IVideoSource? = obj.orNull { fromV8Video(plugin, it as V8ValueObject) };
|
||||
fun fromV8Video(plugin: JSClient, obj: V8ValueObject) : IVideoSource {
|
||||
val type = obj.getString("plugin_type");
|
||||
return when(type) {
|
||||
TYPE_VIDEOURL -> JSVideoUrlSource(config, obj);
|
||||
TYPE_VIDEO_WITH_METADATA -> JSVideoUrlRangeSource(config, obj);
|
||||
TYPE_HLS -> fromV8HLS(config, obj);
|
||||
TYPE_DASH -> fromV8Dash(config, obj);
|
||||
TYPE_VIDEOURL -> JSVideoUrlSource(plugin, obj);
|
||||
TYPE_VIDEO_WITH_METADATA -> JSVideoUrlRangeSource(plugin, obj);
|
||||
TYPE_HLS -> fromV8HLS(plugin, obj);
|
||||
TYPE_DASH -> fromV8Dash(plugin, obj);
|
||||
else -> throw NotImplementedError("Unknown type ${type}");
|
||||
}
|
||||
}
|
||||
fun fromV8DashNullable(config: IV8PluginConfig, obj: V8Value?) : JSDashManifestSource? = obj.orNull { fromV8Dash(config, it as V8ValueObject) };
|
||||
fun fromV8Dash(config: IV8PluginConfig, obj: V8ValueObject) : JSDashManifestSource = JSDashManifestSource(config, obj);
|
||||
fun fromV8HLSNullable(config: IV8PluginConfig, obj: V8Value?) : JSHLSManifestSource? = obj.orNull { fromV8HLS(config, it as V8ValueObject) };
|
||||
fun fromV8HLS(config: IV8PluginConfig, obj: V8ValueObject) : JSHLSManifestSource = JSHLSManifestSource(config, obj);
|
||||
fun fromV8DashNullable(plugin: JSClient, obj: V8Value?) : JSDashManifestSource? = obj.orNull { fromV8Dash(plugin, it as V8ValueObject) };
|
||||
fun fromV8Dash(plugin: JSClient, obj: V8ValueObject) : JSDashManifestSource = JSDashManifestSource(plugin, obj);
|
||||
fun fromV8HLSNullable(plugin: JSClient, obj: V8Value?) : JSHLSManifestSource? = obj.orNull { fromV8HLS(plugin, it as V8ValueObject) };
|
||||
fun fromV8HLS(plugin: JSClient, obj: V8ValueObject) : JSHLSManifestSource = JSHLSManifestSource(plugin, obj);
|
||||
|
||||
fun fromV8Audio(config: IV8PluginConfig, obj: V8ValueObject) : IAudioSource {
|
||||
fun fromV8Audio(plugin: JSClient, obj: V8ValueObject) : IAudioSource {
|
||||
val type = obj.getString("plugin_type");
|
||||
return when(type) {
|
||||
TYPE_HLS -> JSHLSManifestAudioSource.fromV8HLS(config, obj);
|
||||
TYPE_AUDIOURL -> JSAudioUrlSource(config, obj);
|
||||
TYPE_AUDIO_WITH_METADATA -> JSAudioUrlRangeSource(config, obj);
|
||||
TYPE_HLS -> JSHLSManifestAudioSource.fromV8HLS(plugin, obj);
|
||||
TYPE_AUDIOURL -> JSAudioUrlSource(plugin, obj);
|
||||
TYPE_AUDIO_WITH_METADATA -> JSAudioUrlRangeSource(plugin, obj);
|
||||
else -> throw NotImplementedError("Unknown type ${type}");
|
||||
}
|
||||
}
|
||||
|
||||
+5
-3
@@ -5,6 +5,7 @@ import com.caoccao.javet.values.reference.V8ValueObject
|
||||
import com.futo.platformplayer.api.media.models.streams.VideoUnMuxedSourceDescriptor
|
||||
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.platforms.js.JSClient
|
||||
import com.futo.platformplayer.engine.IV8PluginConfig
|
||||
import com.futo.platformplayer.getOrThrow
|
||||
|
||||
@@ -15,15 +16,16 @@ class JSUnMuxVideoSourceDescriptor: VideoUnMuxedSourceDescriptor {
|
||||
override val videoSources: Array<IVideoSource>;
|
||||
override val audioSources: Array<IAudioSource>;
|
||||
|
||||
constructor(config: IV8PluginConfig, obj: V8ValueObject) {
|
||||
constructor(plugin: JSClient, obj: V8ValueObject) {
|
||||
this._obj = obj;
|
||||
val config = plugin.config;
|
||||
val contextName = "UnMuxVideoSource"
|
||||
this.isUnMuxed = obj.getOrThrow(config, "isUnMuxed", contextName);
|
||||
this.videoSources = obj.getOrThrow<V8ValueArray>(config, "videoSources", contextName).toArray()
|
||||
.map { JSSource.fromV8Video(config, it as V8ValueObject) }
|
||||
.map { JSSource.fromV8Video(plugin, it as V8ValueObject) }
|
||||
.toTypedArray();
|
||||
this.audioSources = obj.getOrThrow<V8ValueArray>(config, "audioSources", contextName).toArray()
|
||||
.map { JSSource.fromV8Audio(config, it as V8ValueObject) }
|
||||
.map { JSSource.fromV8Audio(plugin, it as V8ValueObject) }
|
||||
.toTypedArray();
|
||||
}
|
||||
}
|
||||
+8
-5
@@ -5,7 +5,9 @@ import com.caoccao.javet.values.reference.V8ValueObject
|
||||
import com.futo.platformplayer.api.media.models.streams.IVideoSourceDescriptor
|
||||
import com.futo.platformplayer.api.media.models.streams.VideoMuxedSourceDescriptor
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.IVideoSource
|
||||
import com.futo.platformplayer.api.media.platforms.js.JSClient
|
||||
import com.futo.platformplayer.engine.IV8PluginConfig
|
||||
import com.futo.platformplayer.engine.V8Plugin
|
||||
import com.futo.platformplayer.getOrThrow
|
||||
|
||||
class JSVideoSourceDescriptor: VideoMuxedSourceDescriptor {
|
||||
@@ -14,12 +16,13 @@ class JSVideoSourceDescriptor: VideoMuxedSourceDescriptor {
|
||||
override val isUnMuxed: Boolean;
|
||||
override val videoSources: Array<IVideoSource>;
|
||||
|
||||
constructor(config: IV8PluginConfig, obj: V8ValueObject) {
|
||||
constructor(plugin: JSClient, obj: V8ValueObject) {
|
||||
this._obj = obj;
|
||||
val config = plugin.config;
|
||||
val contextName = "VideoSourceDescriptor";
|
||||
this.isUnMuxed = obj.getOrThrow(config, "isUnMuxed", contextName);
|
||||
this.videoSources = obj.getOrThrow<V8ValueArray>(config, "videoSources", contextName).toArray()
|
||||
.map { JSSource.fromV8Video(config, it as V8ValueObject) }
|
||||
.map { JSSource.fromV8Video(plugin, it as V8ValueObject) }
|
||||
.toTypedArray();
|
||||
}
|
||||
|
||||
@@ -28,11 +31,11 @@ class JSVideoSourceDescriptor: VideoMuxedSourceDescriptor {
|
||||
const val TYPE_UNMUXED = "UnMuxVideoSourceDescriptor";
|
||||
|
||||
|
||||
fun fromV8(config: IV8PluginConfig, obj: V8ValueObject) : IVideoSourceDescriptor {
|
||||
fun fromV8(plugin: JSClient, obj: V8ValueObject) : IVideoSourceDescriptor {
|
||||
val type = obj.getString("plugin_type")
|
||||
return when(type) {
|
||||
TYPE_MUXED -> JSVideoSourceDescriptor(config, obj);
|
||||
TYPE_UNMUXED -> JSUnMuxVideoSourceDescriptor(config, obj);
|
||||
TYPE_MUXED -> JSVideoSourceDescriptor(plugin, obj);
|
||||
TYPE_UNMUXED -> JSUnMuxVideoSourceDescriptor(plugin, obj);
|
||||
else -> throw NotImplementedError("Unknown type: ${type}");
|
||||
}
|
||||
}
|
||||
|
||||
+4
-1
@@ -2,7 +2,9 @@ package com.futo.platformplayer.api.media.platforms.js.models.sources
|
||||
|
||||
import com.caoccao.javet.values.reference.V8ValueObject
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.IVideoUrlSource
|
||||
import com.futo.platformplayer.api.media.platforms.js.JSClient
|
||||
import com.futo.platformplayer.engine.IV8PluginConfig
|
||||
import com.futo.platformplayer.engine.V8Plugin
|
||||
import com.futo.platformplayer.getOrNull
|
||||
import com.futo.platformplayer.getOrThrow
|
||||
|
||||
@@ -18,8 +20,9 @@ open class JSVideoUrlSource : IVideoUrlSource, JSSource {
|
||||
|
||||
override var priority: Boolean = false;
|
||||
|
||||
constructor(config: IV8PluginConfig, obj: V8ValueObject): super(TYPE_VIDEOURL, config, obj) {
|
||||
constructor(plugin: JSClient, obj: V8ValueObject): super(TYPE_VIDEOURL, plugin, obj) {
|
||||
val contextName = "JSVideoUrlSource";
|
||||
val config = plugin.config;
|
||||
|
||||
width = _obj.getOrThrow(config, "width", contextName);
|
||||
height = _obj.getOrThrow(config, "height", contextName);
|
||||
|
||||
+4
-1
@@ -3,7 +3,9 @@ package com.futo.platformplayer.api.media.platforms.js.models.sources
|
||||
import com.caoccao.javet.values.reference.V8ValueObject
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.other.IStreamMetaDataSource
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.other.StreamMetaData
|
||||
import com.futo.platformplayer.api.media.platforms.js.JSClient
|
||||
import com.futo.platformplayer.engine.IV8PluginConfig
|
||||
import com.futo.platformplayer.engine.V8Plugin
|
||||
import com.futo.platformplayer.getOrDefault
|
||||
|
||||
class JSVideoUrlRangeSource : JSVideoUrlSource, IStreamMetaDataSource {
|
||||
@@ -21,8 +23,9 @@ class JSVideoUrlRangeSource : JSVideoUrlSource, IStreamMetaDataSource {
|
||||
&& indexEnd != null)
|
||||
StreamMetaData(initStart, initEnd, indexStart, indexEnd) else null;
|
||||
|
||||
constructor(config: IV8PluginConfig, obj: V8ValueObject) : super(config, obj) {
|
||||
constructor(plugin: JSClient, obj: V8ValueObject) : super(plugin, obj) {
|
||||
val contextName = "JSVideoUrlRangeSource";
|
||||
val config = plugin.config;
|
||||
|
||||
itagId = _obj.getOrDefault(config, "itagId", contextName, null);
|
||||
initStart = _obj.getOrDefault(config, "initStart", contextName, null);
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
+3
-3
@@ -41,7 +41,7 @@ class SingleAsyncItemPager<T> {
|
||||
fun getCurrentItem(scope: CoroutineScope) : Deferred<T?>? {
|
||||
synchronized(_requestedPageItems) {
|
||||
if (_currentResultPos >= _requestedPageItems.size) {
|
||||
val startPos = fillDeferredUntil(_currentResultPos);
|
||||
fillDeferredUntil(_currentResultPos);
|
||||
if(!_pager.hasMorePages()) {
|
||||
Logger.i("SingleAsyncItemPager", "end of async page reached");
|
||||
completeRemainder { it?.complete(null) };
|
||||
@@ -49,7 +49,7 @@ class SingleAsyncItemPager<T> {
|
||||
if(_isRequesting)
|
||||
return _requestedPageItems[_currentResultPos];
|
||||
_isRequesting = true;
|
||||
StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
Logger.i("SingleAsyncItemPager", "Started Pager");
|
||||
val timeForPage = measureTimeMillis { _pager.nextPage() };
|
||||
@@ -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;
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
package com.futo.platformplayer.casting
|
||||
|
||||
import android.os.Looper
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
import com.futo.platformplayer.api.http.ManagedHttpClient
|
||||
import com.futo.platformplayer.getConnectedSocket
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
import com.futo.platformplayer.models.CastingDeviceInfo
|
||||
import com.futo.platformplayer.toInetAddress
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import java.net.InetAddress
|
||||
import java.util.UUID
|
||||
|
||||
@@ -18,7 +23,7 @@ class AirPlayCastingDevice : CastingDevice {
|
||||
override var usedRemoteAddress: InetAddress? = null;
|
||||
override var localAddress: InetAddress? = null;
|
||||
override val canSetVolume: Boolean get() = false;
|
||||
override val canSetSpeed: Boolean get() = false; //TODO: Implement playback speed for AirPlay
|
||||
override val canSetSpeed: Boolean get() = true;
|
||||
|
||||
var addresses: Array<InetAddress>? = null;
|
||||
var port: Int = 0;
|
||||
@@ -51,7 +56,8 @@ class AirPlayCastingDevice : CastingDevice {
|
||||
|
||||
Logger.i(FCastCastingDevice.TAG, "Start streaming (streamType: $streamType, contentType: $contentType, contentId: $contentId, resumePosition: $resumePosition, duration: $duration, speed: $speed)");
|
||||
|
||||
time = resumePosition;
|
||||
setTime(resumePosition);
|
||||
setDuration(duration);
|
||||
if (resumePosition > 0.0) {
|
||||
val pos = resumePosition / duration;
|
||||
Logger.i(TAG, "resumePosition: $resumePosition, duration: ${duration}, pos: $pos")
|
||||
@@ -59,6 +65,10 @@ class AirPlayCastingDevice : CastingDevice {
|
||||
} else {
|
||||
post("play", "text/parameters", "Content-Location: $contentId\r\nStart-Position: 0");
|
||||
}
|
||||
|
||||
if (speed != null) {
|
||||
changeSpeed(speed)
|
||||
}
|
||||
}
|
||||
|
||||
override fun loadContent(contentType: String, content: String, resumePosition: Double, duration: Double, speed: Double?) {
|
||||
@@ -161,8 +171,16 @@ class AirPlayCastingDevice : CastingDevice {
|
||||
}
|
||||
|
||||
val progress = progressInfo.substring(progressIndex + "position: ".length).toDoubleOrNull() ?: continue;
|
||||
setTime(progress);
|
||||
|
||||
time = progress;
|
||||
|
||||
val durationIndex = progressInfo.lowercase().indexOf("duration: ");
|
||||
if (durationIndex == -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
val duration = progressInfo.substring(durationIndex + "duration: ".length).toDoubleOrNull() ?: continue;
|
||||
setDuration(duration);
|
||||
} catch (e: Throwable) {
|
||||
Logger.w(TAG, "Failed to get server info from AirPlay device.", e)
|
||||
}
|
||||
@@ -186,6 +204,11 @@ class AirPlayCastingDevice : CastingDevice {
|
||||
_scopeIO = null;
|
||||
}
|
||||
|
||||
override fun changeSpeed(speed: Double) {
|
||||
setSpeed(speed)
|
||||
post("rate?value=$speed")
|
||||
}
|
||||
|
||||
override fun getDeviceInfo(): CastingDeviceInfo {
|
||||
return CastingDeviceInfo(name!!, CastProtocolType.AIRPLAY, addresses!!.filter { a -> a.hostAddress != null }.map { a -> a.hostAddress!! }.toTypedArray(), port);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.futo.platformplayer.casting
|
||||
|
||||
import com.futo.platformplayer.constructs.Event1
|
||||
import com.futo.platformplayer.getNowDiffMiliseconds
|
||||
import com.futo.platformplayer.models.CastingDeviceInfo
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
@@ -11,7 +10,6 @@ import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import java.net.InetAddress
|
||||
import java.time.OffsetDateTime
|
||||
|
||||
enum class CastConnectionState {
|
||||
DISCONNECTED,
|
||||
@@ -59,36 +57,58 @@ abstract class CastingDevice {
|
||||
onPlayChanged.emit(value);
|
||||
}
|
||||
};
|
||||
var timeReceivedAt: OffsetDateTime = OffsetDateTime.now()
|
||||
private set;
|
||||
|
||||
private var lastTimeChangeTime_ms: Long = 0
|
||||
var time: Double = 0.0
|
||||
set(value) {
|
||||
val changed = value != field;
|
||||
field = value;
|
||||
if (changed) {
|
||||
timeReceivedAt = OffsetDateTime.now();
|
||||
onTimeChanged.emit(value);
|
||||
}
|
||||
};
|
||||
private set
|
||||
|
||||
protected fun setTime(value: Double, changeTime_ms: Long = System.currentTimeMillis()) {
|
||||
if (changeTime_ms > lastTimeChangeTime_ms && value != time) {
|
||||
time = value
|
||||
lastTimeChangeTime_ms = changeTime_ms
|
||||
onTimeChanged.emit(value)
|
||||
}
|
||||
}
|
||||
|
||||
private var lastDurationChangeTime_ms: Long = 0
|
||||
var duration: Double = 0.0
|
||||
private set
|
||||
|
||||
protected fun setDuration(value: Double, changeTime_ms: Long = System.currentTimeMillis()) {
|
||||
if (changeTime_ms > lastDurationChangeTime_ms && value != duration) {
|
||||
duration = value
|
||||
lastDurationChangeTime_ms = changeTime_ms
|
||||
onDurationChanged.emit(value)
|
||||
}
|
||||
}
|
||||
|
||||
private var lastVolumeChangeTime_ms: Long = 0
|
||||
var volume: Double = 1.0
|
||||
set(value) {
|
||||
val changed = value != field;
|
||||
field = value;
|
||||
if (changed) {
|
||||
onVolumeChanged.emit(value);
|
||||
}
|
||||
};
|
||||
private set
|
||||
|
||||
protected fun setVolume(value: Double, changeTime_ms: Long = System.currentTimeMillis()) {
|
||||
if (changeTime_ms > lastVolumeChangeTime_ms && value != volume) {
|
||||
volume = value
|
||||
lastVolumeChangeTime_ms = changeTime_ms
|
||||
onVolumeChanged.emit(value)
|
||||
}
|
||||
}
|
||||
|
||||
private var lastSpeedChangeTime_ms: Long = 0
|
||||
var speed: Double = 1.0
|
||||
set(value) {
|
||||
val changed = value != field;
|
||||
speed = value;
|
||||
if (changed) {
|
||||
onSpeedChanged.emit(value);
|
||||
}
|
||||
};
|
||||
private set
|
||||
|
||||
protected fun setSpeed(value: Double, changeTime_ms: Long = System.currentTimeMillis()) {
|
||||
if (changeTime_ms > lastSpeedChangeTime_ms && value != speed) {
|
||||
speed = value
|
||||
lastSpeedChangeTime_ms = changeTime_ms
|
||||
onSpeedChanged.emit(value)
|
||||
}
|
||||
}
|
||||
|
||||
val expectedCurrentTime: Double
|
||||
get() {
|
||||
val diff = timeReceivedAt.getNowDiffMiliseconds().toDouble() / 1000.0;
|
||||
val diff = (System.currentTimeMillis() - lastTimeChangeTime_ms).toDouble() / 1000.0;
|
||||
return time + diff;
|
||||
};
|
||||
var connectionState: CastConnectionState = CastConnectionState.DISCONNECTED
|
||||
@@ -104,6 +124,7 @@ abstract class CastingDevice {
|
||||
var onConnectionStateChanged = Event1<CastConnectionState>();
|
||||
var onPlayChanged = Event1<Boolean>();
|
||||
var onTimeChanged = Event1<Double>();
|
||||
var onDurationChanged = Event1<Double>();
|
||||
var onVolumeChanged = Event1<Double>();
|
||||
var onSpeedChanged = Event1<Double>();
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -70,7 +74,8 @@ class ChromecastCastingDevice : CastingDevice {
|
||||
|
||||
Logger.i(TAG, "Start streaming (streamType: $streamType, contentType: $contentType, contentId: $contentId, resumePosition: $resumePosition, duration: $duration, speed: $speed)");
|
||||
|
||||
time = resumePosition;
|
||||
setTime(resumePosition);
|
||||
setDuration(duration);
|
||||
_streamType = streamType;
|
||||
_contentType = contentType;
|
||||
_contentId = contentId;
|
||||
@@ -132,7 +137,7 @@ class ChromecastCastingDevice : CastingDevice {
|
||||
return;
|
||||
}
|
||||
|
||||
this.volume = volume
|
||||
setVolume(volume)
|
||||
val setVolumeObject = JSONObject();
|
||||
setVolumeObject.put("type", "SET_VOLUME");
|
||||
|
||||
@@ -486,7 +491,7 @@ class ChromecastCastingDevice : CastingDevice {
|
||||
if (!sessionIsRunning) {
|
||||
_sessionId = null;
|
||||
_mediaSessionId = null;
|
||||
time = 0.0;
|
||||
setTime(0.0);
|
||||
_transportId = null;
|
||||
Logger.w(TAG, "Session not found.");
|
||||
|
||||
@@ -502,11 +507,11 @@ 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();
|
||||
this.volume = if (volumeMuted) 0.0 else volumeLevel;
|
||||
//val volumeStepInterval = volume.getString("stepInterval").toFloat();
|
||||
setVolume(if (volumeMuted) 0.0 else volumeLevel);
|
||||
|
||||
Logger.i(TAG, "Status update received volume (level: $volumeLevel, muted: $volumeMuted)");
|
||||
} else if (type == "MEDIA_STATUS") {
|
||||
@@ -517,10 +522,16 @@ class ChromecastCastingDevice : CastingDevice {
|
||||
|
||||
val playerState = status.getString("playerState");
|
||||
val currentTime = status.getDouble("currentTime");
|
||||
if (status.has("media")) {
|
||||
val media = status.getJSONObject("media")
|
||||
if (media.has("duration")) {
|
||||
setDuration(media.getDouble("duration"))
|
||||
}
|
||||
}
|
||||
|
||||
isPlaying = playerState == "PLAYING";
|
||||
if (isPlaying) {
|
||||
time = currentTime;
|
||||
setTime(currentTime);
|
||||
}
|
||||
|
||||
val playbackRate = status.getInt("playbackRate");
|
||||
|
||||
@@ -3,14 +3,24 @@ package com.futo.platformplayer.casting
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import com.futo.platformplayer.UIDialogs
|
||||
import com.futo.platformplayer.casting.models.*
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
import com.futo.platformplayer.casting.models.FCastPlayMessage
|
||||
import com.futo.platformplayer.casting.models.FCastPlaybackErrorMessage
|
||||
import com.futo.platformplayer.casting.models.FCastPlaybackUpdateMessage
|
||||
import com.futo.platformplayer.casting.models.FCastSeekMessage
|
||||
import com.futo.platformplayer.casting.models.FCastSetSpeedMessage
|
||||
import com.futo.platformplayer.casting.models.FCastSetVolumeMessage
|
||||
import com.futo.platformplayer.casting.models.FCastVersionMessage
|
||||
import com.futo.platformplayer.casting.models.FCastVolumeUpdateMessage
|
||||
import com.futo.platformplayer.getConnectedSocket
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
import com.futo.platformplayer.models.CastingDeviceInfo
|
||||
import com.futo.platformplayer.toHexString
|
||||
import com.futo.platformplayer.toInetAddress
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.io.DataInputStream
|
||||
@@ -82,13 +92,16 @@ class FCastCastingDevice : CastingDevice {
|
||||
|
||||
Logger.i(TAG, "Start streaming (streamType: $streamType, contentType: $contentType, contentId: $contentId, resumePosition: $resumePosition, duration: $duration, speed: $speed)");
|
||||
|
||||
time = resumePosition;
|
||||
setTime(resumePosition);
|
||||
setDuration(duration);
|
||||
sendMessage(Opcode.PLAY, FCastPlayMessage(
|
||||
container = contentType,
|
||||
url = contentId,
|
||||
time = resumePosition,
|
||||
speed = speed
|
||||
));
|
||||
|
||||
setSpeed(speed ?: 1.0);
|
||||
}
|
||||
|
||||
override fun loadContent(contentType: String, content: String, resumePosition: Double, duration: Double, speed: Double?) {
|
||||
@@ -103,13 +116,16 @@ class FCastCastingDevice : CastingDevice {
|
||||
|
||||
Logger.i(TAG, "Start streaming content (contentType: $contentType, resumePosition: $resumePosition, duration: $duration, speed: $speed)");
|
||||
|
||||
time = resumePosition;
|
||||
setTime(resumePosition);
|
||||
setDuration(duration);
|
||||
sendMessage(Opcode.PLAY, FCastPlayMessage(
|
||||
container = contentType,
|
||||
content = content,
|
||||
time = resumePosition,
|
||||
speed = speed
|
||||
));
|
||||
|
||||
setSpeed(speed ?: 1.0);
|
||||
}
|
||||
|
||||
override fun changeVolume(volume: Double) {
|
||||
@@ -117,17 +133,17 @@ class FCastCastingDevice : CastingDevice {
|
||||
return;
|
||||
}
|
||||
|
||||
this.volume = volume
|
||||
setVolume(volume);
|
||||
sendMessage(Opcode.SET_VOLUME, FCastSetVolumeMessage(volume))
|
||||
}
|
||||
|
||||
override fun changeSpeed(speed: Double) {
|
||||
if (invokeInIOScopeIfRequired({ changeSpeed(volume) })) {
|
||||
if (invokeInIOScopeIfRequired({ changeSpeed(speed) })) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.speed = speed
|
||||
sendMessage(Opcode.SET_SPEED, FCastSetSpeedMessage(volume))
|
||||
setSpeed(speed);
|
||||
sendMessage(Opcode.SET_SPEED, FCastSetSpeedMessage(speed))
|
||||
}
|
||||
|
||||
override fun seekVideo(timeSeconds: Double) {
|
||||
@@ -247,7 +263,8 @@ class FCastCastingDevice : CastingDevice {
|
||||
val buffer = ByteArray(4096);
|
||||
|
||||
Logger.i(TAG, "Started receiving.");
|
||||
while (_scopeIO?.isActive == true) {
|
||||
var exceptionOccurred = false;
|
||||
while (_scopeIO?.isActive == true && !exceptionOccurred) {
|
||||
try {
|
||||
val inputStream = _inputStream ?: break;
|
||||
Log.d(TAG, "Receiving next packet...");
|
||||
@@ -275,20 +292,25 @@ class FCastCastingDevice : CastingDevice {
|
||||
}
|
||||
|
||||
try {
|
||||
handleMessage(Opcode.values().first { it.value == opcode }, json);
|
||||
handleMessage(Opcode.entries.first { it.value == opcode }, json);
|
||||
} catch (e:Throwable) {
|
||||
Logger.w(TAG, "Failed to handle message.", e);
|
||||
}
|
||||
} catch (e: java.net.SocketException) {
|
||||
Logger.e(TAG, "Socket exception while receiving.", e);
|
||||
break;
|
||||
exceptionOccurred = true;
|
||||
} catch (e: Throwable) {
|
||||
Logger.e(TAG, "Exception while receiving.", e);
|
||||
break;
|
||||
exceptionOccurred = true;
|
||||
}
|
||||
}
|
||||
_socket?.close();
|
||||
Logger.i(TAG, "Socket disconnected.");
|
||||
|
||||
try {
|
||||
_socket?.close();
|
||||
Logger.i(TAG, "Socket disconnected.");
|
||||
} catch (e: Throwable) {
|
||||
Logger.e(TAG, "Failed to close socket.", e)
|
||||
}
|
||||
|
||||
connectionState = CastConnectionState.CONNECTING;
|
||||
Thread.sleep(3000);
|
||||
@@ -310,7 +332,8 @@ class FCastCastingDevice : CastingDevice {
|
||||
}
|
||||
|
||||
val playbackUpdate = FCastCastingDevice.json.decodeFromString<FCastPlaybackUpdateMessage>(json);
|
||||
time = playbackUpdate.time;
|
||||
setTime(playbackUpdate.time, playbackUpdate.generationTime);
|
||||
setDuration(playbackUpdate.duration, playbackUpdate.generationTime);
|
||||
isPlaying = when (playbackUpdate.state) {
|
||||
1 -> true
|
||||
else -> false
|
||||
@@ -323,7 +346,7 @@ class FCastCastingDevice : CastingDevice {
|
||||
}
|
||||
|
||||
val volumeUpdate = FCastCastingDevice.json.decodeFromString<FCastVolumeUpdateMessage>(json);
|
||||
volume = volumeUpdate.volume;
|
||||
setVolume(volumeUpdate.volume, volumeUpdate.generationTime);
|
||||
}
|
||||
Opcode.PLAYBACK_ERROR -> {
|
||||
if (json == null) {
|
||||
|
||||
@@ -5,12 +5,24 @@ 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 android.util.Log
|
||||
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 +33,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 {
|
||||
@@ -52,8 +66,11 @@ class StateCasting {
|
||||
val onActiveDeviceConnectionStateChanged = Event2<CastingDevice, CastConnectionState>();
|
||||
val onActiveDevicePlayChanged = Event1<Boolean>();
|
||||
val onActiveDeviceTimeChanged = Event1<Double>();
|
||||
val onActiveDeviceDurationChanged = Event1<Double>();
|
||||
val onActiveDeviceVolumeChanged = Event1<Double>();
|
||||
var activeDevice: CastingDevice? = null;
|
||||
private val _client = ManagedHttpClient();
|
||||
var _resumeCastingDevice: CastingDeviceInfo? = null;
|
||||
|
||||
val isCasting: Boolean get() = activeDevice != null;
|
||||
|
||||
@@ -169,28 +186,42 @@ class StateCasting {
|
||||
val networkConfig = Json.decodeFromString<FCastNetworkConfig>(json)
|
||||
val tcpService = networkConfig.services.first { v -> v.type == 0 }
|
||||
|
||||
addRememberedDevice(CastingDeviceInfo(
|
||||
val foundInfo = addRememberedDevice(CastingDeviceInfo(
|
||||
name = networkConfig.name,
|
||||
type = CastProtocolType.FCAST,
|
||||
addresses = networkConfig.addresses.toTypedArray(),
|
||||
port = tcpService.port
|
||||
))
|
||||
|
||||
UIDialogs.toast(context,"FCast device '${networkConfig.name}' added")
|
||||
connectDevice(deviceFromCastingDeviceInfo(foundInfo))
|
||||
}
|
||||
|
||||
fun onStop() {
|
||||
val ad = activeDevice ?: return;
|
||||
_resumeCastingDevice = ad.getDeviceInfo()
|
||||
Log.i(TAG, "_resumeCastingDevice set to '${ad.name}'")
|
||||
Logger.i(TAG, "Stopping active device because of onStop.");
|
||||
ad.stop();
|
||||
}
|
||||
|
||||
fun onResume() {
|
||||
val resumeCastingDevice = _resumeCastingDevice
|
||||
if (resumeCastingDevice != null) {
|
||||
connectDevice(deviceFromCastingDeviceInfo(resumeCastingDevice))
|
||||
_resumeCastingDevice = null
|
||||
Log.i(TAG, "_resumeCastingDevice set to null onResume")
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun start(context: Context) {
|
||||
if (_started)
|
||||
return;
|
||||
_started = true;
|
||||
|
||||
Log.i(TAG, "_resumeCastingDevice set null start")
|
||||
_resumeCastingDevice = null;
|
||||
|
||||
Logger.i(TAG, "CastingService starting...");
|
||||
|
||||
rememberedDevices.clear();
|
||||
@@ -213,7 +244,7 @@ class StateCasting {
|
||||
}
|
||||
}
|
||||
_castServer.start();
|
||||
enableDeveloper(context.contentResolver, true);
|
||||
enableDeveloper(true);
|
||||
|
||||
Logger.i(TAG, "CastingService started.");
|
||||
}
|
||||
@@ -233,6 +264,7 @@ class StateCasting {
|
||||
try {
|
||||
jmDNS.removeServiceListener("_googlecast._tcp.local.", _chromecastServiceListener);
|
||||
jmDNS.removeServiceListener("_airplay._tcp", _airPlayServiceListener);
|
||||
jmDNS.removeServiceListener("_fastcast._tcp.local.", _fastCastServiceListener);
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
jmDNS.removeServiceTypeListener(_serviceTypeListener);
|
||||
@@ -267,9 +299,11 @@ class StateCasting {
|
||||
val ad = activeDevice;
|
||||
if (ad != null) {
|
||||
Logger.i(TAG, "Stopping previous device because a new one is being connected.")
|
||||
ad.onPlayChanged.clear();
|
||||
ad.onTimeChanged.clear();
|
||||
ad.onConnectionStateChanged.clear();
|
||||
device.onConnectionStateChanged.clear();
|
||||
device.onPlayChanged.clear();
|
||||
device.onTimeChanged.clear();
|
||||
device.onVolumeChanged.clear();
|
||||
device.onDurationChanged.clear();
|
||||
ad.stop();
|
||||
}
|
||||
|
||||
@@ -279,9 +313,11 @@ class StateCasting {
|
||||
if (castConnectionState == CastConnectionState.DISCONNECTED) {
|
||||
Logger.i(TAG, "Clearing events: $castConnectionState");
|
||||
|
||||
device.onConnectionStateChanged.clear();
|
||||
device.onPlayChanged.clear();
|
||||
device.onTimeChanged.clear();
|
||||
device.onConnectionStateChanged.clear();
|
||||
device.onVolumeChanged.clear();
|
||||
device.onDurationChanged.clear();
|
||||
activeDevice = null;
|
||||
}
|
||||
|
||||
@@ -301,6 +337,12 @@ class StateCasting {
|
||||
device.onPlayChanged.subscribe {
|
||||
invokeInMainScopeIfRequired { onActiveDevicePlayChanged.emit(it) };
|
||||
}
|
||||
device.onDurationChanged.subscribe {
|
||||
invokeInMainScopeIfRequired { onActiveDeviceDurationChanged.emit(it) };
|
||||
};
|
||||
device.onVolumeChanged.subscribe {
|
||||
invokeInMainScopeIfRequired { onActiveDeviceVolumeChanged.emit(it) };
|
||||
};
|
||||
device.onTimeChanged.subscribe {
|
||||
invokeInMainScopeIfRequired { onActiveDeviceTimeChanged.emit(it) };
|
||||
};
|
||||
@@ -315,6 +357,8 @@ class StateCasting {
|
||||
device.onConnectionStateChanged.clear();
|
||||
device.onPlayChanged.clear();
|
||||
device.onTimeChanged.clear();
|
||||
device.onVolumeChanged.clear();
|
||||
device.onDurationChanged.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -322,15 +366,20 @@ class StateCasting {
|
||||
Logger.i(TAG, "Connect to device ${device.name}");
|
||||
}
|
||||
|
||||
fun addRememberedDevice(deviceInfo: CastingDeviceInfo) {
|
||||
fun addRememberedDevice(deviceInfo: CastingDeviceInfo): CastingDeviceInfo {
|
||||
val device = deviceFromCastingDeviceInfo(deviceInfo);
|
||||
addRememberedDevice(device);
|
||||
return addRememberedDevice(device);
|
||||
}
|
||||
|
||||
fun addRememberedDevice(device: CastingDevice) {
|
||||
if (_storage.addDevice(device.getDeviceInfo())) {
|
||||
fun addRememberedDevice(device: CastingDevice): CastingDeviceInfo {
|
||||
val deviceInfo = device.getDeviceInfo()
|
||||
val foundInfo = _storage.addDevice(deviceInfo)
|
||||
if (foundInfo == deviceInfo) {
|
||||
rememberedDevices.add(device);
|
||||
return foundInfo;
|
||||
}
|
||||
|
||||
return foundInfo;
|
||||
}
|
||||
|
||||
fun removeRememberedDevice(device: CastingDevice) {
|
||||
@@ -348,7 +397,7 @@ class StateCasting {
|
||||
action();
|
||||
}
|
||||
|
||||
fun castIfAvailable(contentResolver: ContentResolver, video: IPlatformVideoDetails, videoSource: IVideoSource?, audioSource: IAudioSource?, subtitleSource: ISubtitleSource?, ms: Long = -1): Boolean {
|
||||
fun castIfAvailable(contentResolver: ContentResolver, video: IPlatformVideoDetails, videoSource: IVideoSource?, audioSource: IAudioSource?, subtitleSource: ISubtitleSource?, ms: Long = -1, speed: Double?): Boolean {
|
||||
val ad = activeDevice ?: return false;
|
||||
if (ad.connectionState != CastConnectionState.CONNECTED) {
|
||||
return false;
|
||||
@@ -369,23 +418,23 @@ class StateCasting {
|
||||
if (videoSource is LocalVideoSource || audioSource is LocalAudioSource || subtitleSource is LocalSubtitleSource) {
|
||||
if (ad is AirPlayCastingDevice) {
|
||||
Logger.i(TAG, "Casting as local HLS");
|
||||
castLocalHls(video, videoSource as LocalVideoSource?, audioSource as LocalAudioSource?, subtitleSource as LocalSubtitleSource?, resumePosition);
|
||||
castLocalHls(video, videoSource as LocalVideoSource?, audioSource as LocalAudioSource?, subtitleSource as LocalSubtitleSource?, resumePosition, speed);
|
||||
} else {
|
||||
Logger.i(TAG, "Casting as local DASH");
|
||||
castLocalDash(video, videoSource as LocalVideoSource?, audioSource as LocalAudioSource?, subtitleSource as LocalSubtitleSource?, resumePosition);
|
||||
castLocalDash(video, videoSource as LocalVideoSource?, audioSource as LocalAudioSource?, subtitleSource as LocalSubtitleSource?, resumePosition, speed);
|
||||
}
|
||||
} else {
|
||||
StateApp.instance.scope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
if (ad is FCastCastingDevice) {
|
||||
Logger.i(TAG, "Casting as DASH direct");
|
||||
castDashDirect(contentResolver, video, videoSource as IVideoUrlSource?, audioSource as IAudioUrlSource?, subtitleSource, resumePosition);
|
||||
castDashDirect(contentResolver, video, videoSource as IVideoUrlSource?, audioSource as IAudioUrlSource?, subtitleSource, resumePosition, speed);
|
||||
} else if (ad is AirPlayCastingDevice) {
|
||||
Logger.i(TAG, "Casting as HLS indirect");
|
||||
castHlsIndirect(contentResolver, video, videoSource as IVideoUrlSource?, audioSource as IAudioUrlSource?, subtitleSource, resumePosition);
|
||||
castHlsIndirect(contentResolver, video, videoSource as IVideoUrlSource?, audioSource as IAudioUrlSource?, subtitleSource, resumePosition, speed);
|
||||
} else {
|
||||
Logger.i(TAG, "Casting as DASH indirect");
|
||||
castDashIndirect(contentResolver, video, videoSource as IVideoUrlSource?, audioSource as IAudioUrlSource?, subtitleSource, resumePosition);
|
||||
castDashIndirect(contentResolver, video, videoSource as IVideoUrlSource?, audioSource as IAudioUrlSource?, subtitleSource, resumePosition, speed);
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
Logger.e(TAG, "Failed to start casting DASH videoSource=${videoSource} audioSource=${audioSource}.", e);
|
||||
@@ -395,32 +444,32 @@ class StateCasting {
|
||||
} else {
|
||||
if (videoSource is IVideoUrlSource) {
|
||||
Logger.i(TAG, "Casting as singular video");
|
||||
ad.loadVideo(if (video.isLive) "LIVE" else "BUFFERED", videoSource.container, videoSource.getVideoUrl(), resumePosition, video.duration.toDouble(), null);
|
||||
ad.loadVideo(if (video.isLive) "LIVE" else "BUFFERED", videoSource.container, videoSource.getVideoUrl(), resumePosition, video.duration.toDouble(), speed);
|
||||
} else if (audioSource is IAudioUrlSource) {
|
||||
Logger.i(TAG, "Casting as singular audio");
|
||||
ad.loadVideo(if (video.isLive) "LIVE" else "BUFFERED", audioSource.container, audioSource.getAudioUrl(), resumePosition, video.duration.toDouble(), null);
|
||||
ad.loadVideo(if (video.isLive) "LIVE" else "BUFFERED", audioSource.container, audioSource.getAudioUrl(), resumePosition, video.duration.toDouble(), speed);
|
||||
} else if(videoSource is IHLSManifestSource) {
|
||||
if (ad is ChromecastCastingDevice) {
|
||||
Logger.i(TAG, "Casting as proxied HLS");
|
||||
castProxiedHls(video, videoSource.url, videoSource.codec, resumePosition);
|
||||
castProxiedHls(video, videoSource.url, videoSource.codec, resumePosition, speed);
|
||||
} else {
|
||||
Logger.i(TAG, "Casting as non-proxied HLS");
|
||||
ad.loadVideo(if (video.isLive) "LIVE" else "BUFFERED", videoSource.container, videoSource.url, resumePosition, video.duration.toDouble(), null);
|
||||
ad.loadVideo(if (video.isLive) "LIVE" else "BUFFERED", videoSource.container, videoSource.url, resumePosition, video.duration.toDouble(), speed);
|
||||
}
|
||||
} else if(audioSource is IHLSManifestAudioSource) {
|
||||
if (ad is ChromecastCastingDevice) {
|
||||
Logger.i(TAG, "Casting as proxied audio HLS");
|
||||
castProxiedHls(video, audioSource.url, audioSource.codec, resumePosition);
|
||||
castProxiedHls(video, audioSource.url, audioSource.codec, resumePosition, speed);
|
||||
} else {
|
||||
Logger.i(TAG, "Casting as non-proxied audio HLS");
|
||||
ad.loadVideo(if (video.isLive) "LIVE" else "BUFFERED", audioSource.container, audioSource.url, resumePosition, video.duration.toDouble(), null);
|
||||
ad.loadVideo(if (video.isLive) "LIVE" else "BUFFERED", audioSource.container, audioSource.url, resumePosition, video.duration.toDouble(), speed);
|
||||
}
|
||||
} else if (videoSource is LocalVideoSource) {
|
||||
Logger.i(TAG, "Casting as local video");
|
||||
castLocalVideo(video, videoSource, resumePosition);
|
||||
castLocalVideo(video, videoSource, resumePosition, speed);
|
||||
} else if (audioSource is LocalAudioSource) {
|
||||
Logger.i(TAG, "Casting as local audio");
|
||||
castLocalAudio(video, audioSource, resumePosition);
|
||||
castLocalAudio(video, audioSource, resumePosition, speed);
|
||||
} else {
|
||||
var str = listOf(
|
||||
if(videoSource != null) "Video: ${videoSource::class.java.simpleName}" else null,
|
||||
@@ -458,15 +507,7 @@ class StateCasting {
|
||||
return true;
|
||||
}
|
||||
|
||||
private fun castVideoIndirect() {
|
||||
|
||||
}
|
||||
|
||||
private fun castAudioIndirect() {
|
||||
|
||||
}
|
||||
|
||||
private fun castLocalVideo(video: IPlatformVideoDetails, videoSource: LocalVideoSource, resumePosition: Double) : List<String> {
|
||||
private fun castLocalVideo(video: IPlatformVideoDetails, videoSource: LocalVideoSource, resumePosition: Double, speed: Double?) : List<String> {
|
||||
val ad = activeDevice ?: return listOf();
|
||||
|
||||
val url = "http://${ad.localAddress.toString().trim('/')}:${_castServer.port}";
|
||||
@@ -480,12 +521,12 @@ class StateCasting {
|
||||
).withTag("cast");
|
||||
|
||||
Logger.i(TAG, "Casting local video (videoUrl: $videoUrl).");
|
||||
ad.loadVideo("BUFFERED", videoSource.container, videoUrl, resumePosition, video.duration.toDouble(), null);
|
||||
ad.loadVideo("BUFFERED", videoSource.container, videoUrl, resumePosition, video.duration.toDouble(), speed);
|
||||
|
||||
return listOf(videoUrl);
|
||||
}
|
||||
|
||||
private fun castLocalAudio(video: IPlatformVideoDetails, audioSource: LocalAudioSource, resumePosition: Double) : List<String> {
|
||||
private fun castLocalAudio(video: IPlatformVideoDetails, audioSource: LocalAudioSource, resumePosition: Double, speed: Double?) : List<String> {
|
||||
val ad = activeDevice ?: return listOf();
|
||||
|
||||
val url = "http://${ad.localAddress.toString().trim('/')}:${_castServer.port}";
|
||||
@@ -499,12 +540,12 @@ class StateCasting {
|
||||
).withTag("cast");
|
||||
|
||||
Logger.i(TAG, "Casting local audio (audioUrl: $audioUrl).");
|
||||
ad.loadVideo("BUFFERED", audioSource.container, audioUrl, resumePosition, video.duration.toDouble(), null);
|
||||
ad.loadVideo("BUFFERED", audioSource.container, audioUrl, resumePosition, video.duration.toDouble(), speed);
|
||||
|
||||
return listOf(audioUrl);
|
||||
}
|
||||
|
||||
private fun castLocalHls(video: IPlatformVideoDetails, videoSource: LocalVideoSource?, audioSource: LocalAudioSource?, subtitleSource: LocalSubtitleSource?, resumePosition: Double): List<String> {
|
||||
private fun castLocalHls(video: IPlatformVideoDetails, videoSource: LocalVideoSource?, audioSource: LocalAudioSource?, subtitleSource: LocalSubtitleSource?, resumePosition: Double, speed: Double?): List<String> {
|
||||
val ad = activeDevice ?: return listOf()
|
||||
|
||||
val url = "http://${ad.localAddress.toString().trim('/')}:${_castServer.port}"
|
||||
@@ -595,12 +636,12 @@ class StateCasting {
|
||||
).withTag("castLocalHls")
|
||||
|
||||
Logger.i(TAG, "added new castLocalHls handlers (hlsPath: $hlsPath, videoPath: $videoPath, audioPath: $audioPath, subtitlePath: $subtitlePath).")
|
||||
ad.loadVideo("BUFFERED", "application/vnd.apple.mpegurl", hlsUrl, resumePosition, video.duration.toDouble(), null)
|
||||
ad.loadVideo("BUFFERED", "application/vnd.apple.mpegurl", hlsUrl, resumePosition, video.duration.toDouble(), speed)
|
||||
|
||||
return listOf(hlsUrl, videoUrl, audioUrl, subtitleUrl)
|
||||
}
|
||||
|
||||
private fun castLocalDash(video: IPlatformVideoDetails, videoSource: LocalVideoSource?, audioSource: LocalAudioSource?, subtitleSource: LocalSubtitleSource?, resumePosition: Double) : List<String> {
|
||||
private fun castLocalDash(video: IPlatformVideoDetails, videoSource: LocalVideoSource?, audioSource: LocalAudioSource?, subtitleSource: LocalSubtitleSource?, resumePosition: Double, speed: Double?) : List<String> {
|
||||
val ad = activeDevice ?: return listOf();
|
||||
|
||||
val url = "http://${ad.localAddress.toString().trim('/')}:${_castServer.port}";
|
||||
@@ -641,12 +682,12 @@ class StateCasting {
|
||||
}
|
||||
|
||||
Logger.i(TAG, "added new castLocalDash handlers (dashPath: $dashPath, videoPath: $videoPath, audioPath: $audioPath, subtitlePath: $subtitlePath).");
|
||||
ad.loadVideo("BUFFERED", "application/dash+xml", dashUrl, resumePosition, video.duration.toDouble(), null);
|
||||
ad.loadVideo("BUFFERED", "application/dash+xml", dashUrl, resumePosition, video.duration.toDouble(), speed);
|
||||
|
||||
return listOf(dashUrl, videoUrl, audioUrl, subtitleUrl);
|
||||
}
|
||||
|
||||
private suspend fun castDashDirect(contentResolver: ContentResolver, video: IPlatformVideoDetails, videoSource: IVideoUrlSource?, audioSource: IAudioUrlSource?, subtitleSource: ISubtitleSource?, resumePosition: Double) : List<String> {
|
||||
private suspend fun castDashDirect(contentResolver: ContentResolver, video: IPlatformVideoDetails, videoSource: IVideoUrlSource?, audioSource: IAudioUrlSource?, subtitleSource: ISubtitleSource?, resumePosition: Double, speed: Double?) : List<String> {
|
||||
val ad = activeDevice ?: return listOf();
|
||||
|
||||
val url = "http://${ad.localAddress.toString().trim('/')}:${_castServer.port}";
|
||||
@@ -686,12 +727,12 @@ class StateCasting {
|
||||
val content = DashBuilder.generateOnDemandDash(videoSource, videoUrl, audioSource, audioUrl, subtitleSource, subtitlesUrl);
|
||||
|
||||
Logger.i(TAG, "Direct dash cast to casting device (videoUrl: $videoUrl, audioUrl: $audioUrl).");
|
||||
ad.loadContent("application/dash+xml", content, resumePosition, video.duration.toDouble(), null);
|
||||
ad.loadContent("application/dash+xml", content, resumePosition, video.duration.toDouble(), speed);
|
||||
|
||||
return listOf(videoSource?.getVideoUrl() ?: "", audioSource?.getAudioUrl() ?: "");
|
||||
}
|
||||
|
||||
private fun castProxiedHls(video: IPlatformVideoDetails, sourceUrl: String, codec: String?, resumePosition: Double): List<String> {
|
||||
private fun castProxiedHls(video: IPlatformVideoDetails, sourceUrl: String, codec: String?, resumePosition: Double, speed: Double?): List<String> {
|
||||
_castServer.removeAllHandlers("castProxiedHlsMaster")
|
||||
|
||||
val ad = activeDevice ?: return listOf();
|
||||
@@ -812,7 +853,7 @@ class StateCasting {
|
||||
|
||||
//ChromeCast is sometimes funky with resume position 0
|
||||
val hackfixResumePosition = if (ad is ChromecastCastingDevice && !video.isLive && resumePosition == 0.0) 0.1 else resumePosition;
|
||||
ad.loadVideo(if (video.isLive) "LIVE" else "BUFFERED", "application/vnd.apple.mpegurl", hlsUrl, hackfixResumePosition, video.duration.toDouble(), null);
|
||||
ad.loadVideo(if (video.isLive) "LIVE" else "BUFFERED", "application/vnd.apple.mpegurl", hlsUrl, hackfixResumePosition, video.duration.toDouble(), speed);
|
||||
|
||||
return listOf(hlsUrl);
|
||||
}
|
||||
@@ -863,7 +904,7 @@ class StateCasting {
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun castHlsIndirect(contentResolver: ContentResolver, video: IPlatformVideoDetails, videoSource: IVideoUrlSource?, audioSource: IAudioUrlSource?, subtitleSource: ISubtitleSource?, resumePosition: Double) : List<String> {
|
||||
private suspend fun castHlsIndirect(contentResolver: ContentResolver, video: IPlatformVideoDetails, videoSource: IVideoUrlSource?, audioSource: IAudioUrlSource?, subtitleSource: ISubtitleSource?, resumePosition: Double, speed: Double?) : List<String> {
|
||||
val ad = activeDevice ?: return listOf();
|
||||
val url = "http://${ad.localAddress.toString().trim('/')}:${_castServer.port}";
|
||||
val id = UUID.randomUUID();
|
||||
@@ -986,12 +1027,12 @@ class StateCasting {
|
||||
).withTag("castHlsIndirectMaster")
|
||||
|
||||
Logger.i(TAG, "added new castHls handlers (hlsPath: $hlsPath).");
|
||||
ad.loadVideo(if (video.isLive) "LIVE" else "BUFFERED", "application/vnd.apple.mpegurl", hlsUrl, resumePosition, video.duration.toDouble(), null);
|
||||
ad.loadVideo(if (video.isLive) "LIVE" else "BUFFERED", "application/vnd.apple.mpegurl", hlsUrl, resumePosition, video.duration.toDouble(), speed);
|
||||
|
||||
return listOf(hlsUrl, videoSource?.getVideoUrl() ?: "", audioSource?.getAudioUrl() ?: "", subtitlesUri.toString());
|
||||
}
|
||||
|
||||
private suspend fun castDashIndirect(contentResolver: ContentResolver, video: IPlatformVideoDetails, videoSource: IVideoUrlSource?, audioSource: IAudioUrlSource?, subtitleSource: ISubtitleSource?, resumePosition: Double) : List<String> {
|
||||
private suspend fun castDashIndirect(contentResolver: ContentResolver, video: IPlatformVideoDetails, videoSource: IVideoUrlSource?, audioSource: IAudioUrlSource?, subtitleSource: ISubtitleSource?, resumePosition: Double, speed: Double?) : List<String> {
|
||||
val ad = activeDevice ?: return listOf();
|
||||
val proxyStreams = ad !is FCastCastingDevice;
|
||||
|
||||
@@ -1061,7 +1102,7 @@ class StateCasting {
|
||||
}
|
||||
|
||||
Logger.i(TAG, "added new castDash handlers (dashPath: $dashPath, videoPath: $videoPath, audioPath: $audioPath).");
|
||||
ad.loadVideo(if (video.isLive) "LIVE" else "BUFFERED", "application/dash+xml", dashUrl, resumePosition, video.duration.toDouble(), null);
|
||||
ad.loadVideo(if (video.isLive) "LIVE" else "BUFFERED", "application/dash+xml", dashUrl, resumePosition, video.duration.toDouble(), speed);
|
||||
|
||||
return listOf(dashUrl, videoUrl ?: "", audioUrl ?: "", subtitlesUrl ?: "", videoSource?.getVideoUrl() ?: "", audioSource?.getAudioUrl() ?: "", subtitlesUri.toString());
|
||||
}
|
||||
@@ -1077,7 +1118,6 @@ class StateCasting {
|
||||
CastProtocolType.FCAST -> {
|
||||
FCastCastingDevice(deviceInfo);
|
||||
}
|
||||
else -> throw Exception("${deviceInfo.type} is not a valid casting protocol")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1172,7 +1212,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 ->
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -7,12 +7,20 @@ import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.Button
|
||||
import android.widget.ImageButton
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
import com.futo.platformplayer.R
|
||||
import com.futo.platformplayer.casting.*
|
||||
import com.futo.platformplayer.activities.MainActivity
|
||||
import com.futo.platformplayer.casting.AirPlayCastingDevice
|
||||
import com.futo.platformplayer.casting.CastConnectionState
|
||||
import com.futo.platformplayer.casting.CastingDevice
|
||||
import com.futo.platformplayer.casting.ChromecastCastingDevice
|
||||
import com.futo.platformplayer.casting.FCastCastingDevice
|
||||
import com.futo.platformplayer.casting.StateCasting
|
||||
import com.futo.platformplayer.fragment.mainactivity.main.VideoDetailFragment
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
import com.futo.platformplayer.states.StateApp
|
||||
import com.google.android.material.slider.Slider
|
||||
import com.google.android.material.slider.Slider.OnChangeListener
|
||||
@@ -27,8 +35,16 @@ class ConnectedCastingDialog(context: Context?) : AlertDialog(context) {
|
||||
private lateinit var _textType: TextView;
|
||||
private lateinit var _buttonDisconnect: LinearLayout;
|
||||
private lateinit var _sliderVolume: Slider;
|
||||
private lateinit var _sliderPosition: Slider;
|
||||
private lateinit var _layoutVolumeAdjustable: LinearLayout;
|
||||
private lateinit var _layoutVolumeFixed: LinearLayout;
|
||||
|
||||
private lateinit var _buttonPrevious: ImageButton;
|
||||
private lateinit var _buttonPlay: ImageButton;
|
||||
private lateinit var _buttonPause: ImageButton;
|
||||
private lateinit var _buttonStop: ImageButton;
|
||||
private lateinit var _buttonNext: ImageButton;
|
||||
|
||||
private var _device: CastingDevice? = null;
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@@ -42,17 +58,61 @@ class ConnectedCastingDialog(context: Context?) : AlertDialog(context) {
|
||||
_textType = findViewById(R.id.text_type);
|
||||
_buttonDisconnect = findViewById(R.id.button_disconnect);
|
||||
_sliderVolume = findViewById(R.id.slider_volume);
|
||||
_sliderPosition = findViewById(R.id.slider_position);
|
||||
_layoutVolumeAdjustable = findViewById(R.id.layout_volume_adjustable);
|
||||
_layoutVolumeFixed = findViewById(R.id.layout_volume_fixed);
|
||||
|
||||
_buttonPrevious = findViewById(R.id.button_previous);
|
||||
_buttonPrevious.setOnClickListener {
|
||||
(ownerActivity as MainActivity?)?.getFragment<VideoDetailFragment>()?.previousVideo()
|
||||
}
|
||||
|
||||
_buttonPlay = findViewById(R.id.button_play);
|
||||
_buttonPlay.setOnClickListener {
|
||||
StateCasting.instance.activeDevice?.resumeVideo()
|
||||
}
|
||||
|
||||
_buttonPause = findViewById(R.id.button_pause);
|
||||
_buttonPause.setOnClickListener {
|
||||
StateCasting.instance.activeDevice?.pauseVideo()
|
||||
}
|
||||
|
||||
_buttonStop = findViewById(R.id.button_stop);
|
||||
_buttonStop.setOnClickListener {
|
||||
(ownerActivity as MainActivity?)?.getFragment<VideoDetailFragment>()?.closeVideoDetails()
|
||||
StateCasting.instance.activeDevice?.stopVideo()
|
||||
}
|
||||
|
||||
_buttonNext = findViewById(R.id.button_next);
|
||||
_buttonNext.setOnClickListener {
|
||||
(ownerActivity as MainActivity?)?.getFragment<VideoDetailFragment>()?.nextVideo()
|
||||
}
|
||||
|
||||
_buttonClose.setOnClickListener { dismiss(); };
|
||||
_buttonDisconnect.setOnClickListener {
|
||||
StateCasting.instance.activeDevice?.stopCasting();
|
||||
dismiss();
|
||||
};
|
||||
|
||||
_sliderPosition.addOnChangeListener(OnChangeListener { _, value, fromUser ->
|
||||
if (!fromUser) {
|
||||
return@OnChangeListener
|
||||
}
|
||||
|
||||
val activeDevice = StateCasting.instance.activeDevice ?: return@OnChangeListener;
|
||||
try {
|
||||
activeDevice.seekVideo(value.toDouble());
|
||||
} catch (e: Throwable) {
|
||||
Logger.e(TAG, "Failed to change volume.", e);
|
||||
}
|
||||
});
|
||||
|
||||
//TODO: Check if volume slider is properly hidden in all cases
|
||||
_sliderVolume.addOnChangeListener(OnChangeListener { _, value, _ ->
|
||||
_sliderVolume.addOnChangeListener(OnChangeListener { _, value, fromUser ->
|
||||
if (!fromUser) {
|
||||
return@OnChangeListener
|
||||
}
|
||||
|
||||
val activeDevice = StateCasting.instance.activeDevice ?: return@OnChangeListener;
|
||||
if (activeDevice.canSetVolume) {
|
||||
try {
|
||||
@@ -71,11 +131,21 @@ class ConnectedCastingDialog(context: Context?) : AlertDialog(context) {
|
||||
super.show();
|
||||
Logger.i(TAG, "Dialog shown.");
|
||||
|
||||
_device?.onVolumeChanged?.remove(this);
|
||||
_device?.onVolumeChanged?.subscribe {
|
||||
StateCasting.instance.onActiveDeviceVolumeChanged.remove(this);
|
||||
StateCasting.instance.onActiveDeviceVolumeChanged.subscribe {
|
||||
_sliderVolume.value = it.toFloat();
|
||||
};
|
||||
|
||||
StateCasting.instance.onActiveDeviceTimeChanged.remove(this);
|
||||
StateCasting.instance.onActiveDeviceTimeChanged.subscribe {
|
||||
_sliderPosition.value = it.toFloat();
|
||||
};
|
||||
|
||||
StateCasting.instance.onActiveDeviceDurationChanged.remove(this);
|
||||
StateCasting.instance.onActiveDeviceDurationChanged.subscribe {
|
||||
_sliderPosition.valueTo = it.toFloat();
|
||||
};
|
||||
|
||||
_device = StateCasting.instance.activeDevice;
|
||||
val d = _device;
|
||||
val isConnected = d != null && d.connectionState == CastConnectionState.CONNECTED;
|
||||
@@ -89,7 +159,9 @@ class ConnectedCastingDialog(context: Context?) : AlertDialog(context) {
|
||||
|
||||
override fun dismiss() {
|
||||
super.dismiss();
|
||||
_device?.onVolumeChanged?.remove(this);
|
||||
StateCasting.instance.onActiveDeviceVolumeChanged.remove(this);
|
||||
StateCasting.instance.onActiveDeviceDurationChanged.remove(this);
|
||||
StateCasting.instance.onActiveDeviceTimeChanged.remove(this);
|
||||
_device = null;
|
||||
StateCasting.instance.onActiveDeviceConnectionStateChanged.remove(this);
|
||||
}
|
||||
@@ -110,6 +182,9 @@ class ConnectedCastingDialog(context: Context?) : AlertDialog(context) {
|
||||
|
||||
_textName.text = d.name;
|
||||
_sliderVolume.value = d.volume.toFloat();
|
||||
_sliderPosition.valueFrom = 0.0f;
|
||||
_sliderPosition.valueTo = d.duration.toFloat();
|
||||
_sliderPosition.value = d.time.toFloat();
|
||||
|
||||
if (d.canSetVolume) {
|
||||
_layoutVolumeAdjustable.visibility = View.VISIBLE;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -20,14 +27,17 @@ import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo
|
||||
import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideoDetails
|
||||
import com.futo.platformplayer.constructs.Event1
|
||||
import com.futo.platformplayer.exceptions.DownloadException
|
||||
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 hasAnySource
|
||||
import isDownloadable
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -54,15 +64,10 @@ 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();
|
||||
val thumbnail: String? get() = videoDetails?.thumbnails?.getHQThumbnail();
|
||||
|
||||
var targetPixelCount: Long? = null;
|
||||
var targetBitrate: 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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -10,23 +10,49 @@ 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.api.media.platforms.js.internal.JSHttpClient
|
||||
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
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
class V8Plugin {
|
||||
val config: IV8PluginConfig;
|
||||
private val _client: ManagedHttpClient;
|
||||
private val _clientAuth: ManagedHttpClient;
|
||||
private val _clientOthers: ConcurrentHashMap<String, JSHttpClient> = ConcurrentHashMap();
|
||||
|
||||
|
||||
val httpClient: ManagedHttpClient get() = _client;
|
||||
val httpClientAuth: ManagedHttpClient get() = _clientAuth;
|
||||
val httpClientOthers: Map<String, JSHttpClient> get() = _clientOthers;
|
||||
|
||||
fun registerHttpClient(client: JSHttpClient) {
|
||||
synchronized(_clientOthers) {
|
||||
_clientOthers.put(client.clientId, client);
|
||||
}
|
||||
}
|
||||
|
||||
private val _runtimeLock = Object();
|
||||
var _runtime : V8Runtime? = null;
|
||||
@@ -61,7 +87,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 +234,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}");
|
||||
@@ -253,7 +279,7 @@ class V8Plugin {
|
||||
throwExceptionFromV8(
|
||||
config,
|
||||
result.getOrThrow(config, "plugin_type", "V8Plugin"),
|
||||
result.getOrThrow(config, "message", "V8Plugin"),
|
||||
context + ":" + result.getOrThrow(config, "message", "V8Plugin"),
|
||||
null,
|
||||
null,
|
||||
codeStripped
|
||||
@@ -264,7 +290,7 @@ class V8Plugin {
|
||||
return result;
|
||||
}
|
||||
catch(scriptEx: JavetCompilationException) {
|
||||
throw ScriptCompilationException(config, "Compilation: ${scriptEx.message}\n(${scriptEx.scriptingError.lineNumber})[${scriptEx.scriptingError.startColumn}-${scriptEx.scriptingError.endColumn}]: ${scriptEx.scriptingError.sourceLine}", null, codeStripped);
|
||||
throw ScriptCompilationException(config, "Compilation: [${context}]: ${scriptEx.message}\n(${scriptEx.scriptingError.lineNumber})[${scriptEx.scriptingError.startColumn}-${scriptEx.scriptingError.endColumn}]: ${scriptEx.scriptingError.sourceLine}", null, codeStripped);
|
||||
}
|
||||
catch(executeEx: JavetExecutionException) {
|
||||
if(executeEx.scriptingError?.context?.containsKey("plugin_type") == true) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
-3
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,8 +40,7 @@ class PackageDOMParser : V8Package {
|
||||
@V8Property
|
||||
fun childNodes(): List<DOMNode> {
|
||||
val results = _element.children().map { DOMNode(_package, it) }.toList();
|
||||
if(results != null)
|
||||
_children.addAll(results);
|
||||
_children.addAll(results);
|
||||
return results;
|
||||
}
|
||||
@V8Property
|
||||
|
||||
@@ -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
|
||||
@@ -48,7 +45,12 @@ class PackageHttp: V8Package {
|
||||
|
||||
@V8Function
|
||||
fun newClient(withAuth: Boolean): PackageHttpClient {
|
||||
return PackageHttpClient(this, if(withAuth) _clientAuth.clone() else _client.clone());
|
||||
val httpClient = if(withAuth) _clientAuth.clone() else _client.clone();
|
||||
if(httpClient is JSHttpClient)
|
||||
_plugin.registerHttpClient(httpClient);
|
||||
val client = PackageHttpClient(this, httpClient);
|
||||
|
||||
return client;
|
||||
}
|
||||
@V8Function
|
||||
fun getDefaultClient(withAuth: Boolean): PackageHttpClient {
|
||||
@@ -190,10 +192,19 @@ class PackageHttp: V8Package {
|
||||
|
||||
@Transient
|
||||
private val _defaultHeaders = mutableMapOf<String, String>();
|
||||
@Transient
|
||||
private val _clientId: String?;
|
||||
|
||||
@V8Property
|
||||
fun clientId(): String? {
|
||||
return _clientId;
|
||||
}
|
||||
|
||||
|
||||
constructor(pack: PackageHttp, baseClient: ManagedHttpClient): super() {
|
||||
_package = pack;
|
||||
_client = baseClient;
|
||||
_clientId = if(_client is JSHttpClient) _client.clientId else null;
|
||||
}
|
||||
|
||||
@V8Function
|
||||
@@ -227,10 +238,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 +252,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 +267,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 +281,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 +294,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 +316,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 +333,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 +360,7 @@ class PackageHttp: V8Package {
|
||||
|
||||
return@v stringBuilder.toString();
|
||||
};
|
||||
}
|
||||
}*/
|
||||
|
||||
fun <T> logExceptions(handle: ()->T): T {
|
||||
try {
|
||||
|
||||
@@ -4,6 +4,6 @@ class RateLimitException : Throwable {
|
||||
val pluginIds: List<String>;
|
||||
|
||||
constructor(pluginIds: List<String>): super() {
|
||||
this.pluginIds = pluginIds ?: listOf();
|
||||
this.pluginIds = pluginIds;
|
||||
}
|
||||
}
|
||||
+9
-3
@@ -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) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user