ExoPlayer 支援多種播放分析需求。最終,分析的重點在於收集、解讀、匯總,然後匯總播放的資料。這些資料可用於裝置,例如記錄、偵錯或用於日後的播放決策,或是回報給伺服器以監控所有裝置的播放。
分析系統通常需要先收集事件,然後再進一步處理以使資料具有參考價值:
- 事件收集:只要在
ExoPlayer
執行個體上註冊AnalyticsListener
即可達成此效果。已註冊的數據分析事件監聽器會在使用玩家時收到事件。每個事件都會與播放清單中的對應媒體項目,以及播放位置和時間戳記中繼資料建立關聯。 - 事件處理:部分數據分析系統會將原始事件上傳至伺服器,所有事件處理作業都在伺服器端執行。您也可以在裝置上處理事件,而且這可能比較簡單,或減少需要上傳的資訊量。ExoPlayer 提供
PlaybackStatsListener
,可讓您執行下列處理步驟:- 事件解讀:為了進行分析,事件必須在單次播放的情境下解讀。舉例來說,玩家狀態變更為
STATE_BUFFERING
的原始事件,可能會對應到在跳轉後發生的初始緩衝、重新緩衝或緩衝處理。 - 狀態追蹤:這個步驟會將事件轉換為計數器。例如,狀態變更事件可以轉換為計數器,追蹤每個播放狀態所花費的時間。結果是單次播放作業的一組基本數據分析資料值。
- 匯總:此步驟會合併多個播放期間的分析資料,通常是新增計數器。
- 計算摘要指標:許多最實用的指標都是以其他方式計算平均值或結合基本數據分析資料值。可以計算單一或多次播放的摘要指標。
- 事件解讀:為了進行分析,事件必須在單次播放的情境下解讀。舉例來說,玩家狀態變更為
使用 AnalyticsListener 收集事件
播放器的原始播放事件會回報給 AnalyticsListener
實作。您可以輕鬆新增自己的事件監聽器,並僅覆寫感興趣的方法:
Kotlin
exoPlayer.addAnalyticsListener( object : AnalyticsListener { override fun onPlaybackStateChanged( eventTime: EventTime, @Player.State state: Int ) {} override fun onDroppedVideoFrames( eventTime: EventTime, droppedFrames: Int, elapsedMs: Long, ) {} } )
Java
exoPlayer.addAnalyticsListener( new AnalyticsListener() { @Override public void onPlaybackStateChanged( EventTime eventTime, @Player.State int state) {} @Override public void onDroppedVideoFrames( EventTime eventTime, int droppedFrames, long elapsedMs) {} });
傳遞至每個回呼的 EventTime
會將事件與播放清單中的媒體項目,以及播放位置和時間戳記中繼資料建立關聯:
realtimeMs
:事件的實際時鐘時間。timeline
、windowIndex
和mediaPeriodId
:定義事件所屬的播放清單和項目。mediaPeriodId
包含選用額外資訊,例如指出事件是否屬於項目內的廣告。eventPlaybackPositionMs
:事件發生時項目中的播放位置。currentTimeline
、currentWindowIndex
、currentMediaPeriodId
和currentPlaybackPositionMs
:如上所述,但對目前播放中的項目而言。目前播放中的項目可能與事件所屬的項目不同,例如,如果事件對應到下一個要播放的項目的預先緩衝處理。
使用 PlaybackStatsListener 處理事件
PlaybackStatsListener
是實作裝置端事件處理的 AnalyticsListener
。計算 PlaybackStats
,其中包含計數器和衍生指標,包括:
- 摘要指標,例如總播放時間。
- 自動調整播放品質指標,例如平均影片解析度。
- 轉譯品質指標,例如影格遺失率。
- 資源用量指標,例如透過網路讀取的位元組數。
您可以在 PlaybackStats
Javadoc 中找到可用數量和衍生指標的完整清單。
PlaybackStatsListener
會針對播放清單中的每個媒體項目,以及插入這些項目中的每個用戶端廣告分別計算 PlaybackStats
。您可以向 PlaybackStatsListener
提供回呼,藉此瞭解已結束播放的內容,並使用傳遞至回呼的 EventTime
識別結束播放的進度。您可以為多次播放的匯總數據分析資料進行匯總。您也可以使用 PlaybackStatsListener.getPlaybackStats()
隨時查詢目前播放工作階段的 PlaybackStats
。
Kotlin
exoPlayer.addAnalyticsListener( PlaybackStatsListener(/* keepHistory= */ true) { eventTime: EventTime?, playbackStats: PlaybackStats?, -> // Analytics data for the session started at `eventTime` is ready. } )
Java
exoPlayer.addAnalyticsListener( new PlaybackStatsListener( /* keepHistory= */ true, (eventTime, playbackStats) -> { // Analytics data for the session started at `eventTime` is ready. }));
PlaybackStatsListener
的建構函式可讓您選擇保留已處理事件的完整記錄。請注意,視播放長度和事件數量而定,這可能會產生不明記憶體負擔。因此,除非您需要存取已處理事件的完整記錄,而非只有最終的數據分析資料,否則建議您開啟這項功能。
請注意,PlaybackStats
使用擴充狀態組合來指出媒體的狀態,同時也顯示使用者意圖播放,以及更詳細的資訊,例如播放中斷或結束的原因:
播放狀態 | 使用者玩遊戲的意願 | 沒有遊戲意圖 |
---|---|---|
播放前 | JOINING_FOREGROUND |
NOT_STARTED 、JOINING_BACKGROUND |
播放中 | PLAYING |
|
播放中斷 | BUFFERING 、SEEKING |
PAUSED 、PAUSED_BUFFERING 、SUPPRESSED 、SUPPRESSED_BUFFERING 、INTERRUPTED_BY_AD |
結束狀態 | ENDED 、STOPPED 、FAILED 、ABANDONED |
使用者意圖播放是區分使用者主動等待播放作業與被動等待時間的時間,舉例來說,PlaybackStats.getTotalWaitTimeMs
會傳回 JOINING_FOREGROUND
、BUFFERING
和 SEEKING
狀態花費的總時間,但不會傳回暫停播放的時間。同樣地,PlaybackStats.getTotalPlayAndWaitTimeMs
會傳回使用者意圖玩遊戲的總時間,也就是活動總等待時間,以及處於 PLAYING
狀態的總時間。
已處理和解讀的事件
您可以將 PlaybackStatsListener
與 keepHistory=true
搭配使用,記錄已處理及解譯的事件。產生的 PlaybackStats
會包含下列事件清單:
playbackStateHistory
:使用EventTime
開始套用的已排序播放狀態清單。此外,您也可以使用PlaybackStats.getPlaybackStateAtTime
查詢特定壁掛時間的狀態。mediaTimeHistory
:實際時鐘時間和媒體時間組合的歷史記錄,可讓您重新建構媒體的哪些部分是何時播放。您也可以使用PlaybackStats.getMediaTimeMsAtRealtimeMs
查詢特定實際時鐘時間的播放位置。videoFormatHistory
和audioFormatHistory
:在播放期間使用的影片和音訊格式排序清單,並在EventTime
開始取用時使用。fatalErrorHistory
和nonFatalErrorHistory
:已排序的EventTime
嚴重和非重大錯誤清單。嚴重錯誤是指結束播放,而非重大錯誤可能可以復原。
單一播放的數據分析資料
即使您搭配 keepHistory=false
使用 PlaybackStatsListener
,系統也會自動收集這項資料。最終值是您可以在 PlaybackStats
Javadoc 中找到的公開欄位,以及 getPlaybackStateDurationMs
傳回的播放狀態持續時間。為方便起見,您也可以找到 getTotalPlayTimeMs
和 getTotalWaitTimeMs
等方法,可傳回特定播放狀態組合的時間長度。
Kotlin
Log.d( "DEBUG", "Playback summary: " + "play time = " + playbackStats.totalPlayTimeMs + ", rebuffers = " + playbackStats.totalRebufferCount )
Java
Log.d( "DEBUG", "Playback summary: " + "play time = " + playbackStats.getTotalPlayTimeMs() + ", rebuffers = " + playbackStats.totalRebufferCount);
多次播放的匯總分析資料
您可以呼叫 PlaybackStats.merge
將多個 PlaybackStats
合併。產生的 PlaybackStats
將包含所有合併播放的匯總資料。請注意,由於這些資料無法匯總,因此不包含個別播放事件的記錄。
PlaybackStatsListener.getCombinedPlaybackStats
可用來取得 PlaybackStatsListener
生命週期內收集到的所有數據分析資料的匯總檢視畫面。
計算摘要指標
除了基本的數據分析資料外,PlaybackStats
也提供多種計算摘要指標的方法。
Kotlin
Log.d( "DEBUG", "Additional calculated summary metrics: " + "average video bitrate = " + playbackStats.meanVideoFormatBitrate + ", mean time between rebuffers = " + playbackStats.meanTimeBetweenRebuffers )
Java
Log.d( "DEBUG", "Additional calculated summary metrics: " + "average video bitrate = " + playbackStats.getMeanVideoFormatBitrate() + ", mean time between rebuffers = " + playbackStats.getMeanTimeBetweenRebuffers());
進階主題
為數據分析資料與播放中繼資料建立關聯
收集個別播放的數據分析資料時,您可能會想將播放數據分析資料與所播放媒體的中繼資料建立關聯。
建議使用 MediaItem.Builder.setTag
設定媒體專屬中繼資料。媒體標記是原始事件所回報的 EventTime
的一部分,且當 PlaybackStats
完成時,因此可以在處理對應的數據分析資料時輕鬆擷取:
Kotlin
PlaybackStatsListener(/* keepHistory= */ false) { eventTime: EventTime, playbackStats: PlaybackStats -> val mediaTag = eventTime.timeline .getWindow(eventTime.windowIndex, Timeline.Window()) .mediaItem .localConfiguration ?.tag // Report playbackStats with mediaTag metadata. }
Java
new PlaybackStatsListener( /* keepHistory= */ false, (eventTime, playbackStats) -> { Object mediaTag = eventTime.timeline.getWindow(eventTime.windowIndex, new Timeline.Window()) .mediaItem .localConfiguration .tag; // Report playbackStats with mediaTag metadata. });
回報自訂數據分析事件
如果您需要在數據分析資料中加入自訂事件,就必須將這些事件儲存在自己的資料結構中,然後再與回報的 PlaybackStats
合併。若是這樣,您可以擴充 DefaultAnalyticsCollector
,以便為自訂事件產生 EventTime
例項,並將其傳送至已註冊的事件監聽器,如以下範例所示。
Kotlin
private interface ExtendedListener : AnalyticsListener { fun onCustomEvent(eventTime: EventTime) } private class ExtendedCollector : DefaultAnalyticsCollector(Clock.DEFAULT) { fun customEvent() { val eventTime = generateCurrentPlayerMediaPeriodEventTime() sendEvent(eventTime, CUSTOM_EVENT_ID) { listener: AnalyticsListener -> if (listener is ExtendedListener) { listener.onCustomEvent(eventTime) } } } } // Usage - Setup and listener registration. val player = ExoPlayer.Builder(context).setAnalyticsCollector(ExtendedCollector()).build() player.addAnalyticsListener( object : ExtendedListener { override fun onCustomEvent(eventTime: EventTime?) { // Save custom event for analytics data. } } ) // Usage - Triggering the custom event. (player.analyticsCollector as ExtendedCollector).customEvent()
Java
private interface ExtendedListener extends AnalyticsListener { void onCustomEvent(EventTime eventTime); } private static class ExtendedCollector extends DefaultAnalyticsCollector { public ExtendedCollector() { super(Clock.DEFAULT); } public void customEvent() { AnalyticsListener.EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime(); sendEvent( eventTime, CUSTOM_EVENT_ID, listener -> { if (listener instanceof ExtendedListener) { ((ExtendedListener) listener).onCustomEvent(eventTime); } }); } } // Usage - Setup and listener registration. ExoPlayer player = new ExoPlayer.Builder(context).setAnalyticsCollector(new ExtendedCollector()).build(); player.addAnalyticsListener( (ExtendedListener) eventTime -> { // Save custom event for analytics data. }); // Usage - Triggering the custom event. ((ExtendedCollector) player.getAnalyticsCollector()).customEvent();