mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Move MediaMetricsListener creation and reporting off main thread
The creation can be moved to the playback thread (to guarantee it happens in sync other initialization after playback start) and the potentially blocking calls to the reporting methods can be moved to the generic shared BackgroundExecutor (it can't use the playback thread because it no longer exists when the session is ended after the player is released). PiperOrigin-RevId: 726872818 (cherry picked from commit d386e002d2b34817178d088f277ced3bf3943ef2)
This commit is contained in:
parent
cd6e61d856
commit
841e27ae5c
@ -49,7 +49,6 @@ import android.media.AudioDeviceCallback;
|
|||||||
import android.media.AudioDeviceInfo;
|
import android.media.AudioDeviceInfo;
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
import android.media.MediaFormat;
|
import android.media.MediaFormat;
|
||||||
import android.media.metrics.LogSessionId;
|
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
@ -354,14 +353,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||||||
playbackInfoUpdateHandler.post(() -> handlePlaybackInfo(playbackInfoUpdate));
|
playbackInfoUpdateHandler.post(() -> handlePlaybackInfo(playbackInfoUpdate));
|
||||||
playbackInfo = PlaybackInfo.createDummy(emptyTrackSelectorResult);
|
playbackInfo = PlaybackInfo.createDummy(emptyTrackSelectorResult);
|
||||||
analyticsCollector.setPlayer(this.wrappingPlayer, applicationLooper);
|
analyticsCollector.setPlayer(this.wrappingPlayer, applicationLooper);
|
||||||
PlayerId playerId =
|
PlayerId playerId = new PlayerId(builder.playerName);
|
||||||
Util.SDK_INT < 31
|
|
||||||
? new PlayerId(builder.playerName)
|
|
||||||
: Api31.registerMediaMetricsListener(
|
|
||||||
applicationContext,
|
|
||||||
/* player= */ this,
|
|
||||||
builder.usePlatformDiagnostics,
|
|
||||||
builder.playerName);
|
|
||||||
internalPlayer =
|
internalPlayer =
|
||||||
new ExoPlayerImplInternal(
|
new ExoPlayerImplInternal(
|
||||||
renderers,
|
renderers,
|
||||||
@ -401,6 +393,10 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||||||
if (builder.foregroundModeTimeoutMs > 0) {
|
if (builder.foregroundModeTimeoutMs > 0) {
|
||||||
internalPlayer.experimentalSetForegroundModeTimeoutMs(builder.foregroundModeTimeoutMs);
|
internalPlayer.experimentalSetForegroundModeTimeoutMs(builder.foregroundModeTimeoutMs);
|
||||||
}
|
}
|
||||||
|
if (Util.SDK_INT >= 31) {
|
||||||
|
Api31.registerMediaMetricsListener(
|
||||||
|
applicationContext, /* player= */ this, builder.usePlatformDiagnostics, playerId);
|
||||||
|
}
|
||||||
|
|
||||||
audioSessionIdState =
|
audioSessionIdState =
|
||||||
new BackgroundThreadStateHandler<>(
|
new BackgroundThreadStateHandler<>(
|
||||||
@ -3383,17 +3379,22 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||||||
private static final class Api31 {
|
private static final class Api31 {
|
||||||
private Api31() {}
|
private Api31() {}
|
||||||
|
|
||||||
public static PlayerId registerMediaMetricsListener(
|
public static void registerMediaMetricsListener(
|
||||||
Context context, ExoPlayerImpl player, boolean usePlatformDiagnostics, String playerName) {
|
Context context, ExoPlayerImpl player, boolean usePlatformDiagnostics, PlayerId playerId) {
|
||||||
@Nullable MediaMetricsListener listener = MediaMetricsListener.create(context);
|
HandlerWrapper playbackThreadHandler =
|
||||||
if (listener == null) {
|
player.getClock().createHandler(player.getPlaybackLooper(), /* callback= */ null);
|
||||||
Log.w(TAG, "MediaMetricsService unavailable.");
|
playbackThreadHandler.post(
|
||||||
return new PlayerId(LogSessionId.LOG_SESSION_ID_NONE, playerName);
|
() -> {
|
||||||
}
|
@Nullable MediaMetricsListener listener = MediaMetricsListener.create(context);
|
||||||
if (usePlatformDiagnostics) {
|
if (listener == null) {
|
||||||
player.addAnalyticsListener(listener);
|
Log.w(TAG, "MediaMetricsService unavailable.");
|
||||||
}
|
return;
|
||||||
return new PlayerId(listener.getLogSessionId(), playerName);
|
}
|
||||||
|
if (usePlatformDiagnostics) {
|
||||||
|
player.addAnalyticsListener(listener);
|
||||||
|
}
|
||||||
|
playerId.setLogSessionId(listener.getLogSessionId());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,6 +51,7 @@ import androidx.media3.common.Player;
|
|||||||
import androidx.media3.common.Timeline;
|
import androidx.media3.common.Timeline;
|
||||||
import androidx.media3.common.Tracks;
|
import androidx.media3.common.Tracks;
|
||||||
import androidx.media3.common.VideoSize;
|
import androidx.media3.common.VideoSize;
|
||||||
|
import androidx.media3.common.util.BackgroundExecutor;
|
||||||
import androidx.media3.common.util.NetworkTypeObserver;
|
import androidx.media3.common.util.NetworkTypeObserver;
|
||||||
import androidx.media3.common.util.NullableType;
|
import androidx.media3.common.util.NullableType;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
@ -76,6 +77,7 @@ import java.net.SocketTimeoutException;
|
|||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
|
import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
|
||||||
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||||
|
|
||||||
@ -108,6 +110,7 @@ public final class MediaMetricsListener
|
|||||||
}
|
}
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
|
private final Executor backgroundExecutor;
|
||||||
private final PlaybackSessionManager sessionManager;
|
private final PlaybackSessionManager sessionManager;
|
||||||
private final PlaybackSession playbackSession;
|
private final PlaybackSession playbackSession;
|
||||||
private final long startTimeMs;
|
private final long startTimeMs;
|
||||||
@ -145,6 +148,7 @@ public final class MediaMetricsListener
|
|||||||
context = context.getApplicationContext();
|
context = context.getApplicationContext();
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.playbackSession = playbackSession;
|
this.playbackSession = playbackSession;
|
||||||
|
backgroundExecutor = BackgroundExecutor.get();
|
||||||
window = new Timeline.Window();
|
window = new Timeline.Window();
|
||||||
period = new Timeline.Period();
|
period = new Timeline.Period();
|
||||||
bandwidthBytes = new HashMap<>();
|
bandwidthBytes = new HashMap<>();
|
||||||
@ -357,13 +361,14 @@ public final class MediaMetricsListener
|
|||||||
ErrorInfo errorInfo =
|
ErrorInfo errorInfo =
|
||||||
getErrorInfo(
|
getErrorInfo(
|
||||||
error, context, /* lastIoErrorForManifest= */ ioErrorType == C.DATA_TYPE_MANIFEST);
|
error, context, /* lastIoErrorForManifest= */ ioErrorType == C.DATA_TYPE_MANIFEST);
|
||||||
playbackSession.reportPlaybackErrorEvent(
|
PlaybackErrorEvent playbackErrorEvent =
|
||||||
new PlaybackErrorEvent.Builder()
|
new PlaybackErrorEvent.Builder()
|
||||||
.setTimeSinceCreatedMillis(realtimeMs - startTimeMs)
|
.setTimeSinceCreatedMillis(realtimeMs - startTimeMs)
|
||||||
.setErrorCode(errorInfo.errorCode)
|
.setErrorCode(errorInfo.errorCode)
|
||||||
.setSubErrorCode(errorInfo.subErrorCode)
|
.setSubErrorCode(errorInfo.subErrorCode)
|
||||||
.setException(error)
|
.setException(error)
|
||||||
.build());
|
.build();
|
||||||
|
backgroundExecutor.execute(() -> playbackSession.reportPlaybackErrorEvent(playbackErrorEvent));
|
||||||
reportedEventsForCurrentSession = true;
|
reportedEventsForCurrentSession = true;
|
||||||
pendingPlayerError = null;
|
pendingPlayerError = null;
|
||||||
}
|
}
|
||||||
@ -415,11 +420,12 @@ public final class MediaMetricsListener
|
|||||||
int networkType = getNetworkType(context);
|
int networkType = getNetworkType(context);
|
||||||
if (networkType != currentNetworkType) {
|
if (networkType != currentNetworkType) {
|
||||||
currentNetworkType = networkType;
|
currentNetworkType = networkType;
|
||||||
playbackSession.reportNetworkEvent(
|
NetworkEvent networkEvent =
|
||||||
new NetworkEvent.Builder()
|
new NetworkEvent.Builder()
|
||||||
.setNetworkType(networkType)
|
.setNetworkType(networkType)
|
||||||
.setTimeSinceCreatedMillis(realtimeMs - startTimeMs)
|
.setTimeSinceCreatedMillis(realtimeMs - startTimeMs)
|
||||||
.build());
|
.build();
|
||||||
|
backgroundExecutor.execute(() -> playbackSession.reportNetworkEvent(networkEvent));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -436,11 +442,13 @@ public final class MediaMetricsListener
|
|||||||
if (currentPlaybackState != newPlaybackState) {
|
if (currentPlaybackState != newPlaybackState) {
|
||||||
currentPlaybackState = newPlaybackState;
|
currentPlaybackState = newPlaybackState;
|
||||||
reportedEventsForCurrentSession = true;
|
reportedEventsForCurrentSession = true;
|
||||||
playbackSession.reportPlaybackStateEvent(
|
PlaybackStateEvent playbackStateEvent =
|
||||||
new PlaybackStateEvent.Builder()
|
new PlaybackStateEvent.Builder()
|
||||||
.setState(currentPlaybackState)
|
.setState(currentPlaybackState)
|
||||||
.setTimeSinceCreatedMillis(realtimeMs - startTimeMs)
|
.setTimeSinceCreatedMillis(realtimeMs - startTimeMs)
|
||||||
.build());
|
.build();
|
||||||
|
backgroundExecutor.execute(
|
||||||
|
() -> playbackSession.reportPlaybackStateEvent(playbackStateEvent));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -570,7 +578,8 @@ public final class MediaMetricsListener
|
|||||||
builder.setTrackState(TrackChangeEvent.TRACK_STATE_OFF);
|
builder.setTrackState(TrackChangeEvent.TRACK_STATE_OFF);
|
||||||
}
|
}
|
||||||
reportedEventsForCurrentSession = true;
|
reportedEventsForCurrentSession = true;
|
||||||
playbackSession.reportTrackChangeEvent(builder.build());
|
TrackChangeEvent trackChangeEvent = builder.build();
|
||||||
|
backgroundExecutor.execute(() -> playbackSession.reportTrackChangeEvent(trackChangeEvent));
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresNonNull("metricsBuilder")
|
@RequiresNonNull("metricsBuilder")
|
||||||
@ -613,7 +622,8 @@ public final class MediaMetricsListener
|
|||||||
networkBytes != null && networkBytes > 0
|
networkBytes != null && networkBytes > 0
|
||||||
? PlaybackMetrics.STREAM_SOURCE_NETWORK
|
? PlaybackMetrics.STREAM_SOURCE_NETWORK
|
||||||
: PlaybackMetrics.STREAM_SOURCE_UNKNOWN);
|
: PlaybackMetrics.STREAM_SOURCE_UNKNOWN);
|
||||||
playbackSession.reportPlaybackMetrics(metricsBuilder.build());
|
PlaybackMetrics playbackMetrics = metricsBuilder.build();
|
||||||
|
backgroundExecutor.execute(() -> playbackSession.reportPlaybackMetrics(playbackMetrics));
|
||||||
}
|
}
|
||||||
metricsBuilder = null;
|
metricsBuilder = null;
|
||||||
activeSessionId = null;
|
activeSessionId = null;
|
||||||
|
@ -33,10 +33,7 @@ public final class PlayerId {
|
|||||||
/**
|
/**
|
||||||
* A player identifier with unset default values that can be used as a placeholder or for testing.
|
* A player identifier with unset default values that can be used as a placeholder or for testing.
|
||||||
*/
|
*/
|
||||||
public static final PlayerId UNSET =
|
public static final PlayerId UNSET = new PlayerId(/* playerName= */ "");
|
||||||
Util.SDK_INT < 31
|
|
||||||
? new PlayerId(/* playerName= */ "")
|
|
||||||
: new PlayerId(LogSessionIdApi31.UNSET, /* playerName= */ "");
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A name to identify the player. Use {@link Builder#setName(String)} to set the name, otherwise
|
* A name to identify the player. Use {@link Builder#setName(String)} to set the name, otherwise
|
||||||
@ -52,31 +49,13 @@ public final class PlayerId {
|
|||||||
@Nullable private final Object equalityToken;
|
@Nullable private final Object equalityToken;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance for API < 31.
|
* Creates an instance.
|
||||||
*
|
*
|
||||||
* @param playerName The name of the player, for informational purpose only.
|
* @param playerName The name of the player, for informational purpose only.
|
||||||
*/
|
*/
|
||||||
public PlayerId(String playerName) {
|
public PlayerId(String playerName) {
|
||||||
checkState(Util.SDK_INT < 31);
|
|
||||||
this.name = playerName;
|
|
||||||
this.logSessionIdApi31 = null;
|
|
||||||
equalityToken = new Object();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an instance for API ≥ 31.
|
|
||||||
*
|
|
||||||
* @param logSessionId The {@link LogSessionId} used for this player.
|
|
||||||
* @param playerName The name of the player, for informational purpose only.
|
|
||||||
*/
|
|
||||||
@RequiresApi(31)
|
|
||||||
public PlayerId(LogSessionId logSessionId, String playerName) {
|
|
||||||
this(new LogSessionIdApi31(logSessionId), playerName);
|
|
||||||
}
|
|
||||||
|
|
||||||
private PlayerId(LogSessionIdApi31 logSessionIdApi31, String playerName) {
|
|
||||||
this.logSessionIdApi31 = logSessionIdApi31;
|
|
||||||
this.name = playerName;
|
this.name = playerName;
|
||||||
|
this.logSessionIdApi31 = Util.SDK_INT >= 31 ? new LogSessionIdApi31() : null;
|
||||||
equalityToken = new Object();
|
equalityToken = new Object();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,19 +80,31 @@ public final class PlayerId {
|
|||||||
|
|
||||||
/** Returns the {@link LogSessionId} for this player instance. */
|
/** Returns the {@link LogSessionId} for this player instance. */
|
||||||
@RequiresApi(31)
|
@RequiresApi(31)
|
||||||
public LogSessionId getLogSessionId() {
|
public synchronized LogSessionId getLogSessionId() {
|
||||||
return checkNotNull(logSessionIdApi31).logSessionId;
|
return checkNotNull(logSessionIdApi31).logSessionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the {@link LogSessionId} for this player instance.
|
||||||
|
*
|
||||||
|
* <p>Must not be called if already set.
|
||||||
|
*/
|
||||||
|
@RequiresApi(31)
|
||||||
|
public synchronized void setLogSessionId(LogSessionId logSessionId) {
|
||||||
|
checkNotNull(logSessionIdApi31).setLogSessionId(logSessionId);
|
||||||
|
}
|
||||||
|
|
||||||
@RequiresApi(31)
|
@RequiresApi(31)
|
||||||
private static final class LogSessionIdApi31 {
|
private static final class LogSessionIdApi31 {
|
||||||
|
|
||||||
public static final LogSessionIdApi31 UNSET =
|
public LogSessionId logSessionId;
|
||||||
new LogSessionIdApi31(LogSessionId.LOG_SESSION_ID_NONE);
|
|
||||||
|
|
||||||
public final LogSessionId logSessionId;
|
public LogSessionIdApi31() {
|
||||||
|
this.logSessionId = LogSessionId.LOG_SESSION_ID_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
public LogSessionIdApi31(LogSessionId logSessionId) {
|
public void setLogSessionId(LogSessionId logSessionId) {
|
||||||
|
checkState(this.logSessionId.equals(LogSessionId.LOG_SESSION_ID_NONE));
|
||||||
this.logSessionId = logSessionId;
|
this.logSessionId = logSessionId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,6 @@ package androidx.media3.exoplayer;
|
|||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
import android.media.metrics.LogSessionId;
|
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.Format;
|
import androidx.media3.common.Format;
|
||||||
import androidx.media3.common.MediaItem;
|
import androidx.media3.common.MediaItem;
|
||||||
@ -59,11 +58,7 @@ public class DefaultLoadControlTest {
|
|||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
builder = new Builder();
|
builder = new Builder();
|
||||||
allocator = new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE);
|
allocator = new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE);
|
||||||
playerId =
|
playerId = new PlayerId(/* playerName= */ "");
|
||||||
Util.SDK_INT < 31
|
|
||||||
? new PlayerId(/* playerName= */ "")
|
|
||||||
: new PlayerId(
|
|
||||||
/* logSessionId= */ LogSessionId.LOG_SESSION_ID_NONE, /* playerName= */ "");
|
|
||||||
timeline =
|
timeline =
|
||||||
new SinglePeriodTimeline(
|
new SinglePeriodTimeline(
|
||||||
/* durationUs= */ 10_000_000L,
|
/* durationUs= */ 10_000_000L,
|
||||||
@ -130,7 +125,7 @@ public class DefaultLoadControlTest {
|
|||||||
/* bufferForPlaybackAfterRebufferMs= */ 0);
|
/* bufferForPlaybackAfterRebufferMs= */ 0);
|
||||||
build();
|
build();
|
||||||
// A second player uses the load control.
|
// A second player uses the load control.
|
||||||
PlayerId playerId2 = new PlayerId(LogSessionId.LOG_SESSION_ID_NONE, /* playerName= */ "");
|
PlayerId playerId2 = new PlayerId(/* playerName= */ "");
|
||||||
Timeline timeline2 = new FakeTimeline();
|
Timeline timeline2 = new FakeTimeline();
|
||||||
MediaSource.MediaPeriodId mediaPeriodId2 =
|
MediaSource.MediaPeriodId mediaPeriodId2 =
|
||||||
new MediaSource.MediaPeriodId(
|
new MediaSource.MediaPeriodId(
|
||||||
@ -731,7 +726,7 @@ public class DefaultLoadControlTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void onPrepared_updatesTargetBufferBytes_correctDefaultTargetBufferSize() {
|
public void onPrepared_updatesTargetBufferBytes_correctDefaultTargetBufferSize() {
|
||||||
PlayerId playerId2 = new PlayerId(LogSessionId.LOG_SESSION_ID_NONE, /* playerName= */ "");
|
PlayerId playerId2 = new PlayerId(/* playerName= */ "");
|
||||||
loadControl = builder.setAllocator(allocator).build();
|
loadControl = builder.setAllocator(allocator).build();
|
||||||
|
|
||||||
loadControl.onPrepared(playerId);
|
loadControl.onPrepared(playerId);
|
||||||
@ -743,7 +738,7 @@ public class DefaultLoadControlTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void onTrackSelected_updatesTargetBufferBytes_correctTargetBufferSizeFromTrackType() {
|
public void onTrackSelected_updatesTargetBufferBytes_correctTargetBufferSizeFromTrackType() {
|
||||||
PlayerId playerId2 = new PlayerId(LogSessionId.LOG_SESSION_ID_NONE, /* playerName= */ "");
|
PlayerId playerId2 = new PlayerId(/* playerName= */ "");
|
||||||
loadControl = builder.setAllocator(allocator).build();
|
loadControl = builder.setAllocator(allocator).build();
|
||||||
loadControl.onPrepared(playerId);
|
loadControl.onPrepared(playerId);
|
||||||
loadControl.onPrepared(playerId2);
|
loadControl.onPrepared(playerId2);
|
||||||
@ -791,7 +786,7 @@ public class DefaultLoadControlTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void onRelease_removesLoadingStateOfPlayer() {
|
public void onRelease_removesLoadingStateOfPlayer() {
|
||||||
PlayerId playerId2 = new PlayerId(LogSessionId.LOG_SESSION_ID_NONE, /* playerName= */ "");
|
PlayerId playerId2 = new PlayerId(/* playerName= */ "");
|
||||||
loadControl = builder.setAllocator(allocator).build();
|
loadControl = builder.setAllocator(allocator).build();
|
||||||
loadControl.onPrepared(playerId);
|
loadControl.onPrepared(playerId);
|
||||||
loadControl.onPrepared(playerId2);
|
loadControl.onPrepared(playerId2);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user