diff --git a/app/src/main/java/com/futo/platformplayer/Utility.kt b/app/src/main/java/com/futo/platformplayer/Utility.kt index c7dcd707..b2ca4f80 100644 --- a/app/src/main/java/com/futo/platformplayer/Utility.kt +++ b/app/src/main/java/com/futo/platformplayer/Utility.kt @@ -16,6 +16,8 @@ import android.widget.TextView import androidx.core.content.ContextCompat import androidx.core.content.FileProvider import com.futo.platformplayer.api.http.ManagedHttpClient +import com.futo.platformplayer.api.media.IPlatformClient +import com.futo.platformplayer.api.media.PlatformMultiClientPool import com.futo.platformplayer.api.media.models.video.IPlatformVideo import com.futo.platformplayer.engine.V8Plugin import com.futo.platformplayer.logging.Logger @@ -69,6 +71,8 @@ fun String.isHexColor(): Boolean { return _regexHexColor.matches(this); } +fun IPlatformClient.fromPool(pool: PlatformMultiClientPool) = pool.getClientPooled(this); + fun IPlatformVideo.withTimestamp(sec: Long) = PlatformVideoWithTime(this, sec); 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 43ad99ee..0d57ca06 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 @@ -4,13 +4,17 @@ class PlatformMultiClientPool { private val _maxCap: Int; private val _clientPools: HashMap = hashMapOf(); + private var _isFake = false; + constructor(maxCap: Int = -1) { _maxCap = if(maxCap > 0) maxCap else 99; } - fun getClientPooled(parentClient: IPlatformClient, capacity: Int): IPlatformClient { + fun getClientPooled(parentClient: IPlatformClient, capacity: Int = _maxCap): IPlatformClient { + if(_isFake) + return parentClient; val pool = synchronized(_clientPools) { if(!_clientPools.containsKey(parentClient)) _clientPools[parentClient] = PlatformClientPool(parentClient).apply { @@ -25,4 +29,10 @@ class PlatformMultiClientPool { }; return pool.getClient(capacity.coerceAtMost(_maxCap)); } + + //Allows for testing disabling pooling without changing callers + fun asFake(): PlatformMultiClientPool { + _isFake = true; + return this; + } } \ No newline at end of file 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 d727487c..0c5653aa 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt @@ -31,6 +31,7 @@ import com.futo.platformplayer.awaitFirstNotNullDeferred import com.futo.platformplayer.constructs.BatchedTaskHandler import com.futo.platformplayer.constructs.Event0 import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.fromPool import com.futo.platformplayer.getNowDiffDays import com.futo.platformplayer.getNowDiffSeconds import com.futo.platformplayer.logging.Logger @@ -63,8 +64,18 @@ class StatePlatform { private val _availableClients : ArrayList = ArrayList(); private val _enabledClients : ArrayList = ArrayList(); - private val _channelClientPool = PlatformMultiClientPool(15); - private val _trackerClientPool = PlatformMultiClientPool(1); + //ClientPools are used to isolate plugin usage of certain components from others + //This prevents for example a background task like subscriptions from blocking a user from opening a video + //It also allows parallel usage of plugins that would otherwise be impossible. + //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 _primaryClientPersistent = FragmentedStorage.get("primaryClient"); private var _primaryClientObj : IPlatformClient? = null; @@ -86,8 +97,9 @@ class StatePlatform { private val _batchTaskGetVideoDetails: BatchedTaskHandler = BatchedTaskHandler(_scope, { url -> Logger.i(StatePlatform::class.java.name, "Fetching video details [${url}]"); - _enabledClients.find { it.isContentDetailsUrl(url) }?.getContentDetails(url) - ?: throw NoPlatformClientException("No client enabled that supports this url ($url)"); + _enabledClients.find { it.isContentDetailsUrl(url) }?.let { + _mainClientPool.getClientPooled(it).getContentDetails(url) + } ?: throw NoPlatformClientException("No client enabled that supports this url ($url)"); }, { if(!Settings.instance.browsing.videoCache) @@ -363,7 +375,7 @@ class StatePlatform { synchronized(clientIdsOngoing) { clientIdsOngoing.add(it.id); } - val homeResult = it.getHome(); + val homeResult = it.fromPool(_pagerClientPool).getHome(); synchronized(clientIdsOngoing) { clientIdsOngoing.remove(it.id); } @@ -383,7 +395,7 @@ class StatePlatform { val deferred: List?>>> = clients.map { return@map Pair(it, scope.async(Dispatchers.IO) { try { - val searchResult = it.getHome(); + val searchResult = it.fromPool(_pagerClientPool).getHome(); return@async searchResult; } catch(ex: Throwable) { Logger.e(TAG, "getHomeRefresh", ex); @@ -774,7 +786,7 @@ class StatePlatform { if(!client.capabilities.hasGetComments) return EmptyPager(); - return client.getComments(url); + return client.fromPool(_mainClientPool).getComments(url); } fun getSubComments(comment: IPlatformComment): IPager { Logger.i(TAG, "Platform - getSubComments"); @@ -785,7 +797,7 @@ class StatePlatform { fun getLiveEvents(url: String): IPager? { Logger.i(TAG, "Platform - getLiveChat"); var client = getContentClient(url); - return client.getLiveEvents(url); + return client.fromPool(_liveEventClientPool).getLiveEvents(url); } fun getLiveChatWindow(url: String): ILiveChatWindowDescriptor? { Logger.i(TAG, "Platform - getLiveChat");