天天看點

Android在滾動清單中實作視訊的播放(ListView & RecyclerView)

英文原文:implementing video playback

in a scrolled list (listview & recyclerview) 

本文将講解如何在清單中實作視訊播放。類似于諸如 facebook, instagram 或者 magisto這些熱門應用的效果:

Android在滾動清單中實作視訊的播放(ListView & RecyclerView)
Android在滾動清單中實作視訊的播放(ListView & RecyclerView)
Android在滾動清單中實作視訊的播放(ListView & RecyclerView)

這片文章基于開源項目: videoplayermanager。

所有的代碼和示例都在那裡。本文将跳過許多東西。是以如果你要真正了解它是如何工作的,最好下載下傳源碼,并結合源代碼一起閱讀本文。但是即便是沒有看源代碼,本文也能幫助你了解我們在幹什麼。

要實作我們需要的功能,我們必須解決兩個問題:

我們需要管理視訊的播放。在安卓中,我們有一個和surfaceview 一起工作的mediaplayer.class 類可以播放視訊。但是它有許多缺陷。我們不能在清單中使用普通的videoview 。videoview 繼承自surfaceview,而surfaceview并沒有ui同步緩沖區。這就導緻了在清單滾動的時候,正在播放的視訊需要跟上滾動的步伐。textureview 中有同步緩沖區,但是在android sdk version 15 中沒有基于textureview 的videoview。是以我們需要一個繼承自textureview

并和android mediaplayer一起工作的view。幾乎所有mediaplayer中的方法(prepare, start, stop 等等…)都調用和硬體相關的本地方法。當做了長于16ms的工作時(必然會),硬體會非常棘手然後我們就會看到一個卡頓的清單。這就是為什麼我們需要從背景線程調用它們。

我們還需要知道滾動清單中的哪個view目前處于活動狀态以切換播放的視訊。是以我們需要跟蹤滾動并定義可視範圍最大的view。

我們的目标是提供以下功能:

假設視訊正在播放。使用者滾動清單,一個新的item替代正在播放的item成為可視範圍最大的view。那麼現在我們需要停止目前視訊的播放并開始新的視訊。

主要功能就是:停止前一個播放,并僅在舊的播放停止之後才開始新的播放。

以下是一個例子:當你按下視訊的縮略圖-目前播放的視訊停止播放,另一個視訊開始播放。

Android在滾動清單中實作視訊的播放(ListView & RecyclerView)

我們要做的第一件事就是實作基于textureview的videoview 。我們不能在滾動清單中使用videoview 。這是因為如果在播放的過程中使用者滾動了清單,視訊的渲染會混亂。

我将把這個任務分為幾部分:

1.建立一個scalabletextureview,它是textureview 的子類,同時它還知道如何調整surfacetexture (視訊的播放就是運作在surfacetexture 上),并提供幾個類似于imageview scaletype的選項。

2.建立一個videoplayerview,它是scalabletextureview 的子類,含有跟mediaplayer.class相關的所有功能。這個自定義view封裝了mediaplayer.class并提供了和videoview十分類似的api。它具有mediaplayer的所有方法:setdatasource, prepare, start, stop, pause, reset, release。

video playback manager和 messageshandlerthread 一起工作,負責調用mediaplayer的方法。我們需要在單獨的線程中調用例如prepare(), start()等這樣的方法是因為它們直接和裝置的硬體關聯。我們也做過在ui線程中調用mediaplayer.reset(),但是player出了問題,而且這個方法對ui線程的阻塞幾乎有4分鐘!這就是為什麼我們不必使用異步的mediaplayer.prepareasync,而使用同步的mediaplayer.prepare。我們讓每件事情都在一個單獨的線程裡做。

至于開始一個新的播放的流程,這裡是mediaplayer要做的幾個步驟:

停止前一個播放。調用mediaplayer.stop() 方法來完成。

調用mediaplayer.reset()方法來重設mediaplayer 。這麼做的原因是在滾動清單中,view可能會被重用,我們希望所有的資源都能被釋放。

調用mediaplayer.release() 方法來釋放mediaplayer

清除mediaplayer的執行個體。當應該播放新的視訊的時候,新的mediaplayer執行個體将被建立。

為可視範圍最大的view建立mediaplayer執行個體。

調用mediaplayer.setdatasource(string url)來為新的mediaplayer 設定資料源。

調用mediaplayer.prepare(),這裡沒有必要調用異步的mediaplayer.prepareasync()。

調用mediaplayer.start()

等待實際的視訊開始。

所有的這些操作都被封裝在了在一個獨立線程中處理的message裡面,假如這是stop message,将調用videoplayerview.stop(),而它最終調用的是mediaplayer.stop()。我們需要自定義的messages是因為這樣我們就能設定目前狀态。我們可以知道它是正在停止還是已經停止或者其它狀态。它幫助我們控制目前處理的是什麼message,如果需要,我們可以對它做點什麼,比如,開始新的播放。

如果我們需要開始一個新的播放,我們隻需調用videoplayermanager中的一個方法。它向messageshandlerthread中添加了如下消息組合。

消息的運作是同步的,是以我們可以在任意時刻暫停隊列的處理,比如:

目前的視訊處于準備狀态(medaiplayer.prepare()被調用, mediaplayer.start() 在隊列中等待) ,使用者滾動别表是以我們需要在一個新的view上開始播放視訊。在這種情況下,我們:

暫停隊列的處理

移除所有挂起的消息

把“stop”, “reset”, “release”, “clear player instance” 發送到隊列。它們将在我們從“prepare”傳回的時候立即被調用。

發送 “create new media player instance”, “set current media player”(這個消息改變執行messages的mediaplayer對象), “set data source”, “prepare”, “start”消息。這些消息将在新的view上開始視訊的播放。

好了,這樣我們就有了按照我們需求運作視訊播放的工具:停止前一個播放然後顯示下一個。

這裡是library的gradle 依賴:

第一個問題是管理視訊的播放問題。第二個問題則是跟蹤哪個item的可見範圍最大并把播放切換到那個view。

這裡有一個名叫listitemsvisibilitycalculator 的接口和它的實作singlelistviewitemactivecalculator 就是做這個工作的。

為了計算清單中item的可見度,adapter中使用的model class必須實作listitem interface 。

listitemsvisibilitycalculator 跟蹤滾動的方向并在運作時計算item的可視度。item的可見度可能取決于清單中單個item裡面的任意view。由你來實作getvisibilitypercents() 方法。

在sample demo app中有一個預設的實作:

每個 view都需要知道如何計算它的可見百分比。滾動發生的時候,singlelistviewitemactivecalculator将從每個view 索取這個值,所有這裡的實作不能太複雜。

當某個鄰居的可見度超過了目前活動item,setactive 方法将被調用。就在這時應該切換播放。

還有一個作為listitemsvisibilitycalculator 和 listview 或者 recyclerview之間擴充卡的itemspositiongetter。這樣listitemsvisibilitycalculator 就不需要知道這到底是一個listview 還是recyclerview。它隻是做自己的工作。但是它需要知道一些itemspositiongetter提供的資訊:

考慮到業務邏輯和model分離的原則,把那樣的邏輯放在model 中是有點亂。但是做一些修改的也許能做到分離。不過雖然現在不怎麼好看,但是運作起來還是沒有問題。

下面是效果圖:

Android在滾動清單中實作視訊的播放(ListView & RecyclerView)

下面是這個library的 gradle dependency:

現在我們已經有了兩個能解決我們所有問題的library。讓我們把它們結合起來實作我們需要的功能。

這裡是取自使用了recyclerview的fragment 中的代碼:

1.初始化listitemsvisibilitycalculator,并傳遞一個list的引用給它。

defaultsingleitemcalculatorcallback 隻是在活動view改變的時候調用了 listitem.setactive 方法,但是你可以自己重寫它,做自己想做的事情:

\2. 初始化videoplayermanager。

\3. 為recyclerview設定on scroll listener 并傳遞scroll events 到 list visibility utils。

\4. 建立itemspositiongetter。

\5.同時我們在onresume 中調用一個方法以便在我們打開螢幕的時候馬上開始計算可見範圍最大的item。

這樣我們就得到了一組在清單中播放的視訊。

Android在滾動清單中實作視訊的播放(ListView & RecyclerView)

總的來說,這隻是對最重要部分的解釋。在sample  app中有更多的代碼:

https://github.com/danylovolokh/videoplayermanager

要了解更多細節請檢視源代碼。

cheers ;)

繼續閱讀