Add analytics listener interface and default data collector.
The data collector keeps track of active media periods to assign each event to the correct media period and/or window. This information, together with other information like playback position and buffered duration, is then forwarded with the event to all registered listeners. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=191726408
This commit is contained in:
parent
6862771725
commit
f25c7a859a
@ -0,0 +1,782 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer2.analytics;
|
||||
|
||||
import android.net.NetworkInfo;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.Surface;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.PlaybackParameters;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.Timeline.Period;
|
||||
import com.google.android.exoplayer2.Timeline.Window;
|
||||
import com.google.android.exoplayer2.analytics.AnalyticsListener.EventTime;
|
||||
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
|
||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||
import com.google.android.exoplayer2.drm.DefaultDrmSessionEventListener;
|
||||
import com.google.android.exoplayer2.metadata.Metadata;
|
||||
import com.google.android.exoplayer2.metadata.MetadataOutput;
|
||||
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
|
||||
import com.google.android.exoplayer2.source.MediaSourceEventListener;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.source.ads.AdsMediaSource;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||
import com.google.android.exoplayer2.upstream.BandwidthMeter;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.Clock;
|
||||
import com.google.android.exoplayer2.video.VideoRendererEventListener;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
/**
|
||||
* Data collector which is able to forward analytics events to {@link AnalyticsListener}s by
|
||||
* listening to all available ExoPlayer listeners.
|
||||
*/
|
||||
public class AnalyticsCollector
|
||||
implements Player.EventListener,
|
||||
MetadataOutput,
|
||||
AudioRendererEventListener,
|
||||
VideoRendererEventListener,
|
||||
MediaSourceEventListener,
|
||||
BandwidthMeter.EventListener,
|
||||
AdsMediaSource.EventListener,
|
||||
DefaultDrmSessionEventListener {
|
||||
|
||||
/** Factory for an analytics collector. */
|
||||
public static class Factory {
|
||||
|
||||
/**
|
||||
* Creates an analytics collector for the specified player.
|
||||
*
|
||||
* @param player The {@link Player} for which data will be collected.
|
||||
* @param clock A {@link Clock} used to generate timestamps.
|
||||
* @return An analytics collector.
|
||||
*/
|
||||
public AnalyticsCollector createAnalyticsCollector(Player player, Clock clock) {
|
||||
return new AnalyticsCollector(player, clock);
|
||||
}
|
||||
}
|
||||
|
||||
private final CopyOnWriteArraySet<AnalyticsListener> listeners;
|
||||
private final Player player;
|
||||
private final Clock clock;
|
||||
private final Period period;
|
||||
private final Window window;
|
||||
private final MediaPeriodQueueTracker mediaPeriodQueueTracker;
|
||||
|
||||
/**
|
||||
* Creates an analytics collector for the specified player.
|
||||
*
|
||||
* @param player The {@link Player} for which data will be collected.
|
||||
* @param clock A {@link Clock} used to generate timestamps.
|
||||
*/
|
||||
protected AnalyticsCollector(Player player, Clock clock) {
|
||||
this.player = Assertions.checkNotNull(player);
|
||||
this.clock = Assertions.checkNotNull(clock);
|
||||
listeners = new CopyOnWriteArraySet<>();
|
||||
mediaPeriodQueueTracker = new MediaPeriodQueueTracker();
|
||||
period = new Period();
|
||||
window = new Window();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener for analytics events.
|
||||
*
|
||||
* @param listener The listener to add.
|
||||
*/
|
||||
public void addListener(AnalyticsListener listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a previously added analytics event listener.
|
||||
*
|
||||
* @param listener The listener to remove.
|
||||
*/
|
||||
public void removeListener(AnalyticsListener listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
// External events.
|
||||
|
||||
/**
|
||||
* Notify analytics collector that a seek operation will start. Should be called before the player
|
||||
* adjusts its state and position to the seek.
|
||||
*/
|
||||
public final void notifySeekStarted() {
|
||||
if (!mediaPeriodQueueTracker.isSeeking()) {
|
||||
EventTime eventTime = generatePlayingMediaPeriodEventTime();
|
||||
mediaPeriodQueueTracker.onSeekStarted();
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onSeekStarted(eventTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify analytics collector that the viewport size changed.
|
||||
*
|
||||
* @param width The new width of the viewport in device-independent pixels (dp).
|
||||
* @param height The new height of the viewport in device-independent pixels (dp).
|
||||
*/
|
||||
public final void notifyViewportSizeChanged(int width, int height) {
|
||||
EventTime eventTime = generatePlayingMediaPeriodEventTime();
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onViewportSizeChange(eventTime, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify analytics collector that the network type or connectivity changed.
|
||||
*
|
||||
* @param networkInfo The new network info, or null if no network connection exists.
|
||||
*/
|
||||
public final void notifyNetworkTypeChanged(@Nullable NetworkInfo networkInfo) {
|
||||
EventTime eventTime = generatePlayingMediaPeriodEventTime();
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onNetworkTypeChanged(eventTime, networkInfo);
|
||||
}
|
||||
}
|
||||
|
||||
// MetadataOutput implementation.
|
||||
|
||||
@Override
|
||||
public final void onMetadata(Metadata metadata) {
|
||||
EventTime eventTime = generatePlayingMediaPeriodEventTime();
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onMetadata(eventTime, metadata);
|
||||
}
|
||||
}
|
||||
|
||||
// AudioRendererEventListener implementation.
|
||||
|
||||
@Override
|
||||
public final void onAudioEnabled(DecoderCounters counters) {
|
||||
// The renderers are only enabled after we changed the playing media period.
|
||||
EventTime eventTime = generatePlayingMediaPeriodEventTime();
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onDecoderEnabled(eventTime, C.TRACK_TYPE_AUDIO, counters);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onAudioSessionId(int audioSessionId) {
|
||||
EventTime eventTime = generateReadingMediaPeriodEventTime();
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onAudioSessionId(eventTime, audioSessionId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onAudioDecoderInitialized(
|
||||
String decoderName, long initializedTimestampMs, long initializationDurationMs) {
|
||||
EventTime eventTime = generateReadingMediaPeriodEventTime();
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onDecoderInitialized(
|
||||
eventTime, C.TRACK_TYPE_AUDIO, decoderName, initializationDurationMs);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onAudioInputFormatChanged(Format format) {
|
||||
EventTime eventTime = generateReadingMediaPeriodEventTime();
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onDecoderInputFormatChanged(eventTime, C.TRACK_TYPE_AUDIO, format);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onAudioSinkUnderrun(
|
||||
int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
|
||||
EventTime eventTime = generateReadingMediaPeriodEventTime();
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onAudioUnderrun(eventTime, bufferSize, bufferSizeMs, elapsedSinceLastFeedMs);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onAudioDisabled(DecoderCounters counters) {
|
||||
// The renderers are disabled after we changed the playing media period on the playback thread
|
||||
// but before this change is reported to the app thread.
|
||||
EventTime eventTime = generateLastReportedPlayingMediaPeriodEventTime();
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onDecoderDisabled(eventTime, C.TRACK_TYPE_AUDIO, counters);
|
||||
}
|
||||
}
|
||||
|
||||
// VideoRendererEventListener implementation.
|
||||
|
||||
@Override
|
||||
public final void onVideoEnabled(DecoderCounters counters) {
|
||||
// The renderers are only enabled after we changed the playing media period.
|
||||
EventTime eventTime = generatePlayingMediaPeriodEventTime();
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onDecoderEnabled(eventTime, C.TRACK_TYPE_VIDEO, counters);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onVideoDecoderInitialized(
|
||||
String decoderName, long initializedTimestampMs, long initializationDurationMs) {
|
||||
EventTime eventTime = generateReadingMediaPeriodEventTime();
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onDecoderInitialized(
|
||||
eventTime, C.TRACK_TYPE_VIDEO, decoderName, initializationDurationMs);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onVideoInputFormatChanged(Format format) {
|
||||
EventTime eventTime = generateReadingMediaPeriodEventTime();
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onDecoderInputFormatChanged(eventTime, C.TRACK_TYPE_VIDEO, format);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onDroppedFrames(int count, long elapsedMs) {
|
||||
EventTime eventTime = generateLastReportedPlayingMediaPeriodEventTime();
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onDroppedVideoFrames(eventTime, count, elapsedMs);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onVideoSizeChanged(
|
||||
int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {
|
||||
EventTime eventTime = generateReadingMediaPeriodEventTime();
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onVideoSizeChanged(
|
||||
eventTime, width, height, unappliedRotationDegrees, pixelWidthHeightRatio);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onRenderedFirstFrame(Surface surface) {
|
||||
EventTime eventTime = generateReadingMediaPeriodEventTime();
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onRenderedFirstFrame(eventTime, surface);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onVideoDisabled(DecoderCounters counters) {
|
||||
// The renderers are disabled after we changed the playing media period on the playback thread
|
||||
// but before this change is reported to the app thread.
|
||||
EventTime eventTime = generateLastReportedPlayingMediaPeriodEventTime();
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onDecoderDisabled(eventTime, C.TRACK_TYPE_VIDEO, counters);
|
||||
}
|
||||
}
|
||||
|
||||
// MediaSourceEventListener implementation.
|
||||
|
||||
@Override
|
||||
public final void onMediaPeriodCreated(int windowIndex, MediaPeriodId mediaPeriodId) {
|
||||
mediaPeriodQueueTracker.onMediaPeriodCreated(mediaPeriodId);
|
||||
EventTime eventTime = generateEventTime(windowIndex, mediaPeriodId);
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onMediaPeriodCreated(eventTime);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onMediaPeriodReleased(int windowIndex, MediaPeriodId mediaPeriodId) {
|
||||
mediaPeriodQueueTracker.onMediaPeriodReleased(mediaPeriodId);
|
||||
EventTime eventTime = generateEventTime(windowIndex, mediaPeriodId);
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onMediaPeriodReleased(eventTime);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onLoadStarted(
|
||||
int windowIndex,
|
||||
@Nullable MediaPeriodId mediaPeriodId,
|
||||
LoadEventInfo loadEventInfo,
|
||||
MediaLoadData mediaLoadData) {
|
||||
EventTime eventTime = generateEventTime(windowIndex, mediaPeriodId);
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onLoadStarted(eventTime, loadEventInfo, mediaLoadData);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onLoadCompleted(
|
||||
int windowIndex,
|
||||
@Nullable MediaPeriodId mediaPeriodId,
|
||||
LoadEventInfo loadEventInfo,
|
||||
MediaLoadData mediaLoadData) {
|
||||
EventTime eventTime = generateEventTime(windowIndex, mediaPeriodId);
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onLoadCompleted(eventTime, loadEventInfo, mediaLoadData);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onLoadCanceled(
|
||||
int windowIndex,
|
||||
@Nullable MediaPeriodId mediaPeriodId,
|
||||
LoadEventInfo loadEventInfo,
|
||||
MediaLoadData mediaLoadData) {
|
||||
EventTime eventTime = generateEventTime(windowIndex, mediaPeriodId);
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onLoadCanceled(eventTime, loadEventInfo, mediaLoadData);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onLoadError(
|
||||
int windowIndex,
|
||||
@Nullable MediaPeriodId mediaPeriodId,
|
||||
LoadEventInfo loadEventInfo,
|
||||
MediaLoadData mediaLoadData,
|
||||
IOException error,
|
||||
boolean wasCanceled) {
|
||||
EventTime eventTime = generateEventTime(windowIndex, mediaPeriodId);
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onLoadError(eventTime, loadEventInfo, mediaLoadData, error, wasCanceled);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onReadingStarted(int windowIndex, MediaPeriodId mediaPeriodId) {
|
||||
mediaPeriodQueueTracker.onReadingStarted(mediaPeriodId);
|
||||
EventTime eventTime = generateEventTime(windowIndex, mediaPeriodId);
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onReadingStarted(eventTime);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onUpstreamDiscarded(
|
||||
int windowIndex, @Nullable MediaPeriodId mediaPeriodId, MediaLoadData mediaLoadData) {
|
||||
EventTime eventTime = generateEventTime(windowIndex, mediaPeriodId);
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onUpstreamDiscarded(eventTime, mediaLoadData);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onDownstreamFormatChanged(
|
||||
int windowIndex, @Nullable MediaPeriodId mediaPeriodId, MediaLoadData mediaLoadData) {
|
||||
EventTime eventTime = generateEventTime(windowIndex, mediaPeriodId);
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onDownstreamFormatChanged(eventTime, mediaLoadData);
|
||||
}
|
||||
}
|
||||
|
||||
// Player.EventListener implementation.
|
||||
|
||||
// TODO: Add onFinishedReportingChanges to Player.EventListener to know when a set of simultaneous
|
||||
// callbacks finished. This helps to assign exactly the same EventTime to all of them instead of
|
||||
// having slightly different real times.
|
||||
|
||||
@Override
|
||||
public final void onTimelineChanged(
|
||||
Timeline timeline, Object manifest, @Player.TimelineChangeReason int reason) {
|
||||
mediaPeriodQueueTracker.onTimelineChanged(timeline);
|
||||
EventTime eventTime = generatePlayingMediaPeriodEventTime();
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onTimelineChanged(eventTime, reason);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onTracksChanged(
|
||||
TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
|
||||
EventTime eventTime = generatePlayingMediaPeriodEventTime();
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onTracksChanged(eventTime, trackGroups, trackSelections);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onLoadingChanged(boolean isLoading) {
|
||||
EventTime eventTime = generatePlayingMediaPeriodEventTime();
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onLoadingChanged(eventTime, isLoading);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
|
||||
EventTime eventTime = generatePlayingMediaPeriodEventTime();
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onPlayerStateChanged(eventTime, playWhenReady, playbackState);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onRepeatModeChanged(@Player.RepeatMode int repeatMode) {
|
||||
EventTime eventTime = generatePlayingMediaPeriodEventTime();
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onRepeatModeChanged(eventTime, repeatMode);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {
|
||||
EventTime eventTime = generatePlayingMediaPeriodEventTime();
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onShuffleModeChanged(eventTime, shuffleModeEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onPlayerError(ExoPlaybackException error) {
|
||||
EventTime eventTime = generatePlayingMediaPeriodEventTime();
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onPlayerError(eventTime, error);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onPositionDiscontinuity(@Player.DiscontinuityReason int reason) {
|
||||
mediaPeriodQueueTracker.onPositionDiscontinuity(reason);
|
||||
EventTime eventTime = generatePlayingMediaPeriodEventTime();
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onPositionDiscontinuity(eventTime, reason);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
|
||||
EventTime eventTime = generatePlayingMediaPeriodEventTime();
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onPlaybackParametersChanged(eventTime, playbackParameters);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onSeekProcessed() {
|
||||
if (mediaPeriodQueueTracker.isSeeking()) {
|
||||
mediaPeriodQueueTracker.onSeekProcessed();
|
||||
EventTime eventTime = generatePlayingMediaPeriodEventTime();
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onSeekProcessed(eventTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BandwidthMeter.Listener implementation.
|
||||
|
||||
@Override
|
||||
public final void onBandwidthSample(int elapsedMs, long bytes, long bitrate) {
|
||||
EventTime eventTime = generateLoadingMediaPeriodEventTime();
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onBandwidthEstimate(eventTime, elapsedMs, bytes, bitrate);
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultDrmSessionManager.EventListener implementation.
|
||||
|
||||
@Override
|
||||
public final void onDrmKeysLoaded() {
|
||||
EventTime eventTime = generateReadingMediaPeriodEventTime();
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onDrmKeysLoaded(eventTime);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onDrmSessionManagerError(Exception error) {
|
||||
EventTime eventTime = generateReadingMediaPeriodEventTime();
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onDrmSessionManagerError(eventTime, error);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onDrmKeysRestored() {
|
||||
EventTime eventTime = generateReadingMediaPeriodEventTime();
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onDrmKeysRestored(eventTime);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onDrmKeysRemoved() {
|
||||
EventTime eventTime = generateReadingMediaPeriodEventTime();
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onDrmKeysRemoved(eventTime);
|
||||
}
|
||||
}
|
||||
|
||||
// AdsMediaSource.EventListener implementation.
|
||||
|
||||
@Override
|
||||
public final void onAdLoadError(IOException error) {
|
||||
EventTime eventTime = generatePlayingMediaPeriodEventTime();
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onAdLoadError(eventTime, error);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onInternalAdLoadError(RuntimeException error) {
|
||||
EventTime eventTime = generatePlayingMediaPeriodEventTime();
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onInternalAdLoadError(eventTime, error);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onAdClicked() {
|
||||
EventTime eventTime = generatePlayingMediaPeriodEventTime();
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onAdClicked(eventTime);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void onAdTapped() {
|
||||
EventTime eventTime = generatePlayingMediaPeriodEventTime();
|
||||
for (AnalyticsListener listener : listeners) {
|
||||
listener.onAdTapped(eventTime);
|
||||
}
|
||||
}
|
||||
|
||||
// Internal methods.
|
||||
|
||||
/** Returns read-only set of registered listeners. */
|
||||
protected Set<AnalyticsListener> getListeners() {
|
||||
return Collections.unmodifiableSet(listeners);
|
||||
}
|
||||
|
||||
/** Returns a new {@link EventTime} for the specified window index and media period id. */
|
||||
protected EventTime generateEventTime(int windowIndex, @Nullable MediaPeriodId mediaPeriodId) {
|
||||
long realtimeMs = clock.elapsedRealtime();
|
||||
Timeline timeline = player.getCurrentTimeline();
|
||||
long eventPositionMs;
|
||||
if (windowIndex == player.getCurrentWindowIndex()) {
|
||||
if (mediaPeriodId != null && mediaPeriodId.isAd()) {
|
||||
// This event is for an ad in the currently playing window.
|
||||
eventPositionMs =
|
||||
player.getCurrentAdGroupIndex() == mediaPeriodId.adGroupIndex
|
||||
&& player.getCurrentAdIndexInAdGroup() == mediaPeriodId.adIndexInAdGroup
|
||||
? player.getCurrentPosition()
|
||||
: 0 /* Assume start position of 0 for a future ad. */;
|
||||
} else {
|
||||
// This event is for content in the currently playing window.
|
||||
eventPositionMs = player.getContentPosition();
|
||||
}
|
||||
} else if (timeline.isEmpty() || (mediaPeriodId != null && mediaPeriodId.isAd())) {
|
||||
// This event is for an unknown future window or for an ad in a future window.
|
||||
// Assume start position of zero.
|
||||
eventPositionMs = 0;
|
||||
} else {
|
||||
// This event is for content in a future window. Assume default start position.
|
||||
eventPositionMs = timeline.getWindow(windowIndex, window).getDefaultPositionMs();
|
||||
}
|
||||
// TODO(b/30792113): implement this properly (player.getTotalBufferedDuration()).
|
||||
long bufferedDurationMs = player.getBufferedPosition() - player.getContentPosition();
|
||||
return new EventTime(
|
||||
realtimeMs,
|
||||
timeline,
|
||||
windowIndex,
|
||||
mediaPeriodId,
|
||||
eventPositionMs,
|
||||
player.getCurrentPosition(),
|
||||
bufferedDurationMs);
|
||||
}
|
||||
|
||||
private EventTime generateEventTime(@Nullable MediaPeriodId mediaPeriodId) {
|
||||
Timeline timeline = player.getCurrentTimeline();
|
||||
if (mediaPeriodId == null) {
|
||||
mediaPeriodId = mediaPeriodQueueTracker.tryResolveWindowIndex(player.getCurrentWindowIndex());
|
||||
}
|
||||
int windowIndex =
|
||||
mediaPeriodId == null || timeline.isEmpty()
|
||||
? player.getCurrentWindowIndex()
|
||||
: timeline.getPeriod(mediaPeriodId.periodIndex, period).windowIndex;
|
||||
return generateEventTime(windowIndex, mediaPeriodId);
|
||||
}
|
||||
|
||||
private EventTime generateLastReportedPlayingMediaPeriodEventTime() {
|
||||
return generateEventTime(mediaPeriodQueueTracker.getLastReportedPlayingMediaPeriod());
|
||||
}
|
||||
|
||||
private EventTime generatePlayingMediaPeriodEventTime() {
|
||||
return generateEventTime(mediaPeriodQueueTracker.getPlayingMediaPeriod());
|
||||
}
|
||||
|
||||
private EventTime generateReadingMediaPeriodEventTime() {
|
||||
return generateEventTime(mediaPeriodQueueTracker.getReadingMediaPeriod());
|
||||
}
|
||||
|
||||
private EventTime generateLoadingMediaPeriodEventTime() {
|
||||
return generateEventTime(mediaPeriodQueueTracker.getLoadingMediaPeriod());
|
||||
}
|
||||
|
||||
/** Keeps track of the active media periods and currently playing and reading media period. */
|
||||
private static final class MediaPeriodQueueTracker {
|
||||
|
||||
private final ArrayList<MediaPeriodId> activeMediaPeriods;
|
||||
private final Period period;
|
||||
|
||||
private MediaPeriodId lastReportedPlayingMediaPeriod;
|
||||
private MediaPeriodId readingMediaPeriod;
|
||||
private Timeline timeline;
|
||||
private boolean isSeeking;
|
||||
|
||||
public MediaPeriodQueueTracker() {
|
||||
activeMediaPeriods = new ArrayList<>();
|
||||
period = new Period();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link MediaPeriodId} of the media period in the front of the queue. This is the
|
||||
* playing media period unless the player hasn't started playing yet (in which case it is the
|
||||
* loading media period or null). While the player is seeking or preparing, this method will
|
||||
* always return null to reflect the uncertainty about the current playing period. May also be
|
||||
* null, if the timeline is empty or no media period is active yet.
|
||||
*/
|
||||
public @Nullable MediaPeriodId getPlayingMediaPeriod() {
|
||||
return activeMediaPeriods.isEmpty() || timeline.isEmpty() || isSeeking
|
||||
? null
|
||||
: activeMediaPeriods.get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link MediaPeriodId} of the currently playing media period. This is the publicly
|
||||
* reported period which should always match {@link Player#getCurrentPeriodIndex()} unless the
|
||||
* player is currently seeking or being prepared in which case the previous period is reported
|
||||
* until the seek or preparation is processed. May be null, if no media period is active yet.
|
||||
*/
|
||||
public @Nullable MediaPeriodId getLastReportedPlayingMediaPeriod() {
|
||||
return lastReportedPlayingMediaPeriod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link MediaPeriodId} of the media period currently being read by the player. May
|
||||
* be null, if the player is not reading a media period.
|
||||
*/
|
||||
public @Nullable MediaPeriodId getReadingMediaPeriod() {
|
||||
return readingMediaPeriod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link MediaPeriodId} of the media period at the end of the queue which is
|
||||
* currently loading or will be the next one loading. May be null, if no media period is active
|
||||
* yet.
|
||||
*/
|
||||
public @Nullable MediaPeriodId getLoadingMediaPeriod() {
|
||||
return activeMediaPeriods.isEmpty()
|
||||
? null
|
||||
: activeMediaPeriods.get(activeMediaPeriods.size() - 1);
|
||||
}
|
||||
|
||||
/** Returns whether the player is currently seeking. */
|
||||
public boolean isSeeking() {
|
||||
return isSeeking;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to find an existing media period from the specified window index. Only returns a
|
||||
* non-null media period id if there is a unique, unambiguous match.
|
||||
*/
|
||||
public @Nullable MediaPeriodId tryResolveWindowIndex(int windowIndex) {
|
||||
MediaPeriodId match = null;
|
||||
if (timeline != null && !timeline.isEmpty()) {
|
||||
for (int i = 0; i < activeMediaPeriods.size(); i++) {
|
||||
MediaPeriodId mediaPeriodId = activeMediaPeriods.get(i);
|
||||
if (timeline.getPeriod(mediaPeriodId.periodIndex, period).windowIndex == windowIndex) {
|
||||
if (match != null) {
|
||||
// Ambiguous match.
|
||||
return null;
|
||||
}
|
||||
match = mediaPeriodId;
|
||||
}
|
||||
}
|
||||
}
|
||||
return match;
|
||||
}
|
||||
|
||||
/** Updates the queue with a reported position discontinuity . */
|
||||
public void onPositionDiscontinuity(@Player.DiscontinuityReason int reason) {
|
||||
updateLastReportedPlayingMediaPeriod();
|
||||
}
|
||||
|
||||
/** Updates the queue with a reported timeline change. */
|
||||
public void onTimelineChanged(Timeline timeline) {
|
||||
for (int i = 0; i < activeMediaPeriods.size(); i++) {
|
||||
activeMediaPeriods.set(
|
||||
i, updateMediaPeriodIdToNewTimeline(activeMediaPeriods.get(i), timeline));
|
||||
}
|
||||
if (readingMediaPeriod != null) {
|
||||
readingMediaPeriod = updateMediaPeriodIdToNewTimeline(readingMediaPeriod, timeline);
|
||||
}
|
||||
this.timeline = timeline;
|
||||
updateLastReportedPlayingMediaPeriod();
|
||||
}
|
||||
|
||||
/** Updates the queue with a reported start of seek. */
|
||||
public void onSeekStarted() {
|
||||
isSeeking = true;
|
||||
}
|
||||
|
||||
/** Updates the queue with a reported processed seek. */
|
||||
public void onSeekProcessed() {
|
||||
isSeeking = false;
|
||||
updateLastReportedPlayingMediaPeriod();
|
||||
}
|
||||
|
||||
/** Updates the queue with a newly created media period. */
|
||||
public void onMediaPeriodCreated(MediaPeriodId mediaPeriodId) {
|
||||
activeMediaPeriods.add(mediaPeriodId);
|
||||
if (activeMediaPeriods.size() == 1 && !timeline.isEmpty()) {
|
||||
updateLastReportedPlayingMediaPeriod();
|
||||
}
|
||||
}
|
||||
|
||||
/** Updates the queue with a released media period. */
|
||||
public void onMediaPeriodReleased(MediaPeriodId mediaPeriodId) {
|
||||
activeMediaPeriods.remove(mediaPeriodId);
|
||||
if (mediaPeriodId.equals(readingMediaPeriod)) {
|
||||
readingMediaPeriod = activeMediaPeriods.isEmpty() ? null : activeMediaPeriods.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
/** Update the queue with a change in the reading media period. */
|
||||
public void onReadingStarted(MediaPeriodId mediaPeriodId) {
|
||||
readingMediaPeriod = mediaPeriodId;
|
||||
}
|
||||
|
||||
private void updateLastReportedPlayingMediaPeriod() {
|
||||
lastReportedPlayingMediaPeriod =
|
||||
activeMediaPeriods.isEmpty() ? null : activeMediaPeriods.get(0);
|
||||
}
|
||||
|
||||
private MediaPeriodId updateMediaPeriodIdToNewTimeline(
|
||||
MediaPeriodId mediaPeriodId, Timeline newTimeline) {
|
||||
if (newTimeline.isEmpty()) {
|
||||
return mediaPeriodId;
|
||||
}
|
||||
Object uid = timeline.getPeriod(mediaPeriodId.periodIndex, period, /* setIds= */ true).uid;
|
||||
int newIndex = newTimeline.getIndexOfPeriod(uid);
|
||||
return newIndex == C.INDEX_UNSET
|
||||
? mediaPeriodId
|
||||
: mediaPeriodId.copyWithPeriodIndex(newIndex);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,497 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer2.analytics;
|
||||
|
||||
import android.net.NetworkInfo;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.Surface;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.PlaybackParameters;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.Player.DiscontinuityReason;
|
||||
import com.google.android.exoplayer2.Player.TimelineChangeReason;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.audio.AudioSink;
|
||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||
import com.google.android.exoplayer2.metadata.Metadata;
|
||||
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
|
||||
import com.google.android.exoplayer2.source.MediaSourceEventListener.LoadEventInfo;
|
||||
import com.google.android.exoplayer2.source.MediaSourceEventListener.MediaLoadData;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* A listener for analytics events.
|
||||
*
|
||||
* <p>All events are recorded with an {@link EventTime} specifying the elapsed real time and media
|
||||
* time at the time of the event.
|
||||
*/
|
||||
public interface AnalyticsListener {
|
||||
|
||||
/** Time information of an event. */
|
||||
final class EventTime {
|
||||
|
||||
/**
|
||||
* Elapsed real-time as returned by {@code SystemClock.elapsedRealtime()} at the time of the
|
||||
* event, in milliseconds.
|
||||
*/
|
||||
public final long realtimeMs;
|
||||
|
||||
/** Timeline at the time of the event. */
|
||||
public final Timeline timeline;
|
||||
|
||||
/**
|
||||
* Window index in the {@code timeline} this event belongs to, or the prospective window index
|
||||
* if the timeline is not yet known and empty.
|
||||
*/
|
||||
public final int windowIndex;
|
||||
|
||||
/**
|
||||
* Media period identifier for the media period this event belongs to, or {@code null} if the
|
||||
* event is not associated with a specific media period.
|
||||
*/
|
||||
public final @Nullable MediaPeriodId mediaPeriodId;
|
||||
|
||||
/**
|
||||
* Position in the window or ad this event belongs to at the time of the event, in milliseconds.
|
||||
*/
|
||||
public final long eventPlaybackPositionMs;
|
||||
|
||||
/**
|
||||
* Position in the current timeline window ({@code timeline.getCurrentWindowIndex()} or the
|
||||
* currently playing ad at the time of the event, in milliseconds.
|
||||
*/
|
||||
public final long currentPlaybackPositionMs;
|
||||
|
||||
/**
|
||||
* Total buffered duration from {@link #currentPlaybackPositionMs} at the time of the event, in
|
||||
* milliseconds. This includes pre-buffered data for subsequent ads and windows.
|
||||
*/
|
||||
public final long totalBufferedDurationMs;
|
||||
|
||||
/**
|
||||
* @param realtimeMs Elapsed real-time as returned by {@code SystemClock.elapsedRealtime()} at
|
||||
* the time of the event, in milliseconds.
|
||||
* @param timeline Timeline at the time of the event.
|
||||
* @param windowIndex Window index in the {@code timeline} this event belongs to, or the
|
||||
* prospective window index if the timeline is not yet known and empty.
|
||||
* @param mediaPeriodId Media period identifier for the media period this event belongs to, or
|
||||
* {@code null} if the event is not associated with a specific media period.
|
||||
* @param eventPlaybackPositionMs Position in the window or ad this event belongs to at the time
|
||||
* of the event, in milliseconds.
|
||||
* @param currentPlaybackPositionMs Position in the current timeline window ({@code
|
||||
* timeline.getCurrentWindowIndex()} or the currently playing ad at the time of the event,
|
||||
* in milliseconds.
|
||||
* @param totalBufferedDurationMs Total buffered duration from {@link
|
||||
* #currentPlaybackPositionMs} at the time of the event, in milliseconds. This includes
|
||||
* pre-buffered data for subsequent ads and windows.
|
||||
*/
|
||||
public EventTime(
|
||||
long realtimeMs,
|
||||
Timeline timeline,
|
||||
int windowIndex,
|
||||
@Nullable MediaPeriodId mediaPeriodId,
|
||||
long eventPlaybackPositionMs,
|
||||
long currentPlaybackPositionMs,
|
||||
long totalBufferedDurationMs) {
|
||||
this.realtimeMs = realtimeMs;
|
||||
this.timeline = timeline;
|
||||
this.windowIndex = windowIndex;
|
||||
this.mediaPeriodId = mediaPeriodId;
|
||||
this.eventPlaybackPositionMs = eventPlaybackPositionMs;
|
||||
this.currentPlaybackPositionMs = currentPlaybackPositionMs;
|
||||
this.totalBufferedDurationMs = totalBufferedDurationMs;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the player state changed.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
* @param playWhenReady Whether the playback will proceed when ready.
|
||||
* @param playbackState One of the {@link Player}.STATE constants.
|
||||
*/
|
||||
void onPlayerStateChanged(EventTime eventTime, boolean playWhenReady, int playbackState);
|
||||
|
||||
/**
|
||||
* Called when the timeline changed.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
* @param reason The reason for the timeline change.
|
||||
*/
|
||||
void onTimelineChanged(EventTime eventTime, @TimelineChangeReason int reason);
|
||||
|
||||
/**
|
||||
* Called when a position discontinuity occurred.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
* @param reason The reason for the position discontinuity.
|
||||
*/
|
||||
void onPositionDiscontinuity(EventTime eventTime, @DiscontinuityReason int reason);
|
||||
|
||||
/**
|
||||
* Called when a seek operation started.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
*/
|
||||
void onSeekStarted(EventTime eventTime);
|
||||
|
||||
/**
|
||||
* Called when a seek operation was processed.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
*/
|
||||
void onSeekProcessed(EventTime eventTime);
|
||||
|
||||
/**
|
||||
* Called when the playback parameters changed.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
* @param playbackParameters The new playback parameters.
|
||||
*/
|
||||
void onPlaybackParametersChanged(EventTime eventTime, PlaybackParameters playbackParameters);
|
||||
|
||||
/**
|
||||
* Called when the repeat mode changed.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
* @param repeatMode The new repeat mode.
|
||||
*/
|
||||
void onRepeatModeChanged(EventTime eventTime, @Player.RepeatMode int repeatMode);
|
||||
|
||||
/**
|
||||
* Called when the shuffle mode changed.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
* @param shuffleModeEnabled Whether the shuffle mode is enabled.
|
||||
*/
|
||||
void onShuffleModeChanged(EventTime eventTime, boolean shuffleModeEnabled);
|
||||
|
||||
/**
|
||||
* Called when the player starts or stops loading data from a source.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
* @param isLoading Whether the player is loading.
|
||||
*/
|
||||
void onLoadingChanged(EventTime eventTime, boolean isLoading);
|
||||
|
||||
/**
|
||||
* Called when a fatal player error occurred.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
* @param error The error.
|
||||
*/
|
||||
void onPlayerError(EventTime eventTime, ExoPlaybackException error);
|
||||
|
||||
/**
|
||||
* Called when the available or selected tracks for the renderers changed.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
* @param trackGroups The available tracks. May be empty.
|
||||
* @param trackSelections The track selections for each renderer. May contain null elements.
|
||||
*/
|
||||
void onTracksChanged(
|
||||
EventTime eventTime, TrackGroupArray trackGroups, TrackSelectionArray trackSelections);
|
||||
|
||||
/**
|
||||
* Called when a media source started loading data.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
* @param loadEventInfo The {@link LoadEventInfo} defining the load event.
|
||||
* @param mediaLoadData The {@link MediaLoadData} defining the data being loaded.
|
||||
*/
|
||||
void onLoadStarted(EventTime eventTime, LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData);
|
||||
|
||||
/**
|
||||
* Called when a media source completed loading data.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
* @param loadEventInfo The {@link LoadEventInfo} defining the load event.
|
||||
* @param mediaLoadData The {@link MediaLoadData} defining the data being loaded.
|
||||
*/
|
||||
void onLoadCompleted(
|
||||
EventTime eventTime, LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData);
|
||||
|
||||
/**
|
||||
* Called when a media source canceled loading data.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
* @param loadEventInfo The {@link LoadEventInfo} defining the load event.
|
||||
* @param mediaLoadData The {@link MediaLoadData} defining the data being loaded.
|
||||
*/
|
||||
void onLoadCanceled(
|
||||
EventTime eventTime, LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData);
|
||||
|
||||
/**
|
||||
* Called when a media source loading error occurred. These errors are just for informational
|
||||
* purposes and the player may recover.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
* @param loadEventInfo The {@link LoadEventInfo} defining the load event.
|
||||
* @param mediaLoadData The {@link MediaLoadData} defining the data being loaded.
|
||||
* @param error The load error.
|
||||
* @param wasCanceled Whether the load was canceled as a result of the error.
|
||||
*/
|
||||
void onLoadError(
|
||||
EventTime eventTime,
|
||||
LoadEventInfo loadEventInfo,
|
||||
MediaLoadData mediaLoadData,
|
||||
IOException error,
|
||||
boolean wasCanceled);
|
||||
|
||||
/**
|
||||
* Called when the downstream format sent to the renderers changed.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
* @param mediaLoadData The {@link MediaLoadData} defining the newly selected media data.
|
||||
*/
|
||||
void onDownstreamFormatChanged(EventTime eventTime, MediaLoadData mediaLoadData);
|
||||
|
||||
/**
|
||||
* Called when data is removed from the back of a media buffer, typically so that it can be
|
||||
* re-buffered in a different format.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
* @param mediaLoadData The {@link MediaLoadData} defining the media being discarded.
|
||||
*/
|
||||
void onUpstreamDiscarded(EventTime eventTime, MediaLoadData mediaLoadData);
|
||||
|
||||
/**
|
||||
* Called when a media source created a media period.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
*/
|
||||
void onMediaPeriodCreated(EventTime eventTime);
|
||||
|
||||
/**
|
||||
* Called when a media source released a media period.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
*/
|
||||
void onMediaPeriodReleased(EventTime eventTime);
|
||||
|
||||
/**
|
||||
* Called when the player started reading a media period.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
*/
|
||||
void onReadingStarted(EventTime eventTime);
|
||||
|
||||
/**
|
||||
* Called when the bandwidth estimate for the current data source has been updated.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
* @param totalLoadTimeMs The total time spend loading this update is based on, in milliseconds.
|
||||
* @param totalBytesLoaded The total bytes loaded this update is based on.
|
||||
* @param bitrateEstimate The bandwidth estimate, in bits per second.
|
||||
*/
|
||||
void onBandwidthEstimate(
|
||||
EventTime eventTime, int totalLoadTimeMs, long totalBytesLoaded, long bitrateEstimate);
|
||||
|
||||
/**
|
||||
* Called when the viewport size of the output surface changed.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
* @param width The width of the viewport in device-independent pixels (dp).
|
||||
* @param height The height of the viewport in device-independent pixels (dp).
|
||||
*/
|
||||
void onViewportSizeChange(EventTime eventTime, int width, int height);
|
||||
|
||||
/**
|
||||
* Called when the type of the network connection changed.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
* @param networkInfo The network info for the current connection, or null if disconnected.
|
||||
*/
|
||||
void onNetworkTypeChanged(EventTime eventTime, @Nullable NetworkInfo networkInfo);
|
||||
|
||||
/**
|
||||
* Called when there is {@link Metadata} associated with the current playback time.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
* @param metadata The metadata.
|
||||
*/
|
||||
void onMetadata(EventTime eventTime, Metadata metadata);
|
||||
|
||||
/**
|
||||
* Called when an audio or video decoder has been enabled.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
* @param trackType The track type of the enabled decoder. Either {@link C#TRACK_TYPE_AUDIO} or
|
||||
* {@link C#TRACK_TYPE_VIDEO}.
|
||||
* @param decoderCounters The accumulated event counters associated with this decoder.
|
||||
*/
|
||||
void onDecoderEnabled(EventTime eventTime, int trackType, DecoderCounters decoderCounters);
|
||||
|
||||
/**
|
||||
* Called when an audio or video decoder has been initialized.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
* @param trackType The track type of the initialized decoder. Either {@link C#TRACK_TYPE_AUDIO}
|
||||
* or {@link C#TRACK_TYPE_VIDEO}.
|
||||
* @param decoderName The decoder that was created.
|
||||
* @param initializationDurationMs Time taken to initialize the decoder, in milliseconds.
|
||||
*/
|
||||
void onDecoderInitialized(
|
||||
EventTime eventTime, int trackType, String decoderName, long initializationDurationMs);
|
||||
|
||||
/**
|
||||
* Called when an audio or video decoder input format changed.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
* @param trackType The track type of the decoder whose format changed. Either {@link
|
||||
* C#TRACK_TYPE_AUDIO} or {@link C#TRACK_TYPE_VIDEO}.
|
||||
* @param format The new input format for the decoder.
|
||||
*/
|
||||
void onDecoderInputFormatChanged(EventTime eventTime, int trackType, Format format);
|
||||
|
||||
/**
|
||||
* Called when an audio or video decoder has been disabled.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
* @param trackType The track type of the disabled decoder. Either {@link C#TRACK_TYPE_AUDIO} or
|
||||
* {@link C#TRACK_TYPE_VIDEO}.
|
||||
* @param decoderCounters The accumulated event counters associated with this decoder.
|
||||
*/
|
||||
void onDecoderDisabled(EventTime eventTime, int trackType, DecoderCounters decoderCounters);
|
||||
|
||||
/**
|
||||
* Called when the audio session id is set.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
* @param audioSessionId The audio session id.
|
||||
*/
|
||||
void onAudioSessionId(EventTime eventTime, int audioSessionId);
|
||||
|
||||
/**
|
||||
* Called when an audio underrun occurred.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
* @param bufferSize The size of the {@link AudioSink}'s buffer, in bytes.
|
||||
* @param bufferSizeMs The size of the {@link AudioSink}'s buffer, in milliseconds, if it is
|
||||
* configured for PCM output. {@link C#TIME_UNSET} if it is configured for passthrough output,
|
||||
* as the buffered media can have a variable bitrate so the duration may be unknown.
|
||||
* @param elapsedSinceLastFeedMs The time since the {@link AudioSink} was last fed data.
|
||||
*/
|
||||
void onAudioUnderrun(
|
||||
EventTime eventTime, int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs);
|
||||
|
||||
/**
|
||||
* Called after video frames have been dropped.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
* @param droppedFrames The number of dropped frames since the last call to this method.
|
||||
* @param elapsedMs The duration in milliseconds over which the frames were dropped. This duration
|
||||
* is timed from when the renderer was started or from when dropped frames were last reported
|
||||
* (whichever was more recent), and not from when the first of the reported drops occurred.
|
||||
*/
|
||||
void onDroppedVideoFrames(EventTime eventTime, int droppedFrames, long elapsedMs);
|
||||
|
||||
/**
|
||||
* Called before a frame is rendered for the first time since setting the surface, and each time
|
||||
* there's a change in the size or pixel aspect ratio of the video being rendered.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
* @param width The width of the video.
|
||||
* @param height The height of the video.
|
||||
* @param unappliedRotationDegrees For videos that require a rotation, this is the clockwise
|
||||
* rotation in degrees that the application should apply for the video for it to be rendered
|
||||
* in the correct orientation. This value will always be zero on API levels 21 and above,
|
||||
* since the renderer will apply all necessary rotations internally.
|
||||
* @param pixelWidthHeightRatio The width to height ratio of each pixel.
|
||||
*/
|
||||
void onVideoSizeChanged(
|
||||
EventTime eventTime,
|
||||
int width,
|
||||
int height,
|
||||
int unappliedRotationDegrees,
|
||||
float pixelWidthHeightRatio);
|
||||
|
||||
/**
|
||||
* Called when a frame is rendered for the first time since setting the surface, and when a frame
|
||||
* is rendered for the first time since the renderer was reset.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
* @param surface The {@link Surface} to which a first frame has been rendered, or {@code null} if
|
||||
* the renderer renders to something that isn't a {@link Surface}.
|
||||
*/
|
||||
void onRenderedFirstFrame(EventTime eventTime, Surface surface);
|
||||
|
||||
/**
|
||||
* Called if there was an error loading one or more ads. The ads loader will skip the problematic
|
||||
* ad(s).
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
* @param error The error.
|
||||
*/
|
||||
void onAdLoadError(EventTime eventTime, IOException error);
|
||||
|
||||
/**
|
||||
* Called when an unexpected internal error is encountered while loading ads. The ads loader will
|
||||
* skip all remaining ads, as the error is not recoverable.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
* @param error The error.
|
||||
*/
|
||||
void onInternalAdLoadError(EventTime eventTime, RuntimeException error);
|
||||
|
||||
/**
|
||||
* Called when the user clicks through an ad (for example, following a 'learn more' link).
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
*/
|
||||
void onAdClicked(EventTime eventTime);
|
||||
|
||||
/**
|
||||
* Called when the user taps a non-clickthrough part of an ad.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
*/
|
||||
void onAdTapped(EventTime eventTime);
|
||||
|
||||
/**
|
||||
* Called each time drm keys are loaded.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
*/
|
||||
void onDrmKeysLoaded(EventTime eventTime);
|
||||
|
||||
/**
|
||||
* Called when a drm error occurs. These errors are just for informational purposes and the player
|
||||
* may recover.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
* @param error The error.
|
||||
*/
|
||||
void onDrmSessionManagerError(EventTime eventTime, Exception error);
|
||||
|
||||
/**
|
||||
* Called each time offline drm keys are restored.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
*/
|
||||
void onDrmKeysRestored(EventTime eventTime);
|
||||
|
||||
/**
|
||||
* Called each time offline drm keys are removed.
|
||||
*
|
||||
* @param eventTime The event time.
|
||||
*/
|
||||
void onDrmKeysRemoved(EventTime eventTime);
|
||||
}
|
@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer2.analytics;
|
||||
|
||||
import android.net.NetworkInfo;
|
||||
import android.view.Surface;
|
||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.PlaybackParameters;
|
||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||
import com.google.android.exoplayer2.metadata.Metadata;
|
||||
import com.google.android.exoplayer2.source.MediaSourceEventListener.LoadEventInfo;
|
||||
import com.google.android.exoplayer2.source.MediaSourceEventListener.MediaLoadData;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* {@link AnalyticsListener} allowing selective overrides. All methods are implemented as no-ops.
|
||||
*/
|
||||
public abstract class DefaultAnalyticsListener implements AnalyticsListener {
|
||||
|
||||
@Override
|
||||
public void onPlayerStateChanged(EventTime eventTime, boolean playWhenReady, int playbackState) {}
|
||||
|
||||
@Override
|
||||
public void onTimelineChanged(EventTime eventTime, int reason) {}
|
||||
|
||||
@Override
|
||||
public void onPositionDiscontinuity(EventTime eventTime, int reason) {}
|
||||
|
||||
@Override
|
||||
public void onSeekStarted(EventTime eventTime) {}
|
||||
|
||||
@Override
|
||||
public void onSeekProcessed(EventTime eventTime) {}
|
||||
|
||||
@Override
|
||||
public void onPlaybackParametersChanged(
|
||||
EventTime eventTime, PlaybackParameters playbackParameters) {}
|
||||
|
||||
@Override
|
||||
public void onRepeatModeChanged(EventTime eventTime, int repeatMode) {}
|
||||
|
||||
@Override
|
||||
public void onShuffleModeChanged(EventTime eventTime, boolean shuffleModeEnabled) {}
|
||||
|
||||
@Override
|
||||
public void onLoadingChanged(EventTime eventTime, boolean isLoading) {}
|
||||
|
||||
@Override
|
||||
public void onPlayerError(EventTime eventTime, ExoPlaybackException error) {}
|
||||
|
||||
@Override
|
||||
public void onTracksChanged(
|
||||
EventTime eventTime, TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {}
|
||||
|
||||
@Override
|
||||
public void onLoadStarted(
|
||||
EventTime eventTime, LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData) {}
|
||||
|
||||
@Override
|
||||
public void onLoadCompleted(
|
||||
EventTime eventTime, LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData) {}
|
||||
|
||||
@Override
|
||||
public void onLoadCanceled(
|
||||
EventTime eventTime, LoadEventInfo loadEventInfo, MediaLoadData mediaLoadData) {}
|
||||
|
||||
@Override
|
||||
public void onLoadError(
|
||||
EventTime eventTime,
|
||||
LoadEventInfo loadEventInfo,
|
||||
MediaLoadData mediaLoadData,
|
||||
IOException error,
|
||||
boolean wasCanceled) {}
|
||||
|
||||
@Override
|
||||
public void onDownstreamFormatChanged(EventTime eventTime, MediaLoadData mediaLoadData) {}
|
||||
|
||||
@Override
|
||||
public void onUpstreamDiscarded(EventTime eventTime, MediaLoadData mediaLoadData) {}
|
||||
|
||||
@Override
|
||||
public void onMediaPeriodCreated(EventTime eventTime) {}
|
||||
|
||||
@Override
|
||||
public void onMediaPeriodReleased(EventTime eventTime) {}
|
||||
|
||||
@Override
|
||||
public void onReadingStarted(EventTime eventTime) {}
|
||||
|
||||
@Override
|
||||
public void onBandwidthEstimate(
|
||||
EventTime eventTime, int totalLoadTimeMs, long totalBytesLoaded, long bitrateEstimate) {}
|
||||
|
||||
@Override
|
||||
public void onViewportSizeChange(EventTime eventTime, int width, int height) {}
|
||||
|
||||
@Override
|
||||
public void onNetworkTypeChanged(EventTime eventTime, NetworkInfo networkInfo) {}
|
||||
|
||||
@Override
|
||||
public void onMetadata(EventTime eventTime, Metadata metadata) {}
|
||||
|
||||
@Override
|
||||
public void onDecoderEnabled(
|
||||
EventTime eventTime, int trackType, DecoderCounters decoderCounters) {}
|
||||
|
||||
@Override
|
||||
public void onDecoderInitialized(
|
||||
EventTime eventTime, int trackType, String decoderName, long initializationDurationMs) {}
|
||||
|
||||
@Override
|
||||
public void onDecoderInputFormatChanged(EventTime eventTime, int trackType, Format format) {}
|
||||
|
||||
@Override
|
||||
public void onDecoderDisabled(
|
||||
EventTime eventTime, int trackType, DecoderCounters decoderCounters) {}
|
||||
|
||||
@Override
|
||||
public void onAudioSessionId(EventTime eventTime, int audioSessionId) {}
|
||||
|
||||
@Override
|
||||
public void onAudioUnderrun(
|
||||
EventTime eventTime, int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {}
|
||||
|
||||
@Override
|
||||
public void onDroppedVideoFrames(EventTime eventTime, int droppedFrames, long elapsedMs) {}
|
||||
|
||||
@Override
|
||||
public void onVideoSizeChanged(
|
||||
EventTime eventTime,
|
||||
int width,
|
||||
int height,
|
||||
int unappliedRotationDegrees,
|
||||
float pixelWidthHeightRatio) {}
|
||||
|
||||
@Override
|
||||
public void onRenderedFirstFrame(EventTime eventTime, Surface surface) {}
|
||||
|
||||
@Override
|
||||
public void onAdLoadError(EventTime eventTime, IOException error) {}
|
||||
|
||||
@Override
|
||||
public void onInternalAdLoadError(EventTime eventTime, RuntimeException error) {}
|
||||
|
||||
@Override
|
||||
public void onAdClicked(EventTime eventTime) {}
|
||||
|
||||
@Override
|
||||
public void onAdTapped(EventTime eventTime) {}
|
||||
|
||||
@Override
|
||||
public void onDrmKeysLoaded(EventTime eventTime) {}
|
||||
|
||||
@Override
|
||||
public void onDrmSessionManagerError(EventTime eventTime, Exception error) {}
|
||||
|
||||
@Override
|
||||
public void onDrmKeysRestored(EventTime eventTime) {}
|
||||
|
||||
@Override
|
||||
public void onDrmKeysRemoved(EventTime eventTime) {}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user