Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
SDK
/
exoplayer
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Settings
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
343279b2
authored
Nov 22, 2021
by
tonihei
Committed by
Ian Baker
Nov 22, 2021
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
Add MediaMetricsListener class.
PiperOrigin-RevId: 411517319
parent
5b22b06e
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
847 additions
and
0 deletions
libraries/exoplayer/src/main/java/androidx/media3/exoplayer/analytics/MediaMetricsListener.java
libraries/exoplayer/src/main/java/androidx/media3/exoplayer/analytics/MediaMetricsListener.java
0 → 100644
View file @
343279b2
/*
* Copyright 2021 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
androidx
.
media3
.
exoplayer
.
analytics
;
import
static
androidx
.
media3
.
common
.
util
.
Assertions
.
checkNotNull
;
import
static
androidx
.
media3
.
common
.
util
.
Assertions
.
checkStateNotNull
;
import
static
androidx
.
media3
.
common
.
util
.
Util
.
castNonNull
;
import
android.annotation.SuppressLint
;
import
android.content.Context
;
import
android.media.DeniedByServerException
;
import
android.media.MediaCodec
;
import
android.media.MediaDrm
;
import
android.media.MediaDrmResetException
;
import
android.media.NotProvisionedException
;
import
android.media.metrics.LogSessionId
;
import
android.media.metrics.MediaMetricsManager
;
import
android.media.metrics.NetworkEvent
;
import
android.media.metrics.PlaybackErrorEvent
;
import
android.media.metrics.PlaybackMetrics
;
import
android.media.metrics.PlaybackSession
;
import
android.media.metrics.PlaybackStateEvent
;
import
android.media.metrics.TrackChangeEvent
;
import
android.os.SystemClock
;
import
android.system.ErrnoException
;
import
android.system.OsConstants
;
import
android.util.Pair
;
import
androidx.annotation.Nullable
;
import
androidx.annotation.RequiresApi
;
import
androidx.media3.common.C
;
import
androidx.media3.common.DrmInitData
;
import
androidx.media3.common.Format
;
import
androidx.media3.common.MediaItem
;
import
androidx.media3.common.MediaLibraryInfo
;
import
androidx.media3.common.MimeTypes
;
import
androidx.media3.common.ParserException
;
import
androidx.media3.common.PlaybackException
;
import
androidx.media3.common.Player
;
import
androidx.media3.common.Timeline
;
import
androidx.media3.common.TrackGroup
;
import
androidx.media3.common.TracksInfo
;
import
androidx.media3.common.TracksInfo.TrackGroupInfo
;
import
androidx.media3.common.VideoSize
;
import
androidx.media3.common.util.NetworkTypeObserver
;
import
androidx.media3.common.util.UnstableApi
;
import
androidx.media3.common.util.Util
;
import
androidx.media3.datasource.FileDataSource
;
import
androidx.media3.datasource.HttpDataSource
;
import
androidx.media3.datasource.UdpDataSource
;
import
androidx.media3.exoplayer.DecoderCounters
;
import
androidx.media3.exoplayer.ExoPlaybackException
;
import
androidx.media3.exoplayer.audio.AudioSink
;
import
androidx.media3.exoplayer.drm.DefaultDrmSessionManager
;
import
androidx.media3.exoplayer.drm.DrmSession
;
import
androidx.media3.exoplayer.drm.UnsupportedDrmException
;
import
androidx.media3.exoplayer.mediacodec.MediaCodecDecoderException
;
import
androidx.media3.exoplayer.mediacodec.MediaCodecRenderer
;
import
androidx.media3.exoplayer.source.LoadEventInfo
;
import
androidx.media3.exoplayer.source.MediaLoadData
;
import
androidx.media3.exoplayer.source.MediaSource
;
import
com.google.common.collect.ImmutableList
;
import
java.io.FileNotFoundException
;
import
java.io.IOException
;
import
java.net.SocketTimeoutException
;
import
java.net.UnknownHostException
;
import
java.util.UUID
;
import
org.checkerframework.checker.nullness.compatqual.NullableType
;
import
org.checkerframework.checker.nullness.qual.EnsuresNonNullIf
;
import
org.checkerframework.checker.nullness.qual.RequiresNonNull
;
/**
* An {@link AnalyticsListener} that interacts with the Android {@link MediaMetricsManager}.
*
* <p>It listens to playback events and forwards them to a {@link PlaybackSession}. The {@link
* LogSessionId} of the playback session can be obtained with {@link #getLogSessionId()}.
*/
@UnstableApi
@RequiresApi
(
31
)
public
final
class
MediaMetricsListener
implements
AnalyticsListener
,
PlaybackSessionManager
.
Listener
{
private
final
Context
context
;
private
final
PlaybackSessionManager
sessionManager
;
private
final
PlaybackSession
playbackSession
;
private
final
long
startTimeMs
;
private
final
Timeline
.
Window
window
;
private
final
Timeline
.
Period
period
;
@Nullable
private
PlaybackMetrics
.
Builder
metricsBuilder
;
@Player
.
DiscontinuityReason
private
int
discontinuityReason
;
private
int
currentPlaybackState
;
private
int
currentNetworkType
;
@Nullable
private
PlaybackException
pendingPlayerError
;
@Nullable
private
PendingFormatUpdate
pendingVideoFormat
;
@Nullable
private
PendingFormatUpdate
pendingAudioFormat
;
@Nullable
private
PendingFormatUpdate
pendingTextFormat
;
@Nullable
private
Format
currentVideoFormat
;
@Nullable
private
Format
currentAudioFormat
;
@Nullable
private
Format
currentTextFormat
;
private
boolean
isSeeking
;
private
int
ioErrorType
;
private
boolean
hasFatalError
;
private
int
droppedFrames
;
private
int
playedFrames
;
private
long
bandwidthTimeMs
;
private
long
bandwidthBytes
;
private
int
audioUnderruns
;
/**
* Creates the listener.
*
* @param context A {@link Context}.
*/
public
MediaMetricsListener
(
Context
context
)
{
context
=
context
.
getApplicationContext
();
this
.
context
=
context
;
window
=
new
Timeline
.
Window
();
period
=
new
Timeline
.
Period
();
MediaMetricsManager
mediaMetricsManager
=
checkStateNotNull
(
(
MediaMetricsManager
)
context
.
getSystemService
(
Context
.
MEDIA_METRICS_SERVICE
));
playbackSession
=
mediaMetricsManager
.
createPlaybackSession
();
startTimeMs
=
SystemClock
.
elapsedRealtime
();
currentPlaybackState
=
PlaybackStateEvent
.
STATE_NOT_STARTED
;
currentNetworkType
=
NetworkEvent
.
NETWORK_TYPE_UNKNOWN
;
sessionManager
=
new
DefaultPlaybackSessionManager
();
sessionManager
.
setListener
(
this
);
}
/** Returns the {@link LogSessionId} used by this listener. */
public
LogSessionId
getLogSessionId
()
{
return
playbackSession
.
getSessionId
();
}
// PlaybackSessionManager.Listener implementation.
@Override
public
void
onSessionCreated
(
EventTime
eventTime
,
String
sessionId
)
{}
@Override
public
void
onSessionActive
(
EventTime
eventTime
,
String
sessionId
)
{
if
(
eventTime
.
mediaPeriodId
!=
null
&&
eventTime
.
mediaPeriodId
.
isAd
())
{
// Ignore ad sessions.
return
;
}
finishCurrentSession
();
metricsBuilder
=
new
PlaybackMetrics
.
Builder
()
.
setPlayerName
(
MediaLibraryInfo
.
TAG
)
.
setPlayerVersion
(
MediaLibraryInfo
.
VERSION
);
maybeUpdateTimelineMetadata
(
eventTime
.
timeline
,
eventTime
.
mediaPeriodId
);
}
@Override
public
void
onAdPlaybackStarted
(
EventTime
eventTime
,
String
contentSessionId
,
String
adSessionId
)
{}
@Override
public
void
onSessionFinished
(
EventTime
eventTime
,
String
sessionId
,
boolean
automaticTransitionToNextPlayback
)
{
if
(
eventTime
.
mediaPeriodId
!=
null
&&
eventTime
.
mediaPeriodId
.
isAd
())
{
// Ignore ad sessions.
return
;
}
finishCurrentSession
();
}
// AnalyticsListener implementation.
@Override
public
void
onPositionDiscontinuity
(
EventTime
eventTime
,
Player
.
PositionInfo
oldPosition
,
Player
.
PositionInfo
newPosition
,
@Player
.
DiscontinuityReason
int
reason
)
{
if
(
reason
==
Player
.
DISCONTINUITY_REASON_SEEK
)
{
isSeeking
=
true
;
}
discontinuityReason
=
reason
;
}
@Override
public
void
onVideoDisabled
(
EventTime
eventTime
,
DecoderCounters
decoderCounters
)
{
// TODO(b/181122234): DecoderCounters are not re-reported at period boundaries.
droppedFrames
+=
decoderCounters
.
droppedBufferCount
;
playedFrames
+=
decoderCounters
.
renderedOutputBufferCount
;
}
@Override
public
void
onBandwidthEstimate
(
EventTime
eventTime
,
int
totalLoadTimeMs
,
long
totalBytesLoaded
,
long
bitrateEstimate
)
{
bandwidthTimeMs
+=
totalLoadTimeMs
;
bandwidthBytes
+=
totalBytesLoaded
;
}
@Override
public
void
onDownstreamFormatChanged
(
EventTime
eventTime
,
MediaLoadData
mediaLoadData
)
{
PendingFormatUpdate
update
=
new
PendingFormatUpdate
(
checkNotNull
(
mediaLoadData
.
trackFormat
),
mediaLoadData
.
trackSelectionReason
,
sessionManager
.
getSessionForMediaPeriodId
(
eventTime
.
timeline
,
checkNotNull
(
eventTime
.
mediaPeriodId
)));
switch
(
mediaLoadData
.
trackType
)
{
case
C
.
TRACK_TYPE_VIDEO
:
case
C
.
TRACK_TYPE_DEFAULT
:
pendingVideoFormat
=
update
;
break
;
case
C
.
TRACK_TYPE_AUDIO
:
pendingAudioFormat
=
update
;
break
;
case
C
.
TRACK_TYPE_TEXT
:
pendingTextFormat
=
update
;
break
;
default
:
// Other track type. Ignore.
}
}
@Override
public
void
onVideoSizeChanged
(
EventTime
eventTime
,
VideoSize
videoSize
)
{
@Nullable
PendingFormatUpdate
pendingVideoFormat
=
this
.
pendingVideoFormat
;
if
(
pendingVideoFormat
!=
null
&&
pendingVideoFormat
.
format
.
height
==
Format
.
NO_VALUE
)
{
Format
formatWithHeightAndWidth
=
pendingVideoFormat
.
format
.
buildUpon
()
.
setWidth
(
videoSize
.
width
)
.
setHeight
(
videoSize
.
height
)
.
build
();
this
.
pendingVideoFormat
=
new
PendingFormatUpdate
(
formatWithHeightAndWidth
,
pendingVideoFormat
.
selectionReason
,
pendingVideoFormat
.
sessionId
);
}
}
@Override
public
void
onLoadError
(
EventTime
eventTime
,
LoadEventInfo
loadEventInfo
,
MediaLoadData
mediaLoadData
,
IOException
error
,
boolean
wasCanceled
)
{
ioErrorType
=
mediaLoadData
.
dataType
;
}
@Override
public
void
onPlayerError
(
EventTime
eventTime
,
PlaybackException
error
)
{
pendingPlayerError
=
error
;
}
@Override
public
void
onEvents
(
Player
player
,
Events
events
)
{
if
(
events
.
size
()
==
0
)
{
return
;
}
maybeAddSessions
(
events
);
long
realtimeMs
=
SystemClock
.
elapsedRealtime
();
maybeUpdateMetricsBuilderValues
(
player
,
events
);
maybeReportPlaybackError
(
realtimeMs
);
maybeReportTrackChanges
(
player
,
events
,
realtimeMs
);
maybeReportNetworkChange
(
realtimeMs
);
maybeReportPlaybackStateChange
(
player
,
events
,
realtimeMs
);
if
(
events
.
contains
(
AnalyticsListener
.
EVENT_PLAYER_RELEASED
))
{
sessionManager
.
finishAllSessions
(
events
.
getEventTime
(
EVENT_PLAYER_RELEASED
));
}
}
private
void
maybeAddSessions
(
Events
events
)
{
for
(
int
i
=
0
;
i
<
events
.
size
();
i
++)
{
@EventFlags
int
event
=
events
.
get
(
i
);
EventTime
eventTime
=
events
.
getEventTime
(
event
);
if
(
event
==
EVENT_TIMELINE_CHANGED
)
{
sessionManager
.
updateSessionsWithTimelineChange
(
eventTime
);
}
else
if
(
event
==
EVENT_POSITION_DISCONTINUITY
)
{
sessionManager
.
updateSessionsWithDiscontinuity
(
eventTime
,
discontinuityReason
);
}
else
{
sessionManager
.
updateSessions
(
eventTime
);
}
}
}
private
void
maybeUpdateMetricsBuilderValues
(
Player
player
,
Events
events
)
{
if
(
events
.
contains
(
EVENT_TIMELINE_CHANGED
))
{
EventTime
eventTime
=
events
.
getEventTime
(
EVENT_TIMELINE_CHANGED
);
if
(
metricsBuilder
!=
null
)
{
maybeUpdateTimelineMetadata
(
eventTime
.
timeline
,
eventTime
.
mediaPeriodId
);
}
}
if
(
events
.
contains
(
EVENT_TRACKS_CHANGED
)
&&
metricsBuilder
!=
null
)
{
@Nullable
DrmInitData
drmInitData
=
getDrmInitData
(
player
.
getCurrentTracksInfo
().
getTrackGroupInfos
());
if
(
drmInitData
!=
null
)
{
castNonNull
(
metricsBuilder
).
setDrmType
(
getDrmType
(
drmInitData
));
}
}
if
(
events
.
contains
(
EVENT_AUDIO_UNDERRUN
))
{
audioUnderruns
++;
}
}
private
void
maybeReportPlaybackError
(
long
realtimeMs
)
{
@Nullable
PlaybackException
error
=
pendingPlayerError
;
if
(
error
==
null
)
{
return
;
}
ErrorInfo
errorInfo
=
getErrorInfo
(
error
,
context
,
/* lastIoErrorForManifest= */
ioErrorType
==
C
.
DATA_TYPE_MANIFEST
);
playbackSession
.
reportPlaybackErrorEvent
(
new
PlaybackErrorEvent
.
Builder
()
.
setTimeSinceCreatedMillis
(
realtimeMs
-
startTimeMs
)
.
setErrorCode
(
errorInfo
.
errorCode
)
.
setSubErrorCode
(
errorInfo
.
subErrorCode
)
.
setException
(
error
)
.
build
());
pendingPlayerError
=
null
;
}
private
void
maybeReportTrackChanges
(
Player
player
,
Events
events
,
long
realtimeMs
)
{
if
(
events
.
contains
(
EVENT_TRACKS_CHANGED
))
{
TracksInfo
tracksInfo
=
player
.
getCurrentTracksInfo
();
boolean
isVideoSelected
=
tracksInfo
.
isTypeSelected
(
C
.
TRACK_TYPE_VIDEO
);
boolean
isAudioSelected
=
tracksInfo
.
isTypeSelected
(
C
.
TRACK_TYPE_AUDIO
);
boolean
isTextSelected
=
tracksInfo
.
isTypeSelected
(
C
.
TRACK_TYPE_TEXT
);
if
(
isVideoSelected
||
isAudioSelected
||
isTextSelected
)
{
// Ignore updates with insufficient information where no tracks are selected.
if
(!
isVideoSelected
)
{
maybeUpdateVideoFormat
(
realtimeMs
,
/* videoFormat= */
null
,
C
.
SELECTION_REASON_UNKNOWN
);
}
if
(!
isAudioSelected
)
{
maybeUpdateAudioFormat
(
realtimeMs
,
/* audioFormat= */
null
,
C
.
SELECTION_REASON_UNKNOWN
);
}
if
(!
isTextSelected
)
{
maybeUpdateTextFormat
(
realtimeMs
,
/* textFormat= */
null
,
C
.
SELECTION_REASON_UNKNOWN
);
}
}
}
if
(
canReportPendingFormatUpdate
(
pendingVideoFormat
)
&&
pendingVideoFormat
.
format
.
height
!=
Format
.
NO_VALUE
)
{
maybeUpdateVideoFormat
(
realtimeMs
,
pendingVideoFormat
.
format
,
pendingVideoFormat
.
selectionReason
);
pendingVideoFormat
=
null
;
}
if
(
canReportPendingFormatUpdate
(
pendingAudioFormat
))
{
maybeUpdateAudioFormat
(
realtimeMs
,
pendingAudioFormat
.
format
,
pendingAudioFormat
.
selectionReason
);
pendingAudioFormat
=
null
;
}
if
(
canReportPendingFormatUpdate
(
pendingTextFormat
))
{
maybeUpdateTextFormat
(
realtimeMs
,
pendingTextFormat
.
format
,
pendingTextFormat
.
selectionReason
);
pendingTextFormat
=
null
;
}
}
@EnsuresNonNullIf
(
result
=
true
,
expression
=
"#1"
)
private
boolean
canReportPendingFormatUpdate
(
@Nullable
PendingFormatUpdate
pendingFormatUpdate
)
{
return
pendingFormatUpdate
!=
null
&&
pendingFormatUpdate
.
sessionId
.
equals
(
sessionManager
.
getActiveSessionId
());
}
private
void
maybeReportNetworkChange
(
long
realtimeMs
)
{
int
networkType
=
getNetworkType
(
context
);
if
(
networkType
!=
currentNetworkType
)
{
currentNetworkType
=
networkType
;
playbackSession
.
reportNetworkEvent
(
new
NetworkEvent
.
Builder
()
.
setNetworkType
(
networkType
)
.
setTimeSinceCreatedMillis
(
realtimeMs
-
startTimeMs
)
.
build
());
}
}
private
void
maybeReportPlaybackStateChange
(
Player
player
,
Events
events
,
long
realtimeMs
)
{
if
(
player
.
getPlaybackState
()
!=
Player
.
STATE_BUFFERING
)
{
isSeeking
=
false
;
}
if
(
player
.
getPlayerError
()
==
null
)
{
hasFatalError
=
false
;
}
else
if
(
events
.
contains
(
EVENT_PLAYER_ERROR
))
{
hasFatalError
=
true
;
}
int
newPlaybackState
=
resolveNewPlaybackState
(
player
);
if
(
currentPlaybackState
!=
newPlaybackState
)
{
currentPlaybackState
=
newPlaybackState
;
playbackSession
.
reportPlaybackStateEvent
(
new
PlaybackStateEvent
.
Builder
()
.
setState
(
currentPlaybackState
)
.
setTimeSinceCreatedMillis
(
realtimeMs
-
startTimeMs
)
.
build
());
}
}
private
int
resolveNewPlaybackState
(
Player
player
)
{
@Player
.
State
int
playerPlaybackState
=
player
.
getPlaybackState
();
if
(
isSeeking
)
{
// Seeking takes precedence over errors such that we report a seek while in error state.
return
PlaybackStateEvent
.
STATE_SEEKING
;
}
else
if
(
hasFatalError
)
{
return
PlaybackStateEvent
.
STATE_FAILED
;
}
else
if
(
playerPlaybackState
==
Player
.
STATE_ENDED
)
{
return
PlaybackStateEvent
.
STATE_ENDED
;
}
else
if
(
playerPlaybackState
==
Player
.
STATE_BUFFERING
)
{
if
(
currentPlaybackState
==
PlaybackStateEvent
.
STATE_NOT_STARTED
||
currentPlaybackState
==
PlaybackStateEvent
.
STATE_JOINING_FOREGROUND
)
{
return
PlaybackStateEvent
.
STATE_JOINING_FOREGROUND
;
}
if
(!
player
.
getPlayWhenReady
())
{
return
PlaybackStateEvent
.
STATE_PAUSED_BUFFERING
;
}
return
player
.
getPlaybackSuppressionReason
()
!=
Player
.
PLAYBACK_SUPPRESSION_REASON_NONE
?
PlaybackStateEvent
.
STATE_SUPPRESSED_BUFFERING
:
PlaybackStateEvent
.
STATE_BUFFERING
;
}
else
if
(
playerPlaybackState
==
Player
.
STATE_READY
)
{
if
(!
player
.
getPlayWhenReady
())
{
return
PlaybackStateEvent
.
STATE_PAUSED
;
}
return
player
.
getPlaybackSuppressionReason
()
!=
Player
.
PLAYBACK_SUPPRESSION_REASON_NONE
?
PlaybackStateEvent
.
STATE_SUPPRESSED
:
PlaybackStateEvent
.
STATE_PLAYING
;
}
else
if
(
playerPlaybackState
==
Player
.
STATE_IDLE
&&
currentPlaybackState
!=
PlaybackStateEvent
.
STATE_NOT_STARTED
)
{
// This case only applies for calls to player.stop(). All other IDLE cases are handled by
// !isForeground, hasFatalError or isSuspended. NOT_STARTED is deliberately ignored.
return
PlaybackStateEvent
.
STATE_STOPPED
;
}
return
currentPlaybackState
;
}
private
void
maybeUpdateVideoFormat
(
long
realtimeMs
,
@Nullable
Format
videoFormat
,
@C
.
SelectionReason
int
trackSelectionReason
)
{
if
(
Util
.
areEqual
(
currentVideoFormat
,
videoFormat
))
{
return
;
}
if
(
currentVideoFormat
==
null
&&
trackSelectionReason
==
C
.
SELECTION_REASON_UNKNOWN
)
{
trackSelectionReason
=
C
.
SELECTION_REASON_INITIAL
;
}
currentVideoFormat
=
videoFormat
;
reportTrackChangeEvent
(
TrackChangeEvent
.
TRACK_TYPE_VIDEO
,
realtimeMs
,
videoFormat
,
trackSelectionReason
);
}
private
void
maybeUpdateAudioFormat
(
long
realtimeMs
,
@Nullable
Format
audioFormat
,
@C
.
SelectionReason
int
trackSelectionReason
)
{
if
(
Util
.
areEqual
(
currentAudioFormat
,
audioFormat
))
{
return
;
}
if
(
currentAudioFormat
==
null
&&
trackSelectionReason
==
C
.
SELECTION_REASON_UNKNOWN
)
{
trackSelectionReason
=
C
.
SELECTION_REASON_INITIAL
;
}
currentAudioFormat
=
audioFormat
;
reportTrackChangeEvent
(
TrackChangeEvent
.
TRACK_TYPE_AUDIO
,
realtimeMs
,
audioFormat
,
trackSelectionReason
);
}
private
void
maybeUpdateTextFormat
(
long
realtimeMs
,
@Nullable
Format
textFormat
,
@C
.
SelectionReason
int
trackSelectionReason
)
{
if
(
Util
.
areEqual
(
currentTextFormat
,
textFormat
))
{
return
;
}
if
(
currentTextFormat
==
null
&&
trackSelectionReason
==
C
.
SELECTION_REASON_UNKNOWN
)
{
trackSelectionReason
=
C
.
SELECTION_REASON_INITIAL
;
}
currentTextFormat
=
textFormat
;
reportTrackChangeEvent
(
TrackChangeEvent
.
TRACK_TYPE_TEXT
,
realtimeMs
,
textFormat
,
trackSelectionReason
);
}
private
void
reportTrackChangeEvent
(
int
type
,
long
realtimeMs
,
@Nullable
Format
format
,
@C
.
SelectionReason
int
trackSelectionReason
)
{
TrackChangeEvent
.
Builder
builder
=
new
TrackChangeEvent
.
Builder
(
type
).
setTimeSinceCreatedMillis
(
realtimeMs
-
startTimeMs
);
if
(
format
!=
null
)
{
builder
.
setTrackState
(
TrackChangeEvent
.
TRACK_STATE_ON
);
builder
.
setTrackChangeReason
(
getTrackChangeReason
(
trackSelectionReason
));
if
(
format
.
containerMimeType
!=
null
)
{
// TODO(b/181121074): Progressive container mime type is not filled in by MediaSource.
builder
.
setContainerMimeType
(
format
.
containerMimeType
);
}
if
(
format
.
sampleMimeType
!=
null
)
{
builder
.
setSampleMimeType
(
format
.
sampleMimeType
);
}
if
(
format
.
codecs
!=
null
)
{
builder
.
setCodecName
(
format
.
codecs
);
}
if
(
format
.
bitrate
!=
Format
.
NO_VALUE
)
{
builder
.
setBitrate
(
format
.
bitrate
);
}
if
(
format
.
width
!=
Format
.
NO_VALUE
)
{
builder
.
setWidth
(
format
.
width
);
}
if
(
format
.
height
!=
Format
.
NO_VALUE
)
{
builder
.
setHeight
(
format
.
height
);
}
if
(
format
.
channelCount
!=
Format
.
NO_VALUE
)
{
builder
.
setChannelCount
(
format
.
channelCount
);
}
if
(
format
.
sampleRate
!=
Format
.
NO_VALUE
)
{
builder
.
setAudioSampleRate
(
format
.
sampleRate
);
}
if
(
format
.
language
!=
null
)
{
Pair
<
String
,
@NullableType
String
>
languageAndRegion
=
getLanguageAndRegion
(
format
.
language
);
builder
.
setLanguage
(
languageAndRegion
.
first
);
if
(
languageAndRegion
.
second
!=
null
)
{
builder
.
setLanguageRegion
(
languageAndRegion
.
second
);
}
}
if
(
format
.
frameRate
!=
Format
.
NO_VALUE
)
{
builder
.
setVideoFrameRate
(
format
.
frameRate
);
}
}
else
{
builder
.
setTrackState
(
TrackChangeEvent
.
TRACK_STATE_OFF
);
}
playbackSession
.
reportTrackChangeEvent
(
builder
.
build
());
}
@RequiresNonNull
(
"metricsBuilder"
)
private
void
maybeUpdateTimelineMetadata
(
Timeline
timeline
,
@Nullable
MediaSource
.
MediaPeriodId
mediaPeriodId
)
{
PlaybackMetrics
.
Builder
metricsBuilder
=
this
.
metricsBuilder
;
if
(
mediaPeriodId
==
null
)
{
return
;
}
int
periodIndex
=
timeline
.
getIndexOfPeriod
(
mediaPeriodId
.
periodUid
);
if
(
periodIndex
==
C
.
INDEX_UNSET
)
{
return
;
}
timeline
.
getPeriod
(
periodIndex
,
period
);
timeline
.
getWindow
(
period
.
windowIndex
,
window
);
metricsBuilder
.
setStreamType
(
getStreamType
(
window
.
mediaItem
));
if
(
window
.
durationUs
!=
C
.
TIME_UNSET
&&
!
window
.
isPlaceholder
&&
!
window
.
isDynamic
&&
!
window
.
isLive
())
{
metricsBuilder
.
setMediaDurationMillis
(
window
.
getDurationMs
());
}
metricsBuilder
.
setPlaybackType
(
window
.
isLive
()
?
PlaybackMetrics
.
PLAYBACK_TYPE_LIVE
:
PlaybackMetrics
.
PLAYBACK_TYPE_VOD
);
}
private
void
finishCurrentSession
()
{
if
(
metricsBuilder
==
null
)
{
return
;
}
metricsBuilder
.
setAudioUnderrunCount
(
audioUnderruns
);
metricsBuilder
.
setVideoFramesDropped
(
droppedFrames
);
metricsBuilder
.
setVideoFramesPlayed
(
playedFrames
);
metricsBuilder
.
setNetworkTransferDurationMillis
(
bandwidthTimeMs
);
// TODO(b/181121847): Report localBytesRead. This requires additional callbacks or plumbing.
metricsBuilder
.
setNetworkBytesRead
(
bandwidthBytes
);
// TODO(b/181121847): Detect stream sources mixed and local depending on localBytesRead.
metricsBuilder
.
setStreamSource
(
bandwidthBytes
>
0
?
PlaybackMetrics
.
STREAM_SOURCE_NETWORK
:
PlaybackMetrics
.
STREAM_SOURCE_UNKNOWN
);
playbackSession
.
reportPlaybackMetrics
(
metricsBuilder
.
build
());
metricsBuilder
=
null
;
}
private
static
int
getTrackChangeReason
(
@C
.
SelectionReason
int
trackSelectionReason
)
{
switch
(
trackSelectionReason
)
{
case
C
.
SELECTION_REASON_INITIAL
:
return
TrackChangeEvent
.
TRACK_CHANGE_REASON_INITIAL
;
case
C
.
SELECTION_REASON_ADAPTIVE
:
return
TrackChangeEvent
.
TRACK_CHANGE_REASON_ADAPTIVE
;
case
C
.
SELECTION_REASON_MANUAL
:
return
TrackChangeEvent
.
TRACK_CHANGE_REASON_MANUAL
;
case
C
.
SELECTION_REASON_TRICK_PLAY
:
case
C
.
SELECTION_REASON_UNKNOWN
:
default
:
return
TrackChangeEvent
.
TRACK_CHANGE_REASON_OTHER
;
}
}
private
static
Pair
<
String
,
@NullableType
String
>
getLanguageAndRegion
(
String
languageCode
)
{
String
[]
parts
=
Util
.
split
(
languageCode
,
"-"
);
return
Pair
.
create
(
parts
[
0
],
parts
.
length
>=
2
?
parts
[
1
]
:
null
);
}
private
static
int
getNetworkType
(
Context
context
)
{
switch
(
NetworkTypeObserver
.
getInstance
(
context
).
getNetworkType
())
{
case
C
.
NETWORK_TYPE_WIFI
:
return
NetworkEvent
.
NETWORK_TYPE_WIFI
;
case
C
.
NETWORK_TYPE_2G
:
return
NetworkEvent
.
NETWORK_TYPE_2G
;
case
C
.
NETWORK_TYPE_3G
:
return
NetworkEvent
.
NETWORK_TYPE_3G
;
case
C
.
NETWORK_TYPE_4G
:
return
NetworkEvent
.
NETWORK_TYPE_4G
;
case
C
.
NETWORK_TYPE_5G_SA
:
return
NetworkEvent
.
NETWORK_TYPE_5G_SA
;
case
C
.
NETWORK_TYPE_5G_NSA
:
return
NetworkEvent
.
NETWORK_TYPE_5G_NSA
;
case
C
.
NETWORK_TYPE_ETHERNET
:
return
NetworkEvent
.
NETWORK_TYPE_ETHERNET
;
case
C
.
NETWORK_TYPE_OFFLINE
:
return
NetworkEvent
.
NETWORK_TYPE_OFFLINE
;
case
C
.
NETWORK_TYPE_UNKNOWN
:
return
NetworkEvent
.
NETWORK_TYPE_UNKNOWN
;
default
:
return
NetworkEvent
.
NETWORK_TYPE_OTHER
;
}
}
private
static
int
getStreamType
(
MediaItem
mediaItem
)
{
if
(
mediaItem
.
localConfiguration
==
null
||
mediaItem
.
localConfiguration
.
mimeType
==
null
)
{
return
PlaybackMetrics
.
STREAM_TYPE_UNKNOWN
;
}
String
mimeType
=
mediaItem
.
localConfiguration
.
mimeType
;
switch
(
mimeType
)
{
case
MimeTypes
.
APPLICATION_M3U8
:
return
PlaybackMetrics
.
STREAM_TYPE_HLS
;
case
MimeTypes
.
APPLICATION_MPD
:
return
PlaybackMetrics
.
STREAM_TYPE_DASH
;
case
MimeTypes
.
APPLICATION_SS
:
return
PlaybackMetrics
.
STREAM_TYPE_SS
;
default
:
return
PlaybackMetrics
.
STREAM_TYPE_PROGRESSIVE
;
}
}
private
static
ErrorInfo
getErrorInfo
(
PlaybackException
error
,
Context
context
,
boolean
lastIoErrorForManifest
)
{
if
(
error
.
errorCode
==
PlaybackException
.
ERROR_CODE_REMOTE_ERROR
)
{
return
new
ErrorInfo
(
PlaybackErrorEvent
.
ERROR_PLAYER_REMOTE
,
/* subErrorCode= */
0
);
}
// Unpack the PlaybackException.
// TODO(b/190203080): Use error codes instead of the Exception's cause where possible.
boolean
isRendererExoPlaybackException
=
false
;
int
rendererFormatSupport
=
C
.
FORMAT_UNSUPPORTED_TYPE
;
if
(
error
instanceof
ExoPlaybackException
)
{
ExoPlaybackException
exoPlaybackException
=
(
ExoPlaybackException
)
error
;
isRendererExoPlaybackException
=
exoPlaybackException
.
type
==
ExoPlaybackException
.
TYPE_RENDERER
;
rendererFormatSupport
=
exoPlaybackException
.
rendererFormatSupport
;
}
Throwable
cause
=
checkNotNull
(
error
.
getCause
());
if
(
cause
instanceof
IOException
)
{
if
(
cause
instanceof
HttpDataSource
.
InvalidResponseCodeException
)
{
int
responseCode
=
((
HttpDataSource
.
InvalidResponseCodeException
)
cause
).
responseCode
;
return
new
ErrorInfo
(
PlaybackErrorEvent
.
ERROR_IO_BAD_HTTP_STATUS
,
/* subErrorCode= */
responseCode
);
}
else
if
(
cause
instanceof
HttpDataSource
.
InvalidContentTypeException
||
cause
instanceof
ParserException
)
{
return
new
ErrorInfo
(
lastIoErrorForManifest
?
PlaybackErrorEvent
.
ERROR_PARSING_MANIFEST_MALFORMED
:
PlaybackErrorEvent
.
ERROR_PARSING_CONTAINER_MALFORMED
,
/* subErrorCode= */
0
);
}
else
if
(
cause
instanceof
HttpDataSource
.
HttpDataSourceException
||
cause
instanceof
UdpDataSource
.
UdpDataSourceException
)
{
if
(
NetworkTypeObserver
.
getInstance
(
context
).
getNetworkType
()
==
C
.
NETWORK_TYPE_OFFLINE
)
{
return
new
ErrorInfo
(
PlaybackErrorEvent
.
ERROR_IO_NETWORK_UNAVAILABLE
,
/* subErrorCode= */
0
);
}
else
{
@Nullable
Throwable
detailedCause
=
cause
.
getCause
();
if
(
detailedCause
instanceof
UnknownHostException
)
{
return
new
ErrorInfo
(
PlaybackErrorEvent
.
ERROR_IO_DNS_FAILED
,
/* subErrorCode= */
0
);
}
else
if
(
detailedCause
instanceof
SocketTimeoutException
)
{
return
new
ErrorInfo
(
PlaybackErrorEvent
.
ERROR_IO_CONNECTION_TIMEOUT
,
/* subErrorCode= */
0
);
}
else
if
(
cause
instanceof
HttpDataSource
.
HttpDataSourceException
&&
((
HttpDataSource
.
HttpDataSourceException
)
cause
).
type
==
HttpDataSource
.
HttpDataSourceException
.
TYPE_OPEN
)
{
return
new
ErrorInfo
(
PlaybackErrorEvent
.
ERROR_IO_NETWORK_CONNECTION_FAILED
,
/* subErrorCode= */
0
);
}
else
{
return
new
ErrorInfo
(
PlaybackErrorEvent
.
ERROR_IO_CONNECTION_CLOSED
,
/* subErrorCode= */
0
);
}
}
}
else
if
(
error
.
errorCode
==
PlaybackException
.
ERROR_CODE_BEHIND_LIVE_WINDOW
)
{
return
new
ErrorInfo
(
PlaybackErrorEvent
.
ERROR_PLAYER_BEHIND_LIVE_WINDOW
,
/* subErrorCode= */
0
);
}
else
if
(
cause
instanceof
DrmSession
.
DrmSessionException
)
{
// Unpack DrmSessionException.
cause
=
checkNotNull
(
cause
.
getCause
());
if
(
Util
.
SDK_INT
>=
21
&&
cause
instanceof
MediaDrm
.
MediaDrmStateException
)
{
String
diagnosticsInfo
=
((
MediaDrm
.
MediaDrmStateException
)
cause
).
getDiagnosticInfo
();
int
subErrorCode
=
Util
.
getErrorCodeFromPlatformDiagnosticsInfo
(
diagnosticsInfo
);
int
errorCode
=
getDrmErrorCode
(
subErrorCode
);
return
new
ErrorInfo
(
errorCode
,
subErrorCode
);
}
else
if
(
Util
.
SDK_INT
>=
23
&&
cause
instanceof
MediaDrmResetException
)
{
return
new
ErrorInfo
(
PlaybackErrorEvent
.
ERROR_DRM_SYSTEM_ERROR
,
/* subErrorCode= */
0
);
}
else
if
(
Util
.
SDK_INT
>=
18
&&
cause
instanceof
NotProvisionedException
)
{
return
new
ErrorInfo
(
PlaybackErrorEvent
.
ERROR_DRM_PROVISIONING_FAILED
,
/* subErrorCode= */
0
);
}
else
if
(
Util
.
SDK_INT
>=
18
&&
cause
instanceof
DeniedByServerException
)
{
return
new
ErrorInfo
(
PlaybackErrorEvent
.
ERROR_DRM_DEVICE_REVOKED
,
/* subErrorCode= */
0
);
}
else
if
(
cause
instanceof
UnsupportedDrmException
)
{
return
new
ErrorInfo
(
PlaybackErrorEvent
.
ERROR_DRM_SCHEME_UNSUPPORTED
,
/* subErrorCode= */
0
);
}
else
if
(
cause
instanceof
DefaultDrmSessionManager
.
MissingSchemeDataException
)
{
return
new
ErrorInfo
(
PlaybackErrorEvent
.
ERROR_DRM_CONTENT_ERROR
,
/* subErrorCode= */
0
);
}
else
{
return
new
ErrorInfo
(
PlaybackErrorEvent
.
ERROR_DRM_OTHER
,
/* subErrorCode= */
0
);
}
}
else
if
(
cause
instanceof
FileDataSource
.
FileDataSourceException
&&
cause
.
getCause
()
instanceof
FileNotFoundException
)
{
@Nullable
Throwable
notFoundCause
=
checkNotNull
(
cause
.
getCause
()).
getCause
();
if
(
Util
.
SDK_INT
>=
21
&&
notFoundCause
instanceof
ErrnoException
&&
((
ErrnoException
)
notFoundCause
).
errno
==
OsConstants
.
EACCES
)
{
return
new
ErrorInfo
(
PlaybackErrorEvent
.
ERROR_IO_NO_PERMISSION
,
/* subErrorCode= */
0
);
}
else
{
return
new
ErrorInfo
(
PlaybackErrorEvent
.
ERROR_IO_FILE_NOT_FOUND
,
/* subErrorCode= */
0
);
}
}
else
{
return
new
ErrorInfo
(
PlaybackErrorEvent
.
ERROR_IO_OTHER
,
/* subErrorCode= */
0
);
}
}
else
if
(
isRendererExoPlaybackException
&&
(
rendererFormatSupport
==
C
.
FORMAT_UNSUPPORTED_TYPE
||
rendererFormatSupport
==
C
.
FORMAT_UNSUPPORTED_SUBTYPE
))
{
return
new
ErrorInfo
(
PlaybackErrorEvent
.
ERROR_DECODING_FORMAT_UNSUPPORTED
,
/* subErrorCode= */
0
);
}
else
if
(
isRendererExoPlaybackException
&&
rendererFormatSupport
==
C
.
FORMAT_EXCEEDS_CAPABILITIES
)
{
return
new
ErrorInfo
(
PlaybackErrorEvent
.
ERROR_DECODING_FORMAT_EXCEEDS_CAPABILITIES
,
/* subErrorCode= */
0
);
}
else
if
(
isRendererExoPlaybackException
&&
rendererFormatSupport
==
C
.
FORMAT_UNSUPPORTED_DRM
)
{
return
new
ErrorInfo
(
PlaybackErrorEvent
.
ERROR_DRM_SCHEME_UNSUPPORTED
,
/* subErrorCode= */
0
);
}
else
if
(
cause
instanceof
MediaCodecRenderer
.
DecoderInitializationException
)
{
@Nullable
String
diagnosticsInfo
=
((
MediaCodecRenderer
.
DecoderInitializationException
)
cause
).
diagnosticInfo
;
int
subErrorCode
=
Util
.
getErrorCodeFromPlatformDiagnosticsInfo
(
diagnosticsInfo
);
return
new
ErrorInfo
(
PlaybackErrorEvent
.
ERROR_DECODER_INIT_FAILED
,
subErrorCode
);
}
else
if
(
cause
instanceof
MediaCodecDecoderException
)
{
@Nullable
String
diagnosticsInfo
=
((
MediaCodecDecoderException
)
cause
).
diagnosticInfo
;
int
subErrorCode
=
Util
.
getErrorCodeFromPlatformDiagnosticsInfo
(
diagnosticsInfo
);
return
new
ErrorInfo
(
PlaybackErrorEvent
.
ERROR_DECODING_FAILED
,
subErrorCode
);
}
else
if
(
cause
instanceof
OutOfMemoryError
)
{
return
new
ErrorInfo
(
PlaybackErrorEvent
.
ERROR_DECODING_FAILED
,
/* subErrorCode= */
0
);
}
else
if
(
cause
instanceof
AudioSink
.
InitializationException
)
{
int
subErrorCode
=
((
AudioSink
.
InitializationException
)
cause
).
audioTrackState
;
return
new
ErrorInfo
(
PlaybackErrorEvent
.
ERROR_AUDIO_TRACK_INIT_FAILED
,
subErrorCode
);
}
else
if
(
cause
instanceof
AudioSink
.
WriteException
)
{
int
subErrorCode
=
((
AudioSink
.
WriteException
)
cause
).
errorCode
;
return
new
ErrorInfo
(
PlaybackErrorEvent
.
ERROR_AUDIO_TRACK_WRITE_FAILED
,
subErrorCode
);
}
else
if
(
Util
.
SDK_INT
>=
16
&&
cause
instanceof
MediaCodec
.
CryptoException
)
{
int
subErrorCode
=
((
MediaCodec
.
CryptoException
)
cause
).
getErrorCode
();
int
errorCode
=
getDrmErrorCode
(
subErrorCode
);
return
new
ErrorInfo
(
errorCode
,
subErrorCode
);
}
else
{
return
new
ErrorInfo
(
PlaybackErrorEvent
.
ERROR_PLAYER_OTHER
,
/* subErrorCode= */
0
);
}
}
@Nullable
private
static
DrmInitData
getDrmInitData
(
ImmutableList
<
TrackGroupInfo
>
trackGroupInfos
)
{
for
(
TrackGroupInfo
trackGroupInfo
:
trackGroupInfos
)
{
TrackGroup
trackGroup
=
trackGroupInfo
.
getTrackGroup
();
for
(
int
trackIndex
=
0
;
trackIndex
<
trackGroup
.
length
;
trackIndex
++)
{
if
(
trackGroupInfo
.
isTrackSelected
(
trackIndex
))
{
@Nullable
DrmInitData
drmInitData
=
trackGroup
.
getFormat
(
trackIndex
).
drmInitData
;
if
(
drmInitData
!=
null
)
{
return
drmInitData
;
}
}
}
}
return
null
;
}
private
static
int
getDrmType
(
DrmInitData
drmInitData
)
{
for
(
int
i
=
0
;
i
<
drmInitData
.
schemeDataCount
;
i
++)
{
UUID
uuid
=
drmInitData
.
get
(
i
).
uuid
;
if
(
uuid
.
equals
(
C
.
WIDEVINE_UUID
))
{
// TODO(b/77625596): Forward MediaDrm metrics to distinguish between L1 and L3 and to set
// the drm session id.
return
PlaybackMetrics
.
DRM_TYPE_WIDEVINE_L1
;
}
if
(
uuid
.
equals
(
C
.
PLAYREADY_UUID
))
{
return
PlaybackMetrics
.
DRM_TYPE_PLAY_READY
;
}
if
(
uuid
.
equals
(
C
.
CLEARKEY_UUID
))
{
return
PlaybackMetrics
.
DRM_TYPE_CLEARKEY
;
}
}
return
PlaybackMetrics
.
DRM_TYPE_OTHER
;
}
@SuppressLint
(
"SwitchIntDef"
)
// Only DRM error codes are relevant here.
private
static
int
getDrmErrorCode
(
int
mediaDrmErrorCode
)
{
switch
(
Util
.
getErrorCodeForMediaDrmErrorCode
(
mediaDrmErrorCode
))
{
case
PlaybackException
.
ERROR_CODE_DRM_PROVISIONING_FAILED
:
return
PlaybackErrorEvent
.
ERROR_DRM_PROVISIONING_FAILED
;
case
PlaybackException
.
ERROR_CODE_DRM_LICENSE_ACQUISITION_FAILED
:
return
PlaybackErrorEvent
.
ERROR_DRM_LICENSE_ACQUISITION_FAILED
;
case
PlaybackException
.
ERROR_CODE_DRM_DISALLOWED_OPERATION
:
return
PlaybackErrorEvent
.
ERROR_DRM_DISALLOWED_OPERATION
;
case
PlaybackException
.
ERROR_CODE_DRM_CONTENT_ERROR
:
return
PlaybackErrorEvent
.
ERROR_DRM_CONTENT_ERROR
;
case
PlaybackException
.
ERROR_CODE_DRM_SYSTEM_ERROR
:
default
:
return
PlaybackErrorEvent
.
ERROR_DRM_SYSTEM_ERROR
;
}
}
private
static
final
class
ErrorInfo
{
public
final
int
errorCode
;
public
final
int
subErrorCode
;
public
ErrorInfo
(
int
errorCode
,
int
subErrorCode
)
{
this
.
errorCode
=
errorCode
;
this
.
subErrorCode
=
subErrorCode
;
}
}
private
static
final
class
PendingFormatUpdate
{
public
final
Format
format
;
@C
.
SelectionReason
public
final
int
selectionReason
;
public
final
String
sessionId
;
public
PendingFormatUpdate
(
Format
format
,
@C
.
SelectionReason
int
selectionReason
,
String
sessionId
)
{
this
.
format
=
format
;
this
.
selectionReason
=
selectionReason
;
this
.
sessionId
=
sessionId
;
}
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment