diff --git a/app/src/main/java/com/futo/platformplayer/Settings.kt b/app/src/main/java/com/futo/platformplayer/Settings.kt index c818a8ab..536a774c 100644 --- a/app/src/main/java/com/futo/platformplayer/Settings.kt +++ b/app/src/main/java/com/futo/platformplayer/Settings.kt @@ -333,7 +333,7 @@ class Settings : FragmentedStorageFileJson() { "Submit logs to help us narrow down issues", 1 ) fun submitLogs() { - StateApp.instance.scopeGetter().launch(Dispatchers.IO) { + StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) { try { if (!Logger.submitLogs()) { withContext(Dispatchers.Main) { @@ -462,7 +462,7 @@ class Settings : FragmentedStorageFileJson() { fun viewChangelog() { UIDialogs.toast("Retrieving changelog"); SettingsActivity.getActivity()?.let { - StateApp.instance.scopeGetter().launch(Dispatchers.IO) { + StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) { try { val version = StateUpdate.instance.downloadVersionCode(ManagedHttpClient()) ?: return@launch; Logger.i(TAG, "Version retrieved $version"); 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 ba7f9473..60e73a37 100644 --- a/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt +++ b/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt @@ -392,7 +392,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher { override fun onResume() { super.onResume(); - Logger.i(TAG, "onResume") + Logger.v(TAG, "onResume") val curOrientation = _orientationManager.orientation; @@ -408,13 +408,10 @@ class MainActivity : AppCompatActivity, IWithResultLauncher { val videoToOpen = StateSaved.instance.videoToOpen; if (_wasStopped) { - Logger.i(TAG, "_wasStopped is true"); - Logger.i(TAG, "set _wasStopped = false"); _wasStopped = false; - Logger.i(TAG, "onResume videoToOpen=$videoToOpen"); - if (videoToOpen != null && _fragVideoDetail.state == VideoDetailFragment.State.CLOSED) { + Logger.i(TAG, "onResume videoToOpen=$videoToOpen"); if (StatePlatform.instance.hasEnabledVideoClient(videoToOpen.url)) { navigate(_fragVideoDetail, UrlVideoWithTime(videoToOpen.url, videoToOpen.timeSeconds, false)); _fragVideoDetail.maximizeVideoDetail(true); @@ -427,13 +424,13 @@ class MainActivity : AppCompatActivity, IWithResultLauncher { override fun onPause() { super.onPause(); - Logger.i(TAG, "onPause") + Logger.v(TAG, "onPause") _isVisible = false; } override fun onStop() { super.onStop() - Logger.i(TAG, "_wasStopped = true"); + Logger.v(TAG, "_wasStopped = true"); _wasStopped = true; } @@ -722,22 +719,20 @@ class MainActivity : AppCompatActivity, IWithResultLauncher { } _fragVideoDetail.onOrientationChanged(OrientationManager.Orientation.PORTRAIT); } - - Logger.i(TAG, "onRestart5"); } override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration) { super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig); val isStop: Boolean = lifecycle.currentState == Lifecycle.State.CREATED; - Logger.i(TAG, "onPictureInPictureModeChanged isInPictureInPictureMode=$isInPictureInPictureMode isStop=$isStop") + Logger.v(TAG, "onPictureInPictureModeChanged isInPictureInPictureMode=$isInPictureInPictureMode isStop=$isStop") _fragVideoDetail?.onPictureInPictureModeChanged(isInPictureInPictureMode, isStop, newConfig); - Logger.i(TAG, "onPictureInPictureModeChanged Ready"); + Logger.v(TAG, "onPictureInPictureModeChanged Ready"); } override fun onDestroy() { super.onDestroy(); - Logger.i(TAG, "onDestroy") + Logger.v(TAG, "onDestroy") _orientationManager.disable(); diff --git a/app/src/main/java/com/futo/platformplayer/api/http/server/ManagedHttpServer.kt b/app/src/main/java/com/futo/platformplayer/api/http/server/ManagedHttpServer.kt index 09f898bf..31db3ad4 100644 --- a/app/src/main/java/com/futo/platformplayer/api/http/server/ManagedHttpServer.kt +++ b/app/src/main/java/com/futo/platformplayer/api/http/server/ManagedHttpServer.kt @@ -63,7 +63,7 @@ class ManagedHttpServer(private val _requestedPort: Int = 0) { } }.start(); - Logger.i(TAG, "Started ${port}. \n" + getAddresses().map { it.hostAddress }.joinToString("\n")); + Logger.i(TAG, "Started HTTP Server ${port}. \n" + getAddresses().map { it.hostAddress }.joinToString("\n")); } @Synchronized fun stop() { diff --git a/app/src/main/java/com/futo/platformplayer/api/media/LiveChatManager.kt b/app/src/main/java/com/futo/platformplayer/api/media/LiveChatManager.kt index 78c99bff..3e265c10 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/LiveChatManager.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/LiveChatManager.kt @@ -94,7 +94,10 @@ class LiveChatManager { if(_pager is JSLiveEventPager) nextInterval = _pager.nextRequest.coerceAtLeast(800).toLong(); - Logger.i(TAG, "New Live Events (${newEvents.size}) [${newEvents.map { it.type.name }.joinToString(", ")}]"); + if(newEvents.size > 0) + Logger.i(TAG, "New Live Events (${newEvents.size}) [${newEvents.map { it.type.name }.joinToString(", ")}]"); + else + Logger.v(TAG, "No new Live Events"); _scope.launch(Dispatchers.Main) { try { diff --git a/app/src/main/java/com/futo/platformplayer/api/media/PlatformClientPool.kt b/app/src/main/java/com/futo/platformplayer/api/media/PlatformClientPool.kt index ef1fc37c..5961082b 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/PlatformClientPool.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/PlatformClientPool.kt @@ -11,12 +11,14 @@ class PlatformClientPool { private val _parent: JSClient; private val _pool: HashMap = hashMapOf(); private var _poolCounter = 0; + private val _poolName: String?; var isDead: Boolean = false private set; val onDead = Event2(); - constructor(parentClient: IPlatformClient) { + constructor(parentClient: IPlatformClient, name: String? = null) { + _poolName = name; if(parentClient !is JSClient) throw IllegalArgumentException("Pooling only supported for JSClients right now"); Logger.i(TAG, "Pool for ${parentClient.name} was started"); @@ -47,7 +49,7 @@ class PlatformClientPool { _poolCounter++; reserved = _pool.keys.find { !it.isBusy }; if(reserved == null && _pool.size < capacity) { - Logger.i(TAG, "Started additional [${_parent.name}] client in pool (${_pool.size + 1}/${capacity})"); + Logger.i(TAG, "Started additional [${_parent.name}] client in pool [${_poolName}] (${_pool.size + 1}/${capacity})"); reserved = _parent.getCopy(); reserved?.initialize(); _pool[reserved!!] = _poolCounter; diff --git a/app/src/main/java/com/futo/platformplayer/api/media/PlatformMultiClientPool.kt b/app/src/main/java/com/futo/platformplayer/api/media/PlatformMultiClientPool.kt index 0d57ca06..235a28b0 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/PlatformMultiClientPool.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/PlatformMultiClientPool.kt @@ -1,12 +1,14 @@ package com.futo.platformplayer.api.media class PlatformMultiClientPool { + private val _name: String; private val _maxCap: Int; private val _clientPools: HashMap = hashMapOf(); private var _isFake = false; - constructor(maxCap: Int = -1) { + constructor(name: String, maxCap: Int = -1) { + _name = name; _maxCap = if(maxCap > 0) maxCap else 99; @@ -17,7 +19,7 @@ class PlatformMultiClientPool { return parentClient; val pool = synchronized(_clientPools) { if(!_clientPools.containsKey(parentClient)) - _clientPools[parentClient] = PlatformClientPool(parentClient).apply { + _clientPools[parentClient] = PlatformClientPool(parentClient, _name).apply { this.onDead.subscribe { client, pool -> synchronized(_clientPools) { if(_clientPools[parentClient] == pool) diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSContent.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSContent.kt index 5eb5ab5f..6127601c 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSContent.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSContent.kt @@ -42,7 +42,6 @@ open class JSContent : IPlatformContent, IPluginSourced { id = PlatformID.fromV8(_pluginConfig, _content.getOrThrow(config, "id", contextName)); name = HtmlCompat.fromHtml(_content.getOrThrow(config, "name", contextName).decodeUnicode(), HtmlCompat.FROM_HTML_MODE_LEGACY).toString(); - Logger.i("JSContent", "name=$name"); author = PlatformAuthorLink.fromV8(_pluginConfig, _content.getOrThrow(config, "author", contextName)); val datetimeInt = _content.getOrThrow(config, "datetime", contextName).toLong(); diff --git a/app/src/main/java/com/futo/platformplayer/api/media/structures/MultiParallelPager.kt b/app/src/main/java/com/futo/platformplayer/api/media/structures/MultiParallelPager.kt index 9c782e29..a703619b 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/structures/MultiParallelPager.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/structures/MultiParallelPager.kt @@ -137,11 +137,11 @@ abstract class MultiParallelPager : IPager, IAsyncPager { } } } - Logger.i(TAG, "Pager prepare in ${timeForPage}ms"); + Logger.v(TAG, "Pager prepare in ${timeForPage}ms"); val timeAwait = measureTimeMillis { _currentResults = results.map { it.await() }.mapNotNull { it }; }; - Logger.i(TAG, "Pager load in ${timeAwait}ms"); + Logger.v(TAG, "Pager load in ${timeAwait}ms"); _currentResultExceptions = exceptions; return _currentResults; diff --git a/app/src/main/java/com/futo/platformplayer/cache/ChannelContentCache.kt b/app/src/main/java/com/futo/platformplayer/cache/ChannelContentCache.kt index d25333c9..d0ff0b4f 100644 --- a/app/src/main/java/com/futo/platformplayer/cache/ChannelContentCache.kt +++ b/app/src/main/java/com/futo/platformplayer/cache/ChannelContentCache.kt @@ -111,7 +111,7 @@ class ChannelContentCache { init { val results = pager.getResults(); - Logger.i(TAG, "Caching ${results.size} subscription initial results"); + Logger.i(TAG, "Caching ${results.size} subscription initial results [${pager.hashCode()}]"); scope.launch(Dispatchers.IO) { try { val newCacheItems = instance.cacheVideos(results); diff --git a/app/src/main/java/com/futo/platformplayer/casting/StateCasting.kt b/app/src/main/java/com/futo/platformplayer/casting/StateCasting.kt index 197232ac..a4a14c37 100644 --- a/app/src/main/java/com/futo/platformplayer/casting/StateCasting.kt +++ b/app/src/main/java/com/futo/platformplayer/casting/StateCasting.kt @@ -64,7 +64,7 @@ class StateCasting { } override fun serviceResolved(event: ServiceEvent) { - Logger.i(TAG, "ChromeCast service resolved: " + event.info); + Logger.v(TAG, "ChromeCast service resolved: " + event.info); addOrUpdateDevice(event); } diff --git a/app/src/main/java/com/futo/platformplayer/constructs/TaskHandler.kt b/app/src/main/java/com/futo/platformplayer/constructs/TaskHandler.kt index 9307fff3..52798c4f 100644 --- a/app/src/main/java/com/futo/platformplayer/constructs/TaskHandler.kt +++ b/app/src/main/java/com/futo/platformplayer/constructs/TaskHandler.kt @@ -91,7 +91,7 @@ class TaskHandler { if (!onError.emit(e, parameter)) { Logger.e(TAG, "Uncaught exception handled by TaskHandler.", e); } else { - Logger.w(TAG, "Handled exception in TaskHandler invoke.", e); + //Logger.w(TAG, "Handled exception in TaskHandler invoke.", e); (Prevents duplicate logs) } } } diff --git a/app/src/main/java/com/futo/platformplayer/dialogs/CommentDialog.kt b/app/src/main/java/com/futo/platformplayer/dialogs/CommentDialog.kt index 1d401348..f3a981be 100644 --- a/app/src/main/java/com/futo/platformplayer/dialogs/CommentDialog.kt +++ b/app/src/main/java/com/futo/platformplayer/dialogs/CommentDialog.kt @@ -57,7 +57,7 @@ class CommentDialog(context: Context?, val contextUrl: String, val ref: Protocol val processHandle = StatePolycentric.instance.processHandle!! val eventPointer = processHandle.post(comment, null, ref) - StateApp.instance.scopeGetter().launch(Dispatchers.IO) { + StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) { try { processHandle.fullyBackfillServers() } catch (e: Throwable) { diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PostDetailFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PostDetailFragment.kt index 6441c5fa..eede58fe 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PostDetailFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PostDetailFragment.kt @@ -355,7 +355,7 @@ class PostDetailFragment : MainFragment { processHandle.opinion(ref, Opinion.neutral); } - StateApp.instance.scopeGetter().launch(Dispatchers.IO) { + StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) { try { processHandle.fullyBackfillServers(); } catch (e: Throwable) { diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailFragment.kt index 033eb62b..8504242d 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailFragment.kt @@ -306,12 +306,12 @@ class VideoDetailFragment : MainFragment { override fun onResume() { super.onResume(); - Logger.i(TAG, "onResume"); + Logger.v(TAG, "onResume"); _isActive = true; _leavingPiP = false; _viewDetail?.let { - Logger.i(TAG, "onResume preventPictureInPicture=false"); + Logger.v(TAG, "onResume preventPictureInPicture=false"); it.preventPictureInPicture = false; if (state != State.CLOSED) { @@ -326,7 +326,7 @@ class VideoDetailFragment : MainFragment { } override fun onPause() { super.onPause(); - Logger.i(TAG, "onPause"); + Logger.v(TAG, "onPause"); _isActive = false; if(!isInPictureInPicture && state != State.CLOSED) @@ -334,7 +334,7 @@ class VideoDetailFragment : MainFragment { } override fun onStop() { - Logger.i(TAG, "onStop"); + Logger.v(TAG, "onStop"); stopIfRequired(); super.onStop(); @@ -352,7 +352,7 @@ class VideoDetailFragment : MainFragment { shouldStop = false; } - Logger.i(TAG, "shouldStop: $shouldStop"); + Logger.v(TAG, "shouldStop: $shouldStop"); if(shouldStop) { _viewDetail?.let { val v = it.video ?: return@let; @@ -361,13 +361,13 @@ class VideoDetailFragment : MainFragment { _viewDetail?.onStop(); StateCasting.instance.onStop(); - Logger.i(TAG, "called onStop() shouldStop: $shouldStop"); + Logger.v(TAG, "called onStop() shouldStop: $shouldStop"); } } override fun onDestroyMainView() { super.onDestroyMainView(); - Logger.i(TAG, "onDestroyMainView"); + Logger.v(TAG, "onDestroyMainView"); _viewDetail?.let { _viewDetail = null; it.onDestroy(); 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 b8929a75..8ba894be 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 @@ -100,6 +100,7 @@ import kotlinx.coroutines.* import userpackage.Protocol import java.time.OffsetDateTime import kotlin.collections.ArrayList +import kotlin.math.abs import kotlin.math.roundToLong import kotlin.streams.toList @@ -232,9 +233,19 @@ class VideoDetailView : ConstraintLayout { private val DP_5 = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5f, resources.displayMetrics); private val DP_2 = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2f, resources.displayMetrics); + private var _retryJob: Job? = null; private var _retryCount = 0; - private val _retryIntervals: Array = arrayOf(1, 2, 4, 8, 16, 32); + //TODO: Determine better behavior, waiting 60 seconds for an error that is guaranteed to happen is a bit much. (Needed? If so, maybe need special UI for retrying) + private val _retryIntervals: Array = arrayOf(1, 1);//2, 4, 8, 16, 32); + + private var _liveTryJob: Job? = null; + private val _liveStreamCheckInterval = listOf( + Pair(-10 * 60, 5 * 60), //around 10 minutes, try every 1 minute + Pair(-5 * 60, 30), //around 5 minutes, try every 30 seconds + Pair(0, 10) //around live, try every 10 seconds + ); + constructor(context: Context, attrs : AttributeSet? = null) : super(context, attrs) { inflate(context, R.layout.fragview_video_detail, this); @@ -660,7 +671,7 @@ class VideoDetailView : ConstraintLayout { //Lifecycle fun onResume() { - Logger.i(TAG, "onResume"); + Logger.v(TAG, "onResume"); _onPauseCalled = false; Logger.i(TAG, "_video: ${video?.name ?: "no video"}"); @@ -694,7 +705,7 @@ class VideoDetailView : ConstraintLayout { _player.updateRotateLock(); } fun onPause() { - Logger.i(TAG, "onPause"); + Logger.v(TAG, "onPause"); _onPauseCalled = true; _taskLoadVideo.cancel(); @@ -722,6 +733,8 @@ class VideoDetailView : ConstraintLayout { _overlay_quality_selector?.hide(); _retryJob?.cancel(); _retryJob = null; + _liveTryJob?.cancel(); + _liveTryJob = null; _taskLoadVideo.cancel(); handleStop(); _didStop = true; @@ -808,6 +821,8 @@ class VideoDetailView : ConstraintLayout { _retryJob?.cancel(); _retryJob = null; + _liveTryJob?.cancel(); + _liveTryJob = null; _retryCount = 0; fetchVideo(); @@ -897,6 +912,8 @@ class VideoDetailView : ConstraintLayout { _retryJob?.cancel(); _retryJob = null; + _liveTryJob?.cancel(); + _liveTryJob = null; _retryCount = 0; fetchVideo(); } @@ -1034,7 +1051,7 @@ class VideoDetailView : ConstraintLayout { processHandle.opinion(ref, Opinion.neutral); } - StateApp.instance.scopeGetter().launch(Dispatchers.IO) { + fragment.lifecycleScope.launch(Dispatchers.IO) { try { processHandle.fullyBackfillServers(); } catch (e: Throwable) { @@ -1130,6 +1147,8 @@ class VideoDetailView : ConstraintLayout { if(video.isLive && video.live != null) { loadLiveChat(video); } + if(video.isLive && video.live == null && !video.video.videoSources.any()) + startLiveTry(video); updateMoreButtons(); } @@ -1259,7 +1278,7 @@ class VideoDetailView : ConstraintLayout { //If LiveStream, set to end if(videoSource is IDashManifestSource || videoSource is IHLSManifestSource) { if (video?.isLive == true) { - _player.seekToEnd(5000); + _player.seekToEnd(6000); } val videoTracks = _player.exoPlayer?.player?.currentTracks?.groups?.firstOrNull { it.mediaTrackGroup.type == C.TRACK_TYPE_VIDEO } @@ -1961,7 +1980,7 @@ class VideoDetailView : ConstraintLayout { } fun setProgressBarOverlayed(isOverlayed: Boolean?) { - Logger.i(TAG, "setProgressBarOverlayed(isOverlayed: ${isOverlayed ?: "null"})"); + Logger.v(TAG, "setProgressBarOverlayed(isOverlayed: ${isOverlayed ?: "null"})"); isOverlayed?.let{ _cast.setProgressBarOverlayed(it) }; if(isOverlayed == null) { @@ -2080,6 +2099,8 @@ class VideoDetailView : ConstraintLayout { _retryCount = 0; _retryJob?.cancel(); _retryJob = null; + _liveTryJob?.cancel(); + _liveTryJob = null; UIDialogs.showGeneralRetryErrorDialog(context, "Failed to load video (ScriptException)", it, ::fetchVideo); } } @@ -2090,6 +2111,8 @@ class VideoDetailView : ConstraintLayout { _retryCount = 0; _retryJob?.cancel(); _retryJob = null; + _liveTryJob?.cancel(); + _liveTryJob = null; UIDialogs.showGeneralRetryErrorDialog(context, "Failed to load video", it, ::fetchVideo); } } else TaskHandler(IPlatformVideoDetails::class.java, {fragment.lifecycleScope}); @@ -2107,14 +2130,16 @@ class VideoDetailView : ConstraintLayout { Log.i(TAG, "handleErrorOrCall _retryCount=$_retryCount, starting retry job"); _retryJob?.cancel(); - _retryJob = StateApp.instance.scopeGetter().launch(Dispatchers.Main) { + _retryJob = StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) { try { delay(_retryIntervals[_retryCount++] * 1000); fetchVideo(); } catch (e: Throwable) { - Logger.e(TAG, "Failed to fetch video.", e) + Logger.e(TAG, "Failed to retry fetch video.", e) } } + _liveTryJob?.cancel(); + _liveTryJob = null; } else if (isConnected && nextVideo()) { Log.i(TAG, "handleErrorOrCall retries failed, is connected, skipped to next video"); } else { @@ -2123,6 +2148,46 @@ class VideoDetailView : ConstraintLayout { } } + private fun startLiveTry(liveTryVideo: IPlatformVideoDetails) { + val datetime = liveTryVideo.datetime ?: return; + val diffSeconds = datetime.getNowDiffSeconds(); + val toWait = _liveStreamCheckInterval.toList().sortedBy { abs(diffSeconds - it.first) }.firstOrNull()?.second?.toLong() ?: return; + + fragment.lifecycleScope.launch(Dispatchers.Main){ + UIDialogs.toast(context, "Not yet available, retrying in ${toWait}s"); + } + + _liveTryJob?.cancel(); + _liveTryJob = fragment.lifecycleScope.launch(Dispatchers.IO) { + try { + delay(toWait * 1000); + val videoDetail = StatePlatform.instance.getContentDetails(liveTryVideo.url, true).await(); + if(videoDetail !is IPlatformVideoDetails) + throw IllegalStateException("Expected media content, found ${video?.contentType}"); + + if(videoDetail.datetime != null && videoDetail.live == null && !videoDetail.video.videoSources.any()) { + if(videoDetail.datetime!! > OffsetDateTime.now()) + withContext(Dispatchers.Main) { + UIDialogs.toast(context, "Planned in ${videoDetail.datetime?.toHumanNowDiffString(true)}"); + } + startLiveTry(liveTryVideo); + _liveTryJob = null; + } + else + withContext(Dispatchers.Main) { + setVideoDetails(videoDetail); + _liveTryJob = null; + } + } + catch(ex: Throwable) { + Logger.e(TAG, "Failed to live try fetch video.", ex); + withContext(Dispatchers.Main) { + UIDialogs.toast(context, "Failed to retry for live stream"); + } + } + } + } + fun applyFragment(frag: VideoDetailFragment) { fragment = frag; fragment.onMinimize.subscribe { diff --git a/app/src/main/java/com/futo/platformplayer/services/MediaPlaybackService.kt b/app/src/main/java/com/futo/platformplayer/services/MediaPlaybackService.kt index 0254383f..c9bf4cee 100644 --- a/app/src/main/java/com/futo/platformplayer/services/MediaPlaybackService.kt +++ b/app/src/main/java/com/futo/platformplayer/services/MediaPlaybackService.kt @@ -50,7 +50,7 @@ class MediaPlaybackService : Service() { private var _focusRequest: AudioFocusRequest? = null; override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - Logger.i(TAG, "onStartCommand"); + Logger.v(TAG, "onStartCommand"); if(!FragmentedStorage.isInitialized) { @@ -122,12 +122,12 @@ class MediaPlaybackService : Service() { } override fun onCreate() { - Logger.i(TAG, "onCreate called"); + Logger.v(TAG, "onCreate"); super.onCreate() } override fun onDestroy() { - Logger.i(TAG, "onDestroy called"); + Logger.v(TAG, "onDestroy"); _instance = null; MediaControlReceiver.onCloseReceived.emit(); super.onDestroy(); @@ -138,7 +138,7 @@ class MediaPlaybackService : Service() { } fun closeMediaSession() { - Logger.i(TAG, "closeMediaSession called"); + Logger.v(TAG, "closeMediaSession"); stopForeground(true); val focusRequest = _focusRequest; @@ -159,7 +159,7 @@ class MediaPlaybackService : Service() { } fun updateMediaSession(videoUpdated: IPlatformVideo?) { - Logger.i(TAG, "updateMediaSession called"); + Logger.v(TAG, "updateMediaSession"); var isUpdating = false; val video: IPlatformVideo; if(videoUpdated == null) { @@ -270,7 +270,7 @@ class MediaPlaybackService : Service() { val notif = builder.build(); notif.flags = notif.flags or NotificationCompat.FLAG_ONGOING_EVENT or NotificationCompat.FLAG_NO_CLEAR; - Logger.i(TAG, "Updating notification bitmap=${if (bitmap != null) "not null" else "null"} channelId=${channel.id} icon=${icon} video=${video?.name ?: ""} playWhenReady=${playWhenReady} session.sessionToken=${session.sessionToken}"); + Logger.i(TAG, "Updating notification bitmap=${if (bitmap != null) "yes" else "no."} channelId=${channel.id} icon=${icon} video=${video?.name ?: ""} playWhenReady=${playWhenReady} session.sessionToken=${session.sessionToken}"); startForeground(MEDIA_NOTIF_ID, notif, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK); diff --git a/app/src/main/java/com/futo/platformplayer/states/StateApp.kt b/app/src/main/java/com/futo/platformplayer/states/StateApp.kt index 446cc684..dd61fd79 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateApp.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateApp.kt @@ -299,7 +299,7 @@ class StateApp { } Logger.onLogSubmitted.subscribe { - scopeGetter().launch(Dispatchers.Main) { + scopeOrNull?.launch(Dispatchers.Main) { try { if (it != null) { UIDialogs.toast("Uploaded " + (it ?: "null"), true); @@ -510,7 +510,6 @@ class StateApp { if (_lastNetworkState != NetworkState.DISCONNECTED) { scopeOrNull?.launch(Dispatchers.Main) { try { - Logger.i(TAG, "onConnectionAvailable emitted"); onConnectionAvailable.emit(); } catch (e: Throwable) { Logger.e(TAG, "Failed to emit onConnectionAvailable", e) diff --git a/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt b/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt index 0c5653aa..928b82b4 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt @@ -70,11 +70,11 @@ class StatePlatform { //Pools always follow the behavior of the base client. So if user disables a plugin, it kills all pooled clients. //Each pooled client adds additional memory usage. //WARNING: Be careful with pooling some calls, as they might use the plugin subsequently afterwards. For example pagers might block plugins in future calls. - private val _mainClientPool = PlatformMultiClientPool(2); //Used for all main user events, generally user critical - private val _pagerClientPool = PlatformMultiClientPool(2); //Used primarily for calls that result in front-end pagers, preventing them from blocking other calls. - private val _channelClientPool = PlatformMultiClientPool(15); //Used primarily for subscription/background channel fetches - private val _trackerClientPool = PlatformMultiClientPool(1); //Used exclusively for playback trackers - private val _liveEventClientPool = PlatformMultiClientPool(1); //Used exclusively for live events + private val _mainClientPool = PlatformMultiClientPool("Main", 2); //Used for all main user events, generally user critical + private val _pagerClientPool = PlatformMultiClientPool("Pagers", 2); //Used primarily for calls that result in front-end pagers, preventing them from blocking other calls. + private val _channelClientPool = PlatformMultiClientPool("Channels", 15); //Used primarily for subscription/background channel fetches + private val _trackerClientPool = PlatformMultiClientPool("Trackers", 1); //Used exclusively for playback trackers + private val _liveEventClientPool = PlatformMultiClientPool("LiveEvents", 1); //Used exclusively for live events private val _primaryClientPersistent = FragmentedStorage.get("primaryClient"); diff --git a/app/src/main/java/com/futo/platformplayer/states/StatePlayer.kt b/app/src/main/java/com/futo/platformplayer/states/StatePlayer.kt index a80cc09f..028c5b60 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StatePlayer.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StatePlayer.kt @@ -25,7 +25,7 @@ class StatePlayer { private val MIN_BUFFER_DURATION = 10000; private val MAX_BUFFER_DURATION = 60000; private val MIN_PLAYBACK_START_BUFFER = 500; - private val MIN_PLAYBACK_RESUME_BUFFER = 1000; + private val MIN_PLAYBACK_RESUME_BUFFER = 2500; private val BUFFER_SIZE = 1024 * 64; var isOpen : Boolean = false diff --git a/app/src/main/java/com/futo/platformplayer/states/StatePlugins.kt b/app/src/main/java/com/futo/platformplayer/states/StatePlugins.kt index 24682573..1fbf537e 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StatePlugins.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StatePlugins.kt @@ -27,12 +27,20 @@ class StatePlugins { private val TAG = "StatePlugins"; private val FORCE_REINSTALL_EMBEDDED = false; + private var _isFirstEmbedUpdate = true; private val _pluginScripts = FragmentedStorage.getDirectory(); private var _plugins = FragmentedStorage.storeJson("plugins") .load(); private val iconsDir = FragmentedStorage.getDirectory(); + + private val _syncObject = Object() + private var _embeddedSources: Map? = null + private var _embeddedSourcesDefault: List? = null + private var _sourcesUnderConstruction: Map? = null + + fun getPluginIconOrNull(id: String): ImageVariable? { if(iconsDir.hasIcon(id)) return iconsDir.getIconBinary(id); @@ -53,18 +61,6 @@ class StatePlugins { } } - @Serializable - private data class PluginConfig( - val SOURCES_EMBEDDED: Map, - val SOURCES_EMBEDDED_DEFAULT: List, - val SOURCES_UNDER_CONSTRUCTION: Map - ) - - private val _syncObject = Object() - private var _embeddedSources: Map? = null - private var _embeddedSourcesDefault: List? = null - private var _sourcesUnderConstruction: Map? = null - private fun ensureSourcesConfigLoaded(context: Context) { if (_embeddedSources != null && _embeddedSourcesDefault != null && _sourcesUnderConstruction != null) { return @@ -122,8 +118,11 @@ class StatePlugins { Logger.i(TAG, "Found outdated embedded plugin [${existing.config.id}] ${existing.config.name}, deleting and reinstalling"); deletePlugin(embedded.key); } + else if(existing != null && _isFirstEmbedUpdate) + Logger.i(TAG, "Embedded plugin [${existing.config.id}] ${existing.config.name}, up to date (${existing.config.version} >= ${embeddedConfig?.version})"); } } + _isFirstEmbedUpdate = false; } fun installMissingEmbeddedPlugins(context: Context) { val plugins = getPlugins(); @@ -422,6 +421,13 @@ class StatePlugins { } + @Serializable + private data class PluginConfig( + val SOURCES_EMBEDDED: Map, + val SOURCES_EMBEDDED_DEFAULT: List, + val SOURCES_UNDER_CONSTRUCTION: Map + ) + companion object { private var _instance : StatePlugins? = null; val instance : StatePlugins diff --git a/app/src/main/java/com/futo/platformplayer/states/StateSubscriptions.kt b/app/src/main/java/com/futo/platformplayer/states/StateSubscriptions.kt index ba940c79..e5236e78 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateSubscriptions.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateSubscriptions.kt @@ -67,7 +67,7 @@ class StateSubscriptions { return Pair(_lastGlobalSubscriptionProgress, _lastGlobalSubscriptionTotal); } fun updateSubscriptionFeed(scope: CoroutineScope, onlyIfNull: Boolean = false, onProgress: ((Int, Int)->Unit)? = null) { - Logger.i(TAG, "updateSubscriptionFeed"); + Logger.v(TAG, "updateSubscriptionFeed"); scope.launch(Dispatchers.IO) { synchronized(_globalSubscriptionsLock) { if (isGlobalUpdating || (onlyIfNull && _globalSubscriptionFeed != null)) { diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/CommentViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/CommentViewHolder.kt index a946a937..d6ddb531 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/CommentViewHolder.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/CommentViewHolder.kt @@ -67,7 +67,7 @@ class CommentViewHolder : ViewHolder { processHandle.opinion(c.reference, Opinion.neutral); } - StateApp.instance.scopeGetter().launch(Dispatchers.IO) { + StateApp.instance.scopeOrNull?.launch(Dispatchers.IO) { try { processHandle.fullyBackfillServers(); } catch (e: Throwable) { diff --git a/app/src/main/java/com/futo/platformplayer/views/announcements/AnnouncementView.kt b/app/src/main/java/com/futo/platformplayer/views/announcements/AnnouncementView.kt index 7064e219..e6dda325 100644 --- a/app/src/main/java/com/futo/platformplayer/views/announcements/AnnouncementView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/announcements/AnnouncementView.kt @@ -78,8 +78,6 @@ class AnnouncementView : LinearLayout { } override fun onAttachedToWindow() { - Logger.i(TAG, "onAttachedToWindow"); - super.onAttachedToWindow() StateAnnouncement.instance.onAnnouncementChanged.subscribe(this) { _scope?.launch(Dispatchers.Main) { @@ -91,14 +89,12 @@ class AnnouncementView : LinearLayout { } override fun onDetachedFromWindow() { - Logger.i(TAG, "onDetachedFromWindow"); - super.onDetachedFromWindow() StateAnnouncement.instance.onAnnouncementChanged.remove(this) } private fun refresh() { - Logger.i(TAG, "refresh"); + Logger.v(TAG, "refresh"); val announcements = StateAnnouncement.instance.getVisibleAnnouncements(_category); setAnnouncement(announcements.firstOrNull(), announcements.size); } diff --git a/app/src/main/java/com/futo/platformplayer/views/behavior/GestureControlView.kt b/app/src/main/java/com/futo/platformplayer/views/behavior/GestureControlView.kt index 1b3ed5a1..6b5c63f7 100644 --- a/app/src/main/java/com/futo/platformplayer/views/behavior/GestureControlView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/behavior/GestureControlView.kt @@ -107,7 +107,7 @@ class GestureControlView : LinearLayout { } else { val rx = p0.x / width; val ry = p0.y / height; - Logger.i(TAG, "rx = $rx, ry = $ry, _isFullScreen = $_isFullScreen") + Logger.v(TAG, "rx = $rx, ry = $ry, _isFullScreen = $_isFullScreen") if (ry > 0.1 && ry < 0.9) { if (_isFullScreen && rx < 0.4) { startAdjustingBrightness(); diff --git a/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt index 603978db..76cf609d 100644 --- a/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt +++ b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt @@ -328,7 +328,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase { } override fun onPlaybackStateChanged(playbackState: Int) { - Logger.i(TAG, "onPlaybackStateChanged $playbackState"); + Logger.v(TAG, "onPlaybackStateChanged $playbackState"); val timeLeft = abs(position - duration); if (playbackState == ExoPlayer.STATE_ENDED) { 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 1394d0ea..5c4d6394 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 @@ -130,9 +130,9 @@ abstract class FutoVideoPlayerBase : RelativeLayout { override fun onAttachedToWindow() { super.onAttachedToWindow(); - Logger.i(TAG, "Attached onConnectionAvailable listener."); + Logger.v(TAG, "Attached onConnectionAvailable listener."); StateApp.instance.onConnectionAvailable.subscribe(_referenceObject) { - Logger.i(TAG, "onConnectionAvailable"); + Logger.v(TAG, "onConnectionAvailable"); val pos = position; val dur = duration; diff --git a/app/src/unstable/assets/sources/youtube b/app/src/unstable/assets/sources/youtube index 2061a75e..75816961 160000 --- a/app/src/unstable/assets/sources/youtube +++ b/app/src/unstable/assets/sources/youtube @@ -1 +1 @@ -Subproject commit 2061a75ec1a9428b83f8cef5b84738c6f9cac290 +Subproject commit 75816961722eb8166866f96f7003d1f5e9d59e8e