Add debug log entry for video player pool usage.

fork-5.53.8
Alex Hart 2022-05-20 14:54:24 -03:00
rodzic 63f4f0bcec
commit eaa7262b2f
9 zmienionych plików z 90 dodań i 32 usunięć

Wyświetl plik

@ -54,7 +54,7 @@ public final class GiphyMp4ProjectionPlayerHolder implements Player.Listener, De
this.policyEnforcer = policyEnforcer; this.policyEnforcer = policyEnforcer;
if (player.getExoPlayer() == null) { if (player.getExoPlayer() == null) {
SimpleExoPlayer fromPool = ApplicationDependencies.getExoPlayerPool().get(); SimpleExoPlayer fromPool = ApplicationDependencies.getExoPlayerPool().get(TAG);
if (fromPool == null) { if (fromPool == null) {
Log.i(TAG, "Could not get exoplayer from pool."); Log.i(TAG, "Could not get exoplayer from pool.");
@ -142,7 +142,7 @@ public final class GiphyMp4ProjectionPlayerHolder implements Player.Listener, De
@Override @Override
public void onResume(@NonNull LifecycleOwner owner) { public void onResume(@NonNull LifecycleOwner owner) {
if (mediaItem != null) { if (mediaItem != null) {
SimpleExoPlayer fromPool = ApplicationDependencies.getExoPlayerPool().get(); SimpleExoPlayer fromPool = ApplicationDependencies.getExoPlayerPool().get(TAG);
if (fromPool != null) { if (fromPool != null) {
ExoPlayerKt.configureForGifPlayback(fromPool); ExoPlayerKt.configureForGifPlayback(fromPool);
fromPool.addListener(this); fromPool.addListener(this);

Wyświetl plik

@ -0,0 +1,39 @@
package org.thoughtcrime.securesms.logsubmit
import android.content.Context
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.video.exo.ExoPlayerPool
/**
* Prints off the current exoplayer pool stats, including ownership info.
*/
class LogSectionExoPlayerPool : LogSection {
override fun getTitle(): String = "EXOPLAYER POOL"
override fun getContent(context: Context): CharSequence {
val poolStats = ApplicationDependencies.getExoPlayerPool().getPoolStats()
val owners: Map<String, List<ExoPlayerPool.OwnershipInfo>> = poolStats.owners.groupBy { it.tag }
val output = StringBuilder()
output.append("Total players created: ${poolStats.created}\n")
output.append("Max allowed unreserved instances: ${poolStats.maxUnreserved}\n")
output.append("Max allowed reserved instances: ${poolStats.maxReserved}\n")
output.append("Available created unreserved instances: ${poolStats.unreservedAndAvailable}\n")
output.append("Available created reserved instances: ${poolStats.reservedAndAvailable}\n")
output.append("Total unreserved created: ${poolStats.unreserved}\n")
output.append("Total reserved created: ${poolStats.reserved}\n\n")
output.append("Ownership Info:\n")
if (owners.isEmpty()) {
output.append(" No ownership info to display.")
} else {
owners.forEach { (ownerTag, infoList) ->
output.append(" Owner $ownerTag\n")
output.append(" reserved: ${infoList.filter { it.isReserved }.size}\n")
output.append(" unreserved: ${infoList.filterNot { it.isReserved }.size}\n")
}
}
return output
}
}

Wyświetl plik

@ -78,6 +78,7 @@ public class SubmitDebugLogRepository {
} }
add(new LogSectionNotifications()); add(new LogSectionNotifications());
add(new LogSectionNotificationProfiles()); add(new LogSectionNotificationProfiles());
add(new LogSectionExoPlayerPool());
add(new LogSectionKeyPreferences()); add(new LogSectionKeyPreferences());
add(new LogSectionBadges()); add(new LogSectionBadges());
add(new LogSectionPermissions()); add(new LogSectionPermissions());

Wyświetl plik

@ -49,7 +49,7 @@ public final class VideoMediaPreviewFragment extends MediaPreviewFragment {
videoView = itemView.findViewById(R.id.video_player); videoView = itemView.findViewById(R.id.video_player);
videoView.setWindow(requireActivity().getWindow()); videoView.setWindow(requireActivity().getWindow());
videoView.setVideoSource(new VideoSlide(getContext(), uri, size, false), autoPlay); videoView.setVideoSource(new VideoSlide(getContext(), uri, size, false), autoPlay, TAG);
videoView.setPlayerPositionDiscontinuityCallback((v, r) -> { videoView.setPlayerPositionDiscontinuityCallback((v, r) -> {
if (events.getVideoControlsDelegate() != null) { if (events.getVideoControlsDelegate() != null) {
events.getVideoControlsDelegate().onPlayerPositionDiscontinuity(r); events.getVideoControlsDelegate().onPlayerPositionDiscontinuity(r);

Wyświetl plik

@ -98,7 +98,7 @@ public class VideoEditorFragment extends Fragment implements VideoEditorHud.Even
boolean autoplay = isVideoGif; boolean autoplay = isVideoGif;
player.setWindow(requireActivity().getWindow()); player.setWindow(requireActivity().getWindow());
player.setVideoSource(slide, autoplay); player.setVideoSource(slide, autoplay, TAG);
if (slide.isVideoGif()) { if (slide.isVideoGif()) {
player.setPlayerCallback(new VideoPlayer.PlayerCallback() { player.setPlayerCallback(new VideoPlayer.PlayerCallback() {

Wyświetl plik

@ -131,7 +131,7 @@ public class ViewOnceMessageActivity extends PassphraseRequiredActivity implemen
video.setWindow(getWindow()); video.setWindow(getWindow());
video.setPlayerStateCallbacks(this); video.setPlayerStateCallbacks(this);
video.setVideoSource(videoSlide, true); video.setVideoSource(videoSlide, true, TAG);
video.hideControls(); video.hideControls();
video.loopForever(); video.loopForever();

Wyświetl plik

@ -121,9 +121,9 @@ public class VideoPlayer extends FrameLayout {
private MediaItem mediaItem; private MediaItem mediaItem;
public void setVideoSource(@NonNull VideoSlide videoSource, boolean autoplay) { public void setVideoSource(@NonNull VideoSlide videoSource, boolean autoplay, String poolTag) {
if (exoPlayer == null) { if (exoPlayer == null) {
exoPlayer = ApplicationDependencies.getExoPlayerPool().require(); exoPlayer = ApplicationDependencies.getExoPlayerPool().require(poolTag);
exoPlayer.addListener(exoPlayerListener); exoPlayer.addListener(exoPlayerListener);
exoPlayer.addListener(playerListener); exoPlayer.addListener(playerListener);
exoView.setPlayer(exoPlayer); exoView.setPlayer(exoPlayer);

Wyświetl plik

@ -10,6 +10,7 @@ import com.google.android.exoplayer2.source.MediaSourceFactory
import com.google.android.exoplayer2.source.ProgressiveMediaSource import com.google.android.exoplayer2.source.ProgressiveMediaSource
import com.google.android.exoplayer2.upstream.DataSource import com.google.android.exoplayer2.upstream.DataSource
import com.google.android.exoplayer2.util.MimeTypes import com.google.android.exoplayer2.util.MimeTypes
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.net.ContentProxySelector import org.thoughtcrime.securesms.net.ContentProxySelector
import org.thoughtcrime.securesms.util.AppForegroundObserver import org.thoughtcrime.securesms.util.AppForegroundObserver
@ -81,6 +82,10 @@ abstract class ExoPlayerPool<T : ExoPlayer>(
private val maximumReservedPlayers: Int, private val maximumReservedPlayers: Int,
) : AppForegroundObserver.Listener { ) : AppForegroundObserver.Listener {
companion object {
private val TAG = Log.tag(ExoPlayerPool::class.java)
}
private val pool: MutableMap<T, PoolState> = mutableMapOf() private val pool: MutableMap<T, PoolState> = mutableMapOf()
/** /**
@ -89,8 +94,8 @@ abstract class ExoPlayerPool<T : ExoPlayer>(
* @return A player if one is available, otherwise null * @return A player if one is available, otherwise null
*/ */
@MainThread @MainThread
fun get(): T? { fun get(tag: String): T? {
return get(allowReserved = false) return get(allowReserved = false, tag = tag)
} }
/** /**
@ -100,8 +105,8 @@ abstract class ExoPlayerPool<T : ExoPlayer>(
* @throws IllegalStateException if no player is available. * @throws IllegalStateException if no player is available.
*/ */
@MainThread @MainThread
fun require(): T { fun require(tag: String): T {
return checkNotNull(get(allowReserved = true)) { "Required exoPlayer could not be acquired! :: ${poolStats()}" } return checkNotNull(get(allowReserved = true, tag = tag)) { "Required exoPlayer could not be acquired for $tag! :: ${poolStats()}" }
} }
/** /**
@ -113,25 +118,26 @@ abstract class ExoPlayerPool<T : ExoPlayer>(
fun pool(exoPlayer: T) { fun pool(exoPlayer: T) {
val poolState = pool[exoPlayer] val poolState = pool[exoPlayer]
if (poolState != null) { if (poolState != null) {
pool[exoPlayer] = poolState.copy(available = true) pool[exoPlayer] = poolState.copy(available = true, tag = null)
} else { } else {
throw IllegalArgumentException("Tried to return unknown ExoPlayer to pool :: ${poolStats()}") throw IllegalArgumentException("Tried to return unknown ExoPlayer to pool :: ${poolStats()}")
} }
} }
@MainThread @MainThread
private fun get(allowReserved: Boolean): T? { private fun get(allowReserved: Boolean, tag: String): T? {
val player = findAvailablePlayer(allowReserved) val player = findAvailablePlayer(allowReserved)
return if (player == null && pool.size < getMaximumAllowed(allowReserved)) { return if (player == null && pool.size < getMaximumAllowed(allowReserved)) {
val newPlayer = createPlayer() val newPlayer = createPlayer()
val poolState = createPoolStateForNewEntry(allowReserved) val poolState = createPoolStateForNewEntry(allowReserved, tag)
pool[newPlayer] = poolState pool[newPlayer] = poolState
newPlayer newPlayer
} else if (player != null) { } else if (player != null) {
val poolState = pool[player]!!.copy(available = false) val poolState = pool[player]!!.copy(available = false, tag = tag)
pool[player] = poolState pool[player] = poolState
player player
} else { } else {
Log.d(TAG, "Failed to get an ExoPlayer instance for tag: $tag")
null null
} }
} }
@ -140,11 +146,11 @@ abstract class ExoPlayerPool<T : ExoPlayer>(
return if (allowReserved) getMaxSimultaneousPlayback() else getMaxSimultaneousPlayback() - maximumReservedPlayers return if (allowReserved) getMaxSimultaneousPlayback() else getMaxSimultaneousPlayback() - maximumReservedPlayers
} }
private fun createPoolStateForNewEntry(allowReserved: Boolean): PoolState { private fun createPoolStateForNewEntry(allowReserved: Boolean, tag: String?): PoolState {
return if (allowReserved && pool.none { (_, v) -> v.reserved }) { return if (allowReserved && pool.none { (_, v) -> v.reserved }) {
PoolState(available = false, reserved = true) PoolState(available = false, reserved = true, tag = tag)
} else { } else {
PoolState(available = false, reserved = false) PoolState(available = false, reserved = false, tag = tag)
} }
} }
@ -175,39 +181,51 @@ abstract class ExoPlayerPool<T : ExoPlayer>(
} }
private fun poolStats(): String { private fun poolStats(): String {
return getPoolStats().toString()
}
fun getPoolStats(): PoolStats {
val poolStats = PoolStats( val poolStats = PoolStats(
created = pool.size, created = pool.size,
maxUnreserved = getMaxSimultaneousPlayback() - maximumReservedPlayers, maxUnreserved = getMaxSimultaneousPlayback() - maximumReservedPlayers,
maxReserved = maximumReservedPlayers maxReserved = maximumReservedPlayers,
owners = emptyList()
) )
pool.values.fold(poolStats) { acc, state -> return pool.values.fold(poolStats) { acc, state ->
Log.d(TAG, "$state")
acc.copy( acc.copy(
unreservedAndAvailable = acc.unreservedAndAvailable + if (state.unreservedAndAvailable) 1 else 0, unreservedAndAvailable = acc.unreservedAndAvailable + if (state.unreservedAndAvailable) 1 else 0,
reservedAndAvailable = acc.reservedAndAvailable + if (state.reservedAndAvailable) 1 else 0, reservedAndAvailable = acc.reservedAndAvailable + if (state.reservedAndAvailable) 1 else 0,
unreserved = acc.unreserved + if (!state.reserved) 1 else 0, unreserved = acc.unreserved + if (!state.reserved) 1 else 0,
reserved = acc.reserved + if (state.reserved) 1 else 0 reserved = acc.reserved + if (state.reserved) 1 else 0,
owners = if (!state.available) acc.owners + OwnershipInfo(state.tag!!, state.reserved) else acc.owners
) )
} }
return poolStats.toString()
} }
protected abstract fun getMaxSimultaneousPlayback(): Int protected abstract fun getMaxSimultaneousPlayback(): Int
private data class PoolStats( data class PoolStats(
val created: Int = 0, val created: Int = 0,
val maxUnreserved: Int = 0, val maxUnreserved: Int = 0,
val maxReserved: Int = 0, val maxReserved: Int = 0,
val unreservedAndAvailable: Int = 0, val unreservedAndAvailable: Int = 0,
val reservedAndAvailable: Int = 0, val reservedAndAvailable: Int = 0,
val unreserved: Int = 0, val unreserved: Int = 0,
val reserved: Int = 0 val reserved: Int = 0,
val owners: List<OwnershipInfo>
)
data class OwnershipInfo(
val tag: String,
val isReserved: Boolean
) )
private data class PoolState( private data class PoolState(
val available: Boolean, val available: Boolean,
val reserved: Boolean val reserved: Boolean,
val tag: String?
) { ) {
val unreservedAndAvailable = available && !reserved val unreservedAndAvailable = available && !reserved
val reservedAndAvailable = available && reserved val reservedAndAvailable = available && reserved

Wyświetl plik

@ -18,7 +18,7 @@ class ExoPlayerPoolTest {
val testSubject = createTestSubject(1, 1) val testSubject = createTestSubject(1, 1)
// WHEN // WHEN
val player = testSubject.require() val player = testSubject.require("")
// THEN // THEN
assertNotNull(player) assertNotNull(player)
@ -30,7 +30,7 @@ class ExoPlayerPoolTest {
val testSubject = createTestSubject(1, 0) val testSubject = createTestSubject(1, 0)
// WHEN // WHEN
testSubject.require() testSubject.require("")
// THEN // THEN
fail("Expected an IllegalStateException") fail("Expected an IllegalStateException")
@ -42,8 +42,8 @@ class ExoPlayerPoolTest {
val testSubject = createTestSubject(0, 10) val testSubject = createTestSubject(0, 10)
// WHEN // WHEN
val players = (1..10).map { testSubject.get() } val players = (1..10).map { testSubject.get("") }
val nulls = (1..10).map { testSubject.get() } val nulls = (1..10).map { testSubject.get("") }
// THEN // THEN
assertTrue(players.all { it != null }) assertTrue(players.all { it != null })
@ -54,11 +54,11 @@ class ExoPlayerPoolTest {
fun `Given a pool that allows 10 items and has all items checked out, when I return then check them all out again, then I expect 10 non null players`() { fun `Given a pool that allows 10 items and has all items checked out, when I return then check them all out again, then I expect 10 non null players`() {
// GIVEN // GIVEN
val testSubject = createTestSubject(0, 10) val testSubject = createTestSubject(0, 10)
val players = (1..10).map { testSubject.get() } val players = (1..10).map { testSubject.get("") }
// WHEN // WHEN
players.filterNotNull().forEach { testSubject.pool(it) } players.filterNotNull().forEach { testSubject.pool(it) }
val morePlayers = (1..10).map { testSubject.get() } val morePlayers = (1..10).map { testSubject.get("") }
assertTrue(morePlayers.all { it != null }) assertTrue(morePlayers.all { it != null })
} }