diff --git a/app/src/main/java/com/futo/platformplayer/SettingsDev.kt b/app/src/main/java/com/futo/platformplayer/SettingsDev.kt index aa5e4c94..76d7adf2 100644 --- a/app/src/main/java/com/futo/platformplayer/SettingsDev.kt +++ b/app/src/main/java/com/futo/platformplayer/SettingsDev.kt @@ -8,6 +8,7 @@ import androidx.work.WorkManager import com.caoccao.javet.values.primitive.V8ValueInteger import com.caoccao.javet.values.primitive.V8ValueString import com.futo.platformplayer.activities.DeveloperActivity +import com.futo.platformplayer.activities.MainActivity import com.futo.platformplayer.activities.SettingsActivity import com.futo.platformplayer.api.http.ManagedHttpClient import com.futo.platformplayer.api.media.models.contents.IPlatformContent @@ -491,6 +492,13 @@ class SettingsDev : FragmentedStorageFileJson() { } } } + + + @FormField(R.string.test_playback, FieldForm.BUTTON, + R.string.test_playback, 1) + fun testPlayback(context: Context) { + context.startActivity(MainActivity.getActionIntent(context, "TEST_PLAYBACK")); + } } diff --git a/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt index 90902f5e..eebc1275 100644 --- a/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt +++ b/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt @@ -538,6 +538,11 @@ class MainActivity : AppCompatActivity, IWithResultLauncher { "IMPORT_OPTIONS" -> { UIDialogs.showImportOptionsDialog(this); } + "ACTION" -> { + val action = intent.getStringExtra("ACTION"); + StateDeveloper.instance.testState = "TestPlayback"; + StateDeveloper.instance.testPlayback(); + } "TAB" -> { when(intent.getStringExtra("TAB")){ "Sources" -> { @@ -1180,6 +1185,13 @@ class MainActivity : AppCompatActivity, IWithResultLauncher { sourcesIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); return sourcesIntent; } + fun getActionIntent(context: Context, action: String) : Intent { + val sourcesIntent = Intent(context, MainActivity::class.java); + sourcesIntent.action = "ACTION"; + sourcesIntent.putExtra("ACTION", action); + sourcesIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + return sourcesIntent; + } fun getImportOptionsIntent(context: Context): Intent { val sourcesIntent = Intent(context, MainActivity::class.java); diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginConfig.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginConfig.kt index 52914770..744c9d92 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginConfig.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginConfig.kt @@ -5,6 +5,7 @@ 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.Contextual import java.net.URL import java.util.UUID @@ -77,7 +78,8 @@ class SourcePluginConfig( private var _allowUrlsLowerVal: List? = null; private val _allowUrlsLower: List get() { if(_allowUrlsLowerVal == null) - _allowUrlsLowerVal = allowUrls.map { it.lowercase() }; + _allowUrlsLowerVal = allowUrls.map { it.lowercase() } + .filter { it.length > 0 && (it[0] != '*' || (_allowRegex.matches(it))) }; return _allowUrlsLowerVal!!; }; @@ -170,10 +172,12 @@ class SourcePluginConfig( return true; val uri = Uri.parse(url); val host = uri.host?.lowercase() ?: ""; - return _allowUrlsLower.any { it == host }; + return _allowUrlsLower.any { it == host || (it.length > 0 && it[0] == '*' && host.endsWith(it.substring(1))) }; } companion object { + private val _allowRegex = Regex("\\*\\.[a-z0-9]*\\.[a-z]*"); + fun fromJson(json: String, sourceUrl: String? = null): SourcePluginConfig { val obj = Serializer.json.decodeFromString(json); if(obj.sourceUrl == null) diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSAudioWithMetadataSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSAudioWithMetadataSource.kt index 42eeffce..5130a2a4 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSAudioWithMetadataSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSAudioWithMetadataSource.kt @@ -35,4 +35,9 @@ class JSAudioUrlRangeSource : JSAudioUrlSource, IStreamMetaDataSource { indexEnd = _obj.getOrDefault(config, "indexEnd", contextName, null); audioChannels = _obj.getOrDefault(config, "audioChannels", contextName, 2) ?: 2; } + + override fun toString(): String { + return "RangeSource(url=[${getAudioUrl()}], itagId=[${itagId}], initStart=[${initStart}], initEnd=[${initEnd}], indexStart=[${indexStart}], indexEnd=[${indexEnd}]))"; + return super.toString() + } } \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSVideoWithMetadataSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSVideoWithMetadataSource.kt index 0e86c2fc..7c77120c 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSVideoWithMetadataSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSVideoWithMetadataSource.kt @@ -33,4 +33,9 @@ class JSVideoUrlRangeSource : JSVideoUrlSource, IStreamMetaDataSource { indexStart = _obj.getOrDefault(config, "indexStart", contextName, null); indexEnd = _obj.getOrDefault(config, "indexEnd", contextName, null); } + + override fun toString(): String { + return "RangeSource(url=[${getVideoUrl()}], itagId=[${itagId}], initStart=[${initStart}], initEnd=[${initEnd}], indexStart=[${indexStart}], indexEnd=[${indexEnd}]))"; + return super.toString() + } } \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/engine/packages/PackageBridge.kt b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageBridge.kt index 44464b0d..c26885d9 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/packages/PackageBridge.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageBridge.kt @@ -52,6 +52,7 @@ class PackageBridge : V8Package { @V8Function fun toast(str: String) { + Logger.i(TAG, "Plugin toast [${_config.name}]: ${str}"); StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) { try { UIDialogs.toast(str); diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt index c072e9a0..128d7e67 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt @@ -102,6 +102,7 @@ import com.futo.platformplayer.selectBestImage import com.futo.platformplayer.states.AnnouncementType import com.futo.platformplayer.states.StateAnnouncement import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.states.StateDeveloper import com.futo.platformplayer.states.StateDownloads import com.futo.platformplayer.states.StateHistory import com.futo.platformplayer.states.StatePlatform @@ -1772,19 +1773,21 @@ class VideoDetailView : ConstraintLayout { } } - val bestVideoSources = (videoSources?.map { it.height * it.width } + val doDedup = false; + + val bestVideoSources = if(doDedup) (videoSources?.map { it.height * it.width } ?.distinct() ?.map { x -> VideoHelper.selectBestVideoSource(videoSources.filter { x == it.height * it.width }, -1, FutoVideoPlayerBase.PREFERED_VIDEO_CONTAINERS) } ?.plus(videoSources.filter { it is IHLSManifestSource || it is IDashManifestSource })) ?.distinct() ?.filter { it != null } - ?.toList() ?: listOf(); + ?.toList() ?: listOf() else videoSources?.toList() ?: listOf() val bestAudioContainer = audioSources?.let { VideoHelper.selectBestAudioSource(it, FutoVideoPlayerBase.PREFERED_AUDIO_CONTAINERS)?.container }; - val bestAudioSources = audioSources + val bestAudioSources = if(doDedup) audioSources ?.filter { it.container == bestAudioContainer } ?.plus(audioSources.filter { it is IHLSManifestAudioSource || it is IDashManifestSource }) ?.distinct() - ?.toList() ?: listOf(); + ?.toList() ?: listOf() else audioSources?.toList() ?: listOf(); val canSetSpeed = !_isCasting || StateCasting.instance.activeDevice?.canSetSpeed == true val currentPlaybackRate = if (_isCasting) StateCasting.instance.activeDevice?.speed else _player.getPlaybackRate() @@ -2312,6 +2315,15 @@ class VideoDetailView : ConstraintLayout { } updateTracker(positionMilliseconds, isPlaying, false); + + if(StateDeveloper.instance.isPlaybackTesting) { + if((positionMilliseconds > 1000 * 70 || positionMilliseconds > (video!!.duration * 1000 - 1000))) { + StateDeveloper.instance.testPlayback(); + } + else if(video!!.duration > 70 && positionMilliseconds < 10000) { + handleSeek(40000); + } + } } private fun updateTracker(positionMs: Long, isPlaying: Boolean, forceUpdate: Boolean = false) { diff --git a/app/src/main/java/com/futo/platformplayer/states/StateDeveloper.kt b/app/src/main/java/com/futo/platformplayer/states/StateDeveloper.kt index 12904470..fcd16129 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateDeveloper.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateDeveloper.kt @@ -1,11 +1,19 @@ package com.futo.platformplayer.states import android.content.Context +import com.futo.platformplayer.SettingsDev import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.activities.MainActivity import com.futo.platformplayer.api.http.server.ManagedHttpServer +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.structures.IPager +import com.futo.platformplayer.api.media.structures.PlatformContentPager import com.futo.platformplayer.developer.DeveloperEndpoints import com.futo.platformplayer.engine.exceptions.ScriptExecutionException +import com.futo.platformplayer.fragment.mainactivity.main.VideoDetailView import com.futo.platformplayer.logging.Logger +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import kotlin.system.measureTimeMillis /*** @@ -23,6 +31,12 @@ class StateDeveloper { var devProxy: DevProxySettings? = null; + var testState: String? = null; + val isPlaybackTesting: Boolean get() { + return SettingsDev.instance.developerMode && testState == "TestPlayback"; + }; + + fun initializeDev(id: String) { currentDevID = id; synchronized(_devLogs) { @@ -135,6 +149,37 @@ class StateDeveloper { } + private var homePager: IPager? = null; + private var pagerIndex = 0; + fun testPlayback(){ + val mainActivity = if(StateApp.instance.isMainActive) StateApp.instance.context as MainActivity else return; + StateApp.instance.scope.launch(Dispatchers.IO) { + if(homePager == null) + homePager = StatePlatform.instance.getHome(); + var pager = homePager ?: return@launch; + pagerIndex++; + val video = if(pager.getResults().size <= pagerIndex) { + if(!pager.hasMorePages()) { + homePager = StatePlatform.instance.getHome(); + pager = homePager as IPager; + } + pager.nextPage(); + pagerIndex = 0; + val results = pager.getResults(); + if(results.size <= 0) + null; + else + results[0]; + } + else + pager.getResults()[pagerIndex]; + + StateApp.instance.scope.launch(Dispatchers.Main) { + mainActivity.navigate(mainActivity._fragVideoDetail, video); + } + } + } + companion object { const val DEV_ID = "DEV"; @@ -152,6 +197,7 @@ class StateDeveloper { it._server?.stop(); } } + } @kotlinx.serialization.Serializable diff --git a/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayerBase.kt b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayerBase.kt index 6889d911..21aca2a7 100644 --- a/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayerBase.kt +++ b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayerBase.kt @@ -645,13 +645,14 @@ abstract class FutoVideoPlayerBase : RelativeLayout { when (error.errorCode) { PlaybackException.ERROR_CODE_IO_BAD_HTTP_STATUS -> { + Logger.w(TAG, "ERROR_CODE_IO_BAD_HTTP_STATUS ${error.cause?.javaClass?.simpleName}"); if(error.cause is HttpDataSource.InvalidResponseCodeException) { val cause = error.cause as HttpDataSource.InvalidResponseCodeException - Logger.v(TAG, null) { + Logger.w(TAG, null) { "ERROR BAD HTTP ${cause.responseCode},\n" + - "Video Source: ${V8RemoteObject.gsonStandard.toJson(lastVideoSource)}\n" + - "Audio Source: ${V8RemoteObject.gsonStandard.toJson(lastAudioSource)}\n" + + "Video Source: ${lastVideoSource?.toString()}\n" + + "Audio Source: ${lastAudioSource?.toString()}\n" + "Dash: ${_lastGeneratedDash}" }; } diff --git a/app/src/main/java/com/futo/platformplayer/views/video/datasources/JSHttpDataSource.java b/app/src/main/java/com/futo/platformplayer/views/video/datasources/JSHttpDataSource.java index bfd3d3e3..94b0fc99 100644 --- a/app/src/main/java/com/futo/platformplayer/views/video/datasources/JSHttpDataSource.java +++ b/app/src/main/java/com/futo/platformplayer/views/video/datasources/JSHttpDataSource.java @@ -25,6 +25,7 @@ import androidx.media3.datasource.HttpDataSource; import androidx.media3.datasource.HttpUtil; import androidx.media3.datasource.TransferListener; +import com.futo.platformplayer.engine.dev.V8RemoteObject; import com.futo.platformplayer.logging.Logger; import com.google.common.base.Predicate; import com.google.common.collect.ForwardingMap; @@ -46,6 +47,8 @@ import java.util.Map; import java.util.Set; import java.util.zip.GZIPInputStream; +import kotlinx.serialization.json.Json; + /* * Based on the default ExoPlayer DefaultHttpDataSource */ @@ -583,7 +586,7 @@ public class JSHttpDataSource extends BaseDataSource implements HttpDataSource { requestHeaders = result.getHeaders(); } - Logger.Companion.v("JSHttpDataSource", "DataSource REQ: " + requestUrl, null); + Logger.Companion.v("JSHttpDataSource", "DataSource REQ: " + requestUrl + "\nHEADERS: [" + V8RemoteObject.Companion.getGsonStandard().toJson(requestHeaders)+ "]", null); HttpURLConnection connection = openConnection(new URL(requestUrl)); connection.setConnectTimeout(connectTimeoutMillis); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ea17dc8a..f4398812 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -477,6 +477,8 @@ Removes all subscriptions Settings related to development server, be careful as it may open your phone to security vulnerabilities Start Server + Test Playback + Keeps playing videos Subscriptions Cache 5000 History Cache 100 Start Server on boot