簡述
視訊播放是我們開發中比較常見的場景。這兩年關於視訊方面的熱度不斷提升,可以說前兩年是直播年,今年是小視訊年,各種短視訊應用鋪天蓋地。對於視訊的業務場景也越來越豐富,功能也越來越多。對於我們開發來說播放相關元件的程式碼變得也越來越複雜,管理維護成本也越來越高,面對不斷迭代的業務,我們需要一種有效的方案來應對這種頻繁的業務變化。
這幾年一直在做視訊相關的業務,手機端和TV端均做過適配開發。MediaPlayer、exoplayer、ijkplayer、VLC、FFmpeg等都摸索使用過。這一路遇到很多問題……說多了都是淚,為了適應多變的產品需求,中間重構了N多個版本。最終PlayerBase也就誕生了。PlayerBase3 版本進行了完整重構設計,目前大致框架基本已穩定下來。對於大部分應用視訊播放元件場景都能輕鬆處理。
^_^ star傳送門--->專案地址:github.com/jiajunhui/P…
QQ交流群:600201778 ,有問題群裡直接提出,看到後會一一解答。
P圖技術有限,文中圖片就湊合著看吧!
框架簡介
請注意! 請注意! 請注意! PlayerBase區別於大部分播放器封裝庫。
PlayerBase是一種將解碼器和播放檢視元件化處理的解決方案框架。您需要什麼解碼器實現框架定義的抽象引入即可,對於檢視,無論是播放器內的控制檢視還是業務檢視,均可以做到元件化處理。將播放器的開發變得清晰簡單,更利於產品的迭代。
PlayerBase不會為您做任何多餘的功能業務元件,有別於大部分播放器封裝庫的通過配置或者繼承然後重寫然後定製你需要的功能元件和遮蔽你不需要的功能元件(這種之前我也經歷過,上層可能需要經常改動,感覺很low!!!)。正確的方向應該是需要什麼元件就擴充新增什麼元件,不需要時移除即可,而不是已經提供了該元件去選擇用不用。
功能特色
- 檢視的元件化處理
- 檢視元件的高複用、低耦合
- 解碼方案的元件化、配置化管理
- 檢視元件的完全定製
- 檢視元件的熱插拔,用時新增不用時移除
- 自定義接入各種解碼方案
- 解碼方案的切換
- 支援倍速播放
- 支援Window模式播放
- 支援Window模式的無縫續播
- 支援列表模式的無縫續播
- 支援跨頁面無縫續播
- 支援調整畫面顯示比例
- 支援動態調整渲染檢視型別
- 支援VideoView切角處理,邊緣陰影效果
- 提供自定義資料提供者
- 統一的事件下發機制
- 擴充套件事件的新增
- 等功能……
部分使用示例
- 解碼配置和框架初始化
public class App extends Application {
@Override
public void onCreate() {
//...
//如果您想使用預設的網路狀態事件生產者,請新增此行配置。
//並需要新增許可權 android.permission.ACCESS_NETWORK_STATE
PlayerConfig.setUseDefaultNetworkEventProducer(true);
//設定預設解碼器
int defaultPlanId = 1;
PlayerConfig.addDecoderPlan(new DecoderPlan(defaultPlanId, IjkPlayer.class.getName(), "IjkPlayer"));
PlayerConfig.setDefaultPlanId(defaultPlanId);
//初始化庫
PlayerLibrary.init(this);
}
}
複製程式碼
- 組裝元件(新增您需要的元件【元件來自使用者自定義,框架不提供任何檢視元件】)
ReceiverGroup receiverGroup = new ReceiverGroup();
//Loading元件
receiverGroup.addReceiver(KEY_LOADING_COVER, new LoadingCover(context));
//Controller元件
receiverGroup.addReceiver(KEY_CONTROLLER_COVER, new ControllerCover(context));
//CompleteCover元件
receiverGroup.addReceiver(KEY_COMPLETE_COVER, new CompleteCover(context));
//Error元件
receiverGroup.addReceiver(KEY_ERROR_COVER, new ErrorCover(context));
複製程式碼
- 設定元件啟動播放
BaseVideoView videoView = findViewById(R.id.videoView);
videoView.setReceiverGroup(receiverGroup);
DataSource data = new DataSource("http://url...");
videoView.setDataSource(data);
videoView.start();
複製程式碼
- 事件的監聽
//player event
videoView.setOnPlayerEventListener(new OnPlayerEventListener(){
@Override
public void onPlayerEvent(int eventCode, Bundle bundle){
//...
}
});
//receiver event
videoView.setOnReceiverEventListener(new OnReceiverEventListener(){
@Override
public void onReceiverEvent(int eventCode, Bundle bundle) {
//...
}
});
複製程式碼
詳細使用示例請參閱github專案主頁及wiki介紹
框架的設計
檢視的處理
別小看一個小小的播放器,裡面真的是別有洞天。有時檢視元件複雜到你懷疑人生。
我們先看下播放器開發時常見的一些檢視場景:
以上是我們最常見到的一些檢視(其實還有很多,比如清晰度切換、視訊列表、播放完成提示頁等等),這些檢視如果沒有一個行之有效的方案來進行管理,將逐漸會亂到失控。
上面只是列出了控制器檢視、載入檢視、手勢檢視、錯誤檢視、彈幕檢視和廣告檢視,這一股腦的檢視都是和播放緊密相連的,完全由播放狀態驅動,檢視之間可能共存、可能制約。
那麼這些檢視如何進行統一的管理呢?光佈局檔案就夠喝一壺了吧,即便用include來管理依然擺脫不了顯示層級的管理問題。要是一股腦全寫到一個xml中,想想都可怕……, 改進型的一般都是把每個元件封裝成View了,然後再分別寫到佈局中,顯然比前一種要輕鬆一些。但是,但是播放器和元件間的通訊、元件與元件間的通訊是個問題。依然有問題存在:
- 元件佈局的層級完全由佈局檔案決定了,想調整隻能去修改佈局檔案。並不友好。
- 元件和播放器完全捆綁了,耦合度相當高,播放器和元件,元件和元件間的通訊完全直接使用引用去操作,如果產品說某個元件要去掉或者大改,你就哭吧,改不好手一哆嗦就有可能帶來一堆bug。元件耦合度高,並不支援插拔。這是最大阻礙。
- 元件的複用困難。
接下來,且看PlayerBase如何做。
接收者Receiver與覆蓋層Cover的概念
做過播放器開發的應該都很清楚一點,所有檢視的工作都是由狀態事件來驅動的,這是一條主線。有可能是來自播放器的事件(比如解碼器出錯了),也有可能是來自某個檢視的事件(比如手勢調節播放進度),還有可能是外部事件(比如網路狀態變化)。
這些資訊我們可以歸結為
- 檢視是事件接收者,也是事件的生產者
- 解碼器是事件生產者
- 可能有外來的事件生產者
也就是說我們把檢視當做事件接收者,同時檢視具備傳送事件的能力。
解碼器不斷髮出自己工作狀態的事件要傳遞給檢視。
外部的某些事件也需要傳遞給檢視
至此,框架內部定義了事件接收者的概念,接收者作為事件消費者的同時也能生產事件,而覆蓋層繼承自接收者引入了檢視View。
public abstract class BaseReceiver implements IReceiver {
//...
protected final void notifyReceiverEvent(int eventCode, Bundle bundle){
//..
}
/**
* all player event dispatch by this method.
*/
void onPlayerEvent(int eventCode, Bundle bundle);
/**
* error event.
*/
void onErrorEvent(int eventCode, Bundle bundle);
/**
* receivers event.
*/
void onReceiverEvent(int eventCode, Bundle bundle);
/**
* you can call this method dispatch private event for a receiver.
*
* @return Bundle Return value after the receiver's response, nullable.
*/
@Nullable
Bundle onPrivateEvent(int eventCode, Bundle bundle);
}
複製程式碼
public abstract class BaseCover extends BaseReceiver{
//...
public abstract View onCreateCoverView(Context context);
//...
}
複製程式碼
且看程式碼,有播放器的事件、有錯誤事件、有元件(Receiver)間的事件。這眾多事件如何下發呢,如果有N多個接收者呢,如何破?
接收者組管理(ReceiverGroup)
ReceiverGroup的出現目的就是對眾多接收者進行統一的管理,統一的事件下發,當然還有下面的資料共享問題。來張圖:
在ReceiverGroup中包含Cover(其實也是Receiver)和Receiver,提供了Receiver的新增、移除、遍歷、銷燬等操作。當有事件需要下發時,便可通過ReceiverGroup進行統一的遍歷下發。
public interface IReceiverGroup {
void setOnReceiverGroupChangeListener(OnReceiverGroupChangeListener onReceiverGroupChangeListener);
/**
* add a receiver, you need put a unique key for this receiver.
* @param key
* @param receiver
*/
void addReceiver(String key, IReceiver receiver);
/**
* remove a receiver by key.
* @param key
*/
void removeReceiver(String key);
/**
* loop all receivers
* @param onLoopListener
*/
void forEach(OnLoopListener onLoopListener);
/**
* loop all receivers by a receiver filter.
* @param filter
* @param onLoopListener
*/
void forEach(OnReceiverFilter filter, OnLoopListener onLoopListener);
/**
* get receiver by key.
* @param key
* @param <T>
* @return
*/
<T extends IReceiver> T getReceiver(String key);
/**
* get the ReceiverGroup group value.
* @return
*/
GroupValue getGroupValue();
/**
* clean receivers.
*/
void clearReceivers();
}
複製程式碼
元件間資料共享(GroupValue)
播放器開發中很多時候我們需要依據某個檢視的狀態來限制另外檢視的功能或狀態,比如當處於載入中時禁止拖動進度條或者播放出錯顯示error後禁止其他檢視操作等等。這些都屬於狀態上的相互制約。
GroupValue就相當於提供了一個共享的資料池,當某個資料被重新整理時,監聽該資料的回撥介面能及時收到通知,當然也可以直接去主動獲取資料狀態。你可以指定你要監聽那些資料的更新事件,如果您註冊了您要監聽的資料的key值,其對應的value被更新時,您就會收到回撥。然後您可以在回撥中進行UI檢視的控制。
public class CustomCover extends BaseCover{
//...
@Override
public void onReceiverBind() {
super.onReceiverBind();
getGroupValue().registerOnGroupValueUpdateListener(mOnGroupValueUpdateListener);
}
//...
private IReceiverGroup.OnGroupValueUpdateListener mOnGroupValueUpdateListener =
new IReceiverGroup.OnGroupValueUpdateListener() {
@Override
public String[] filterKeys() {
return new String[]{ DataInter.Key.KEY_COMPLETE_SHOW };
}
@Override
public void onValueUpdate(String key, Object value) {
//...
}
};
//...
@Override
public void onReceiverUnBind() {
super.onReceiverUnBind();
getGroupValue().unregisterOnGroupValueUpdateListener(mOnGroupValueUpdateListener);
}
}
複製程式碼
檢視的佈局管理(分級填充)
上文中常見的檢視元件,我們在使用中肯定會遇到覆蓋優先順序的問題。舉個例子,比如Error檢視出現後其他的檢視一概不可見,也就是說Error檢視的優先順序是最高的,誰都不能擋著它,我們建立了一個個的Cover檢視,對於檢視的放置就需要一個檢視的優先順序標量(CoverLevel)來進行控制,不同的Level的Cover檢視會被放置於不同級別的容器內。
總結為以下:
- 指定Cover的優先順序CoverLevel
- Cover元件被新增時自動根據Level值進行分別放置
示意圖
程式碼示例public class CustomCover extends BaseCover{
//...
@Override
public int getCoverLevel() {
return ICover.COVER_LEVEL_LOW;
}
//...
}
複製程式碼
預設的檢視容器管理器
public class DefaultLevelCoverContainer extends BaseLevelCoverContainer {
//...
@Override
protected void onAvailableCoverAdd(BaseCover cover) {
super.onAvailableCoverAdd(cover);
switch (cover.getCoverLevel()){
case ICover.COVER_LEVEL_LOW:
mLevelLowCoverContainer.addView(cover.getView(),getNewMatchLayoutParams());
break;
case ICover.COVER_LEVEL_MEDIUM:
mLevelMediumCoverContainer.addView(cover.getView(),getNewMatchLayoutParams());
break;
case ICover.COVER_LEVEL_HIGH:
mLevelHighCoverContainer.addView(cover.getView(),getNewMatchLayoutParams());
break;
}
}
//...
}
複製程式碼
元件的層級關係
如圖:
事件生產者(EventProducer)
顧名思義,就是它是產生事件的源。比如系統網路狀態發生了變化,發出了通知,然後各個應用根據自己的情況來調整顯示或設定等。又或者電池電量的變化和低電量預警通知事件等。
再比如,我們上文中的彈幕檢視中需要顯示彈幕資料,彈幕資料來自伺服器,我們需要源源不斷的從伺服器上取資料,然後顯示在彈幕檢視。取回資料傳給檢視的這個過程我們可以將其看作是一個事件生產者在不斷生產彈幕資料更新事件,彈幕資料更新時不斷將事件傳送給彈幕檢視來重新整理顯示。
框架內自帶了一個網路變化事件生產者的示例:
public class NetworkEventProducer extends BaseEventProducer {
//...
private Handler mHandler = new Handler(Looper.getMainLooper()){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what){
case MSG_CODE_NETWORK_CHANGE:
int state = (int) msg.obj;
//...將網路狀態傳送出去
getSender().sendInt(InterKey.KEY_NETWORK_STATE, state);
PLog.d(TAG,"onNetworkChange : " + state);
break;
}
}
};
//...
public NetworkEventProducer(Context context){
//...
}
//...
public static class NetChangeBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
//...
//post state message
}
//...
}
}
複製程式碼
由於事件生產者所發出的事件是針對Receiver的,所以會被回撥到onReceiverEvent()中,如果傳送的是key-value的資料,會被放置於GroupValue中。如下程式碼:
public class CustomCover extends BaseCover{
//...
@Override
public void onReceiverEvent(int eventCode, Bundle bundle) {
//...
}
//...
}
複製程式碼
資料提供者(DataProvider)
DataProvider是為了播控的統一以及使用上的優雅而設計的。
在開發中,我們可能會遇到如下場景:你拿到的資料來源可能只是個id之類的標識,並不是能直接播放的uri或者url,需要你再用這個id去請求一個介面才能拿到播放的源地址。通常我們都是先去請求介面,然後在成功回撥中用拿到的源資料再設定給播放器去播放。
DataProvider的設計就是為了將此過程獨立出來包裝為一個資料提供者(其實也可以叫資料生產者),拿到資料後傳送出去即可。而您只需要把那個id標識給DataProvider即可,接下來的過程就由DataProvider來完成了。DataProvider的具體實現需要由使用者完成。public class MonitorDataProvider extends BaseDataProvider {
//...
public MonitorDataProvider(){
//...
}
private Handler mHandler = new Handler(Looper.getMainLooper());
@Override
public void handleSourceData(DataSource sourceData) {
this.mDataSource = sourceData;
//...provider start
onProviderDataStart();
//...
//...將資料回撥出去
onProviderMediaDataSuccess(bundle);
//...
//...異常時
onProviderError(-1, null)
}
//...
@Override
public void cancel() {
//...cancel something
}
@Override
public void destroy() {
//...destroy something
}
}
複製程式碼
注意: 資料提供者必須要設定在啟動播放前。
功能使用
VideoView的使用
大致歸結為以下步驟:
- 初始化VideoView,並設定相應的監聽事件或者資料提供者等
- 使用ReceiverGroup組裝需要的元件Cover和Receiver
- 把元件設定給VideoView
- 設定資料啟動播放
- 暫停恢復播放等操作
- 銷燬播放器
public class VideoViewActivity extends AppCompatActivity implements OnPlayerEventListener{
//...
BaseVideoView mVideoView;
@Override
public void onCreate(Bundle saveInstance){
super.onCreate(saveInstance);
mVideoView = findViewById(R.id.videoView);
mVideoView.setOnPlayerEventListener(this);
//設定資料提供者 MonitorDataProvider
MonitorDataProvider dataProvider = new MonitorDataProvider();
mVideoView.setDataProvider(dataProvider);
//...
ReceiverGroup receiverGroup = new ReceiverGroup();
//Loading元件
receiverGroup.addReceiver(KEY_LOADING_COVER, new LoadingCover(context));
//Controller元件
receiverGroup.addReceiver(KEY_CONTROLLER_COVER, new ControllerCover(context));
//CompleteCover元件
receiverGroup.addReceiver(KEY_COMPLETE_COVER, new CompleteCover(context));
//Error元件
receiverGroup.addReceiver(KEY_ERROR_COVER, new ErrorCover(context));
//...
DataSource data = new DataSource("monitor_id");
videoView.setDataSource(data);
videoView.start();
}
//...
public void onPlayerEvent(int eventCode, Bundle bundle){
switch (eventCode){
case OnPlayerEventListener.PLAYER_EVENT_ON_VIDEO_RENDER_START:
//...
break;
case OnPlayerEventListener.PLAYER_EVENT_ON_PLAY_COMPLETE:
//...
break;
}
}
//...
@Override
public void onPause(){
super.onPause();
mVideoView.pause();
//...
}
@Override
public void onResume(){
super.onResume();
mVideoView.onResume();
//...
}
@Override
public void onDestroy(){
super.onDestroy();
mVideoView.stopPlayback();
//...
}
}
複製程式碼
AVPlayer的使用
如果您想直接使用AVPlayer自己進行處理播放,那麼大致步驟如下:
- 初始化一個AVPlayer物件。
- 初始化一個SuperContainer物件,將ReceiverGroup設定到SuperContainer中。
- 使用SuperContainer設定一個渲染檢視Render,然後自己處理RenderCallBack並關聯解碼器。
SuperContainer mSuperContainer = new SuperContainer(context);
ReceiverGroup receiverGroup = new ReceiverGroup();
//...add some covers
receiverGroup.addReceiver(KEY_LOADING_COVER, new LoadingCover(context));
mSuperContainer.setReceiverGroup(receiverGroup);
//...
final RenderTextureView render = new RenderTextureView(mAppContext);
render.setTakeOverSurfaceTexture(true);
//....
mPlayer.setOnPlayerEventListener(new OnPlayerEventListener() {
@Override
public void onPlayerEvent(int eventCode, Bundle bundle) {
//...此處需要根據事件自行實現一些特定的設定
//...比如視訊的尺寸需要傳遞Render重新整理測量或者視訊的角度等等
//將事件分發給子檢視
mSuperContainer.dispatchPlayEvent(eventCode, bundle);
}
});
mPlayer.setOnErrorEventListener(new OnErrorEventListener() {
@Override
public void onErrorEvent(int eventCode, Bundle bundle) {
//將事件分發給子檢視
mSuperContainer.dispatchErrorEvent(eventCode, bundle);
}
});
//...
render.setRenderCallback(new IRender.IRenderCallback() {
@Override
public void onSurfaceCreated(IRender.IRenderHolder renderHolder, int width, int height) {
mRenderHolder = renderHolder;
bindRenderHolder(mRenderHolder);
}
@Override
public void onSurfaceChanged(IRender.IRenderHolder renderHolder, int format, int width, int height) {
}
@Override
public void onSurfaceDestroy(IRender.IRenderHolder renderHolder) {
mRenderHolder = null;
}
});
mSuperContainer.setRenderView(render.getRenderView());
mPlayer.setDataSource(dataSource);
mPlayer.start();
複製程式碼
如果非必須,請儘量使用框架封裝好的BaseVideoView進行播放,框架相對來說處理的比較完善且提供了豐富的回撥和定製性。
關聯助手的使用(RelationAssist)
現在的短視訊應用都有這樣的場景:
- 列表中播放
- 列表跳詳情自然過渡無停頓播放
對於第一條在列表中播放,理論上VideoView就能完成,但是VideoView用在列表中量級較重,不太適合。需要一個輕量化處理的方案。
而對於第二條,VideoView就不行了,VideoView是對解碼器進行了包裝,當跳到下一個頁面時,是一個新的頁面自然有新的檢視,無法使用前一個頁面的播放器例項去渲染當前頁面播放。
其實對於這種無縫的續播,原理很簡單。就是不同的渲染檢視使用同一個解碼例項即可。可以簡單比作一個MediaPlayer去不斷設定不同的surface呈現播放。如果自己處理這個過程的話想對比較繁瑣,你需要處理Render的回撥並關聯給解碼器,還需要自己處理Render的測量以及顯示比例、角度等等問題。
RelationAssist 就是為了簡化這個過程而設計的。在不同的頁面或檢視切換播放時,您只需要提供並傳入對應位置的檢視容器(ViewGroup型別)即可。內部複雜的設定項和關聯由RelationAssist完成。
public class TestActivity extends AppcompatActivity{
//...
RelationAssist mAssist;
ViewGroup view2;
public void onCreate(Bundle saveInstance){
super.onCreate(saveInstance);
//...
mAssist = new RelationAssist(this);
mAssist.setEventAssistHandler(eventHandler);
mReceiverGroup = ReceiverGroupManager.get().getLiteReceiverGroup(this);
mAssist.setReceiverGroup(mReceiverGroup);
DataSource dataSource = new DataSource();
dataSource.setData("http://...");
dataSource.setTitle("xxx");
mAssist.setDataSource(dataSource);
mAssist.attachContainer(mVideoContainer);
mAssist.play();
//...
switchPlay(view2);
}
//...
private void switchPlay(ViewGroup container){
mAssist.attachContainer(container);
}
}
複製程式碼
如果您想跨頁面進行關聯,只需要自己將RelationAssist包裝為一個單例即可。此處不做程式碼展示,詳細程式碼可參見github專案demo程式碼。
事件助手處理器(EventAssistHandler)
檢視中的一些基本操作,比如暫停播放、重播、重試、恢復播放等等,這些事件最終都要傳遞給解碼器進行相關操作。可能還有使用者自定義的事件比如播放下一個或上一個等。
對於基本的操作事件(暫停、恢復、重播等),框架內部可自動完成,而使用者自定的事件需要讓使用者自行處理。框架內部BaseVideoView和RelationAssist均做了EventAssistHandler的對接,使用時需要傳入一個可用的事件處理器物件,可根據不同的事件引數進行相應處理。如下程式碼:
mVideoView.setOnVideoViewEventHandler(new OnVideoViewEventHandler(){
@Override
public void onAssistHandle(BaseVideoView assist, int eventCode, Bundle bundle) {
//基本的事件處理已在父類super中完成,如果需要重寫,重寫相應方法即可。
super.onAssistHandle(assist, eventCode, bundle);
switch (eventCode){
case DataInter.Event.EVENT_CODE_REQUEST_NEXT:
//...播放下一個
break;
}
}
});
複製程式碼
Window模式播放
我們有時可能為了不打斷使用者的瀏覽需要小窗播放。框架特意設計了window播放的使用。框架提供了兩種window相關的元件。
- WindowVideoView
- FloatWindow
WindowVideoView使用上幾乎和VideoView是一樣的,只不過WindowVideoView是以window的形式呈現的。window預設是可以拖動的,如果您不需要,可以禁止,window的每個設定項都有預設值,window的設定示例程式碼:
FloatWindowParams windowParams = new FloatWindowParams();
windowParams.setWindowType(WindowManager.LayoutParams.TYPE_TOAST)
.setFormat(PixelFormat.RGBA_8888)
.setFlag(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)
.setDefaultAnimation(true)
.setX(100)
.setY(100)
.setWidth(width)
.setHeight(height)
.setGravity(Gravity.TOP | Gravity.LEFT));
mWindowVideoView = new WindowVideoView(this,windowParams);
//...
複製程式碼
而FloatWindow只是一個懸浮窗View,您可以傳入您要顯示的佈局View。可以用於視窗切換播放時的無縫續播。此處不做程式碼示例展示。
一些樣式設定(StyleSetter)
樣式的設定是針對 VideoView、WindowVideoView 和 FloatWindow 的。當然框架提供的StyleSetter您也可以用於別處。提供瞭如下的樣式設定:
public interface IStyleSetter {
//設定圓角
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
void setRoundRectShape(float radius);
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
void setRoundRectShape(Rect rect, float radius);
//設定為圓形
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
void setOvalRectShape();
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
void setOvalRectShape(Rect rect);
//清除樣式設定
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
void clearShapeStyle();
//設定陰影
//注意陰影的設定要求對應的View物件必須要有背景色(不能是TRANSPARENT)
//如果您沒設定,框架內部會自定設定為黑色
void setElevationShadow(float elevation);
void setElevationShadow(int backgroundColor, float elevation);
}
複製程式碼
解碼器的接入
框架自帶了系統的MediaPlayer的解碼實現,專案demo中示例接入了ijkplayer和exoplayer,如果您想接入其他的解碼器,請參見示例程式碼,以下為簡單示例,更詳細的請參見專案原始碼。
接入步驟
- 繼承自BaseInternalPlayer
- 實現定義的抽象方法
- 配置引入您的解碼器
public class XXXPlayer extends BaseInternalPlayer{
public XXXPlayer() {
//...
}
//...
//implements some abstract methods.
}
複製程式碼
通過配置設定使用該解碼器。
int planId = 2;
PlayerConfig.addDecoderPlan(new DecoderPlan(planId, XXXPlayer.class.getName(), "XXXPlayer"));
PlayerConfig.setDefaultPlanId(planId);
複製程式碼
以上對於PlayerBase的講解基本完成。碼字好累!!!
主要的模組差不多就這麼多了,更詳細的可參見專案原始碼。
如有問題聯絡:junhui_jia@163.com
QQ交流群:600201778
最後再附上專案地址:PlayerBase