中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當(dāng)前位置: 首頁 > news >正文

網(wǎng)站收縮欄免費(fèi)自助建站網(wǎng)站

網(wǎng)站收縮欄,免費(fèi)自助建站網(wǎng)站,win10如何部署自己做的網(wǎng)站,招聘網(wǎng)站開發(fā)方案doc轉(zhuǎn)自用MediaPlayerTextureView封裝一個(gè)完美實(shí)現(xiàn)全屏、小窗口的視頻播放器 GitHub 為什么使用TextureView 在Android總播放視頻可以直接使用VideoView,VideoView是通過繼承自SurfaceView來實(shí)現(xiàn)的。SurfaceView的大概原理就是在現(xiàn)有View的位置上創(chuàng)建一個(gè)新的Window…

轉(zhuǎn)自用MediaPlayer+TextureView封裝一個(gè)完美實(shí)現(xiàn)全屏、小窗口的視頻播放器
GitHub

為什么使用TextureView

在Android總播放視頻可以直接使用VideoView,VideoView是通過繼承自SurfaceView來實(shí)現(xiàn)的。SurfaceView的大概原理就是在現(xiàn)有View的位置上創(chuàng)建一個(gè)新的Window,內(nèi)容的顯示和渲染都在新的Window中。這使得SurfaceView的繪制和刷新可以在單獨(dú)的線程中進(jìn)行,從而大大提高效率。但是呢,由于SurfaceView的內(nèi)容沒有顯示在View中而是顯示在新建的Window中, 使得SurfaceView的顯示不受View的屬性控制,不能進(jìn)行平移,縮放等變換,也不能放在其它RecyclerView或ScrollView中,一些View中的特性也無法使用。

TextureView是在4.0(API level 14)引入的,與SurfaceView相比,它不會(huì)創(chuàng)建新的窗口來顯示內(nèi)容。它是將內(nèi)容流直接投放到View中,并且可以和其它普通View一樣進(jìn)行移動(dòng),旋轉(zhuǎn),縮放,動(dòng)畫等變化。TextureView必須在硬件加速的窗口中使用。

TextureView被創(chuàng)建后不能直接使用,必須要在它被它添加到ViewGroup后,待SurfaceTexture準(zhǔn)備就緒才能起作用(看TextureView的源碼,TextureView是在繪制的時(shí)候創(chuàng)建的內(nèi)部SurfaceTexture)。通常需要給TextureView設(shè)置監(jiān)聽器SurfaceTextuListener:

mTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {@Overridepublic void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {// SurfaceTexture準(zhǔn)備就緒}@Overridepublic void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {// SurfaceTexture緩沖大小變化}@Overridepublic boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {// SurfaceTexture即將被銷毀return false;}@Overridepublic void onSurfaceTextureUpdated(SurfaceTexture surface) {// SurfaceTexture通過updateImage更新}
});

SurfaceTexture的準(zhǔn)備就緒、大小變化、銷毀、更新等狀態(tài)變化時(shí)都會(huì)回調(diào)相對應(yīng)的方法。當(dāng)TextureView內(nèi)部創(chuàng)建好SurfaceTexture后,在監(jiān)聽器的onSurfaceTextureAvailable方法中,用SurfaceTexture來關(guān)聯(lián)MediaPlayer,作為播放視頻的圖像數(shù)據(jù)來源。

SurfaceTexture作為數(shù)據(jù)通道,把從數(shù)據(jù)源(MediaPlayer)中獲取到的圖像幀數(shù)據(jù)轉(zhuǎn)為GL外部紋理,交給TextureVeiw作為View heirachy中的一個(gè)硬件加速層來顯示,從而實(shí)現(xiàn)視頻播放功能。

MediaPlayer介紹

MediaPlayer是Android原生的多媒體播放器,可以用它來實(shí)現(xiàn)本地或者在線音視頻的播放,同時(shí)它支持https和rtsp。

MediaPlayer定義了各種狀態(tài),可以理解為是它的生命周期。
MediaPlayer狀態(tài)圖(生命周期)
這里寫圖片描述
這個(gè)狀態(tài)圖描述了MediaPlayer的各種狀態(tài),以及主要方法調(diào)用后的狀態(tài)變化。

MediaPlayer的相關(guān)方法及監(jiān)聽接口:

  • setDataSource 設(shè)置數(shù)據(jù)源 Initialized 、
  • prepare 準(zhǔn)備播放,同步 Preparing —>Prepared
  • start 開始或恢復(fù)播放 Started
  • pause 暫停 Paused
  • stop 停止 Stopped
  • seekTo 到指定時(shí)間點(diǎn)位置 PrePared/Started
  • reset 重置播放器 Idle
  • setAudioStreamType 設(shè)置音頻流類型 –
  • setDisplay 設(shè)置播放視頻的Surface –
  • setVolume 設(shè)置聲音 –
  • getBufferPercentage 獲取緩沖半分比 –
  • getCurrentPosition 獲取當(dāng)前播放位置 –
    getDuration 獲取播放文件總時(shí)間 –

內(nèi)部回調(diào)接口

  • OnPreparedListener 準(zhǔn)備監(jiān)聽 Preparing ——>Prepared
  • OnVideoSizeChangedListener 視頻尺寸變化監(jiān)聽 –
  • OnInfoListener 指示信息和警告信息監(jiān)聽–
  • OnCompletionListener 播放完成監(jiān)聽 PlaybackCompleted
  • OnErrorListener 播放錯(cuò)誤監(jiān)聽 Error
  • OnBufferingUpdateListener 緩沖更新監(jiān)聽 –

MediaPlayer在直接new出來之后就進(jìn)入了Idle狀態(tài),此時(shí)可以調(diào)用多個(gè)重載的setDataSource()方法從idle狀態(tài)進(jìn)入Initialized狀態(tài)(如果調(diào)用setDataSource()方法的時(shí)候,MediaPlayer對象不是出于Idle狀態(tài),會(huì)拋異常,可以調(diào)用reset()方法回到Idle狀態(tài))。

調(diào)用prepared()方法和preparedAsync()方法進(jìn)入Prepared狀態(tài),prepared()方法直接進(jìn)入Parpared狀態(tài),preparedAsync()方法會(huì)先進(jìn)入PreParing狀態(tài),播放引擎準(zhǔn)備完畢后會(huì)通過OnPreparedListener.onPrepared()回調(diào)方法通知Prepared狀態(tài)。

在Prepared狀態(tài)下就可以調(diào)用start()方法進(jìn)行播放了,此時(shí)進(jìn)入started()狀態(tài),如果播放的是網(wǎng)絡(luò)資源,Started狀態(tài)下也會(huì)自動(dòng)調(diào)用客戶端注冊的OnBufferingUpdateListener.OnBufferingUpdate()回調(diào)方法,對流播放緩沖的狀態(tài)進(jìn)行追蹤。

pause()方法和start()方法是對應(yīng)的,調(diào)用pause()方法會(huì)進(jìn)入Paused狀態(tài),調(diào)用start()方法重新進(jìn)入Started狀態(tài),繼續(xù)播放。

stop()方法會(huì)使MdiaPlayer從Started、Paused、Prepared、PlaybackCompleted等狀態(tài)進(jìn)入到Stoped狀態(tài),播放停止。

當(dāng)資源播放完畢時(shí),如果調(diào)用了setLooping(boolean)方法,會(huì)自動(dòng)進(jìn)入Started狀態(tài)重新播放,如果沒有調(diào)用則會(huì)自動(dòng)調(diào)用客戶端播放器注冊的OnCompletionListener.OnCompletion()方法,此時(shí)MediaPlayer進(jìn)入PlaybackCompleted狀態(tài),在此狀態(tài)里可以調(diào)用start()方法重新進(jìn)入Started狀態(tài)。

封裝考慮

MediaPlayer的方法和接口比較多,不同的狀態(tài)調(diào)用各個(gè)方法后狀態(tài)變化情況也比較復(fù)雜。播放相關(guān)的邏輯只與MediaPlayer的播放狀態(tài)和調(diào)用方法相關(guān),而界面展示和UI操作很多時(shí)候都需要根據(jù)自己項(xiàng)目來定制。參考原生的VideoView,為了解耦和方便定制,把MediaPlayer的播放邏輯和UI界面展示及操作相關(guān)的邏輯分離。我是把MediaPlayer直接封裝到NiceVideoPlayer中,各種UI狀態(tài)和操作反饋都封裝到NiceVideoPlayerController里面。如果需要根據(jù)不同的項(xiàng)目需求來修改播放器的功能,就只重寫NiceVideoPlayerController就可以了。

NiceVideoPlayer

首先,需要一個(gè)FrameLayout容器mContainer,里面有兩層內(nèi)容,第一層就是展示播放視頻內(nèi)容的TextureView,第二層就是播放器控制器mController。那么自定義一個(gè)NiceVideoPlayer繼承自FrameLayout,將mContainer添加到當(dāng)前控件:

public class NiceVideoPlayer extends FrameLayout{private Context mContext;private NiceVideoController mController;private FrameLayout mContainer;public NiceVideoPlayer(Context context) {this(context, null);}public NiceVideoPlayer(Context context, AttributeSet attrs) {super(context, attrs);mContext = context;init();}private void init() {mContainer = new FrameLayout(mContext);mContainer.setBackgroundColor(Color.BLACK);LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);this.addView(mContainer, params);}
}

添加setUp方法來配置播放的視頻資源路徑(本地/網(wǎng)絡(luò)資源):

public void setUp(String url, Map<String, String> headers) {mUrl = url;mHeaders = headers;}

用戶要在mController中操作才能播放,因此需要在播放之前設(shè)置好mController:

public void setController(NiceVideoPlayerController controller) {mController = controller;mController.setNiceVideoPlayer(this);LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);mContainer.addView(mController, params);
}

用戶在自定義好自己的控制器后通過setController這個(gè)方法設(shè)置給播放器進(jìn)行關(guān)聯(lián)。

觸發(fā)播放時(shí),NiceVideoPlayer將展示視頻圖像內(nèi)容的mTextureView添加到mContainer中(在mController的下層),同時(shí)初始化mMediaPlayer,待mTextureView的數(shù)據(jù)通道SurfaceTexture準(zhǔn)備就緒后就可以打開播放器:

public void start() {initMediaPlayer();  // 初始化播放器initTextureView();  // 初始化展示視頻內(nèi)容的TextureViewaddTextureView();   // 將TextureView添加到容器中
}private void initTextureView() {if (mTextureView == null) {mTextureView = new TextureView(mContext);mTextureView.setSurfaceTextureListener(this);}
}private void addTextureView() {mContainer.removeView(mTextureView);LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);mContainer.addView(mTextureView, 0,  params);
}private void initMediaPlayer() {if (mMediaPlayer == null) {mMediaPlayer = new MediaPlayer();mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);mMediaPlayer.setScreenOnWhilePlaying(true);mMediaPlayer.setOnPreparedListener(mOnPreparedListener);mMediaPlayer.setOnVideoSizeChangedListener(mOnVideoSizeChangedListener);mMediaPlayer.setOnCompletionListener(mOnCompletionListener);mMediaPlayer.setOnErrorListener(mOnErrorListener);mMediaPlayer.setOnInfoListener(mOnInfoListener);mMediaPlayer.setOnBufferingUpdateListener(mOnBufferingUpdateListener);}
}@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {// surfaceTexture數(shù)據(jù)通道準(zhǔn)備就緒,打開播放器openMediaPlayer(surface);
}private void openMediaPlayer(SurfaceTexture surface) {try {mMediaPlayer.setDataSource(mContext.getApplicationContext(), Uri.parse(mUrl), mHeaders);mMediaPlayer.setSurface(new Surface(surface));mMediaPlayer.prepareAsync();} catch (IOException e) {e.printStackTrace();}
}@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {}@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {return false;
}@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {}打開播放器調(diào)用prepareAsync()方法后,mMediaPlayer進(jìn)入準(zhǔn)備狀態(tài),準(zhǔn)備就緒后就可以開始:
private MediaPlayer.OnPreparedListener mOnPreparedListener= new MediaPlayer.OnPreparedListener() {@Overridepublic void onPrepared(MediaPlayer mp) {mp.start();}
};

NiceVideoPlayer的這些邏輯已經(jīng)實(shí)現(xiàn)視頻播放了,操作相關(guān)以及UI展示的邏輯需要在控制器NiceVideoPlayerController中來實(shí)現(xiàn)。但是呢,UI的展示和反饋都需要依據(jù)播放器當(dāng)前的播放狀態(tài),所以需要給播放器定義一些常量來表示它的播放狀態(tài):

public static final int STATE_ERROR = -1;          // 播放錯(cuò)誤
public static final int STATE_IDLE = 0;            // 播放未開始
public static final int STATE_PREPARING = 1;       // 播放準(zhǔn)備中
public static final int STATE_PREPARED = 2;        // 播放準(zhǔn)備就緒
public static final int STATE_PLAYING = 3;         // 正在播放
public static final int STATE_PAUSED = 4;          // 暫停播放
// 正在緩沖(播放器正在播放時(shí),緩沖區(qū)數(shù)據(jù)不足,進(jìn)行緩沖,緩沖區(qū)數(shù)據(jù)足夠后恢復(fù)播放)
public static final int STATE_BUFFERING_PLAYING = 5;
// 正在緩沖(播放器正在播放時(shí),緩沖區(qū)數(shù)據(jù)不足,進(jìn)行緩沖,此時(shí)暫停播放器,繼續(xù)緩沖,緩沖區(qū)數(shù)據(jù)足夠后恢復(fù)暫停)
public static final int STATE_BUFFERING_PAUSED = 6;
public static final int STATE_COMPLETED = 7;       // 播放完成

播放視頻時(shí),mMediaPlayer準(zhǔn)備就緒(Prepared)后沒有馬上進(jìn)入播放狀態(tài),中間有一個(gè)時(shí)間延遲時(shí)間段,然后開始渲染圖像。所以將Prepared——>“開始渲染”中間這個(gè)時(shí)間段定義為STATE_PREPARED。

如果是播放網(wǎng)絡(luò)視頻,在播放過程中,緩沖區(qū)數(shù)據(jù)不足時(shí)mMediaPlayer內(nèi)部會(huì)停留在某一幀畫面以進(jìn)行緩沖。正在緩沖時(shí),mMediaPlayer可能是在正在播放也可能是暫停狀態(tài),因?yàn)樵诰彌_時(shí)如果用戶主動(dòng)點(diǎn)擊了暫停,就是處于STATE_BUFFERING_PAUSED,所以緩沖有STATE_BUFFERING_PLAYING和STATE_BUFFERING_PAUSED兩種狀態(tài),緩沖結(jié)束后,恢復(fù)播放或暫停。

private MediaPlayer.OnPreparedListener mOnPreparedListener= new MediaPlayer.OnPreparedListener() {@Overridepublic void onPrepared(MediaPlayer mp) {mp.start();mCurrentState = STATE_PREPARED;mController.setControllerState(mPlayerState, mCurrentState);LogUtil.d("onPrepared ——> STATE_PREPARED");}
};private MediaPlayer.OnVideoSizeChangedListener mOnVideoSizeChangedListener= new MediaPlayer.OnVideoSizeChangedListener() {@Overridepublic void onVideoSizeChanged(MediaPlayer mp, int width, int height) {LogUtil.d("onVideoSizeChanged ——> width:" + width + ",height:" + height);}
};private MediaPlayer.OnCompletionListener mOnCompletionListener= new MediaPlayer.OnCompletionListener() {@Overridepublic void onCompletion(MediaPlayer mp) {mCurrentState = STATE_COMPLETED;mController.setControllerState(mPlayerState, mCurrentState);LogUtil.d("onCompletion ——> STATE_COMPLETED");}
};private MediaPlayer.OnErrorListener mOnErrorListener= new MediaPlayer.OnErrorListener() {@Overridepublic boolean onError(MediaPlayer mp, int what, int extra) {mCurrentState = STATE_ERROR;mController.setControllerState(mPlayerState, mCurrentState);LogUtil.d("onError ——> STATE_ERROR ———— what:" + what);return false;}
};private MediaPlayer.OnInfoListener mOnInfoListener= new MediaPlayer.OnInfoListener() {@Overridepublic boolean onInfo(MediaPlayer mp, int what, int extra) {if (what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) {// 播放器渲染第一幀mCurrentState = STATE_PLAYING;mController.setControllerState(mPlayerState, mCurrentState);LogUtil.d("onInfo ——> MEDIA_INFO_VIDEO_RENDERING_START:STATE_PLAYING");} else if (what == MediaPlayer.MEDIA_INFO_BUFFERING_START) {// MediaPlayer暫時(shí)不播放,以緩沖更多的數(shù)據(jù)if (mCurrentState == STATE_PAUSED || mCurrentState == STATE_BUFFERING_PAUSED) {mCurrentState = STATE_BUFFERING_PAUSED;LogUtil.d("onInfo ——> MEDIA_INFO_BUFFERING_START:STATE_BUFFERING_PAUSED");} else {mCurrentState = STATE_BUFFERING_PLAYING;LogUtil.d("onInfo ——> MEDIA_INFO_BUFFERING_START:STATE_BUFFERING_PLAYING");}mController.setControllerState(mPlayerState, mCurrentState);} else if (what == MediaPlayer.MEDIA_INFO_BUFFERING_END) {// 填充緩沖區(qū)后,MediaPlayer恢復(fù)播放/暫停if (mCurrentState == STATE_BUFFERING_PLAYING) {mCurrentState = STATE_PLAYING;mController.setControllerState(mPlayerState, mCurrentState);LogUtil.d("onInfo ——> MEDIA_INFO_BUFFERING_END: STATE_PLAYING");}if (mCurrentState == STATE_BUFFERING_PAUSED) {mCurrentState = STATE_PAUSED;mController.setControllerState(mPlayerState, mCurrentState);LogUtil.d("onInfo ——> MEDIA_INFO_BUFFERING_END: STATE_PAUSED");}} else {LogUtil.d("onInfo ——> what:" + what);}return true;}
};private MediaPlayer.OnBufferingUpdateListener mOnBufferingUpdateListener= new MediaPlayer.OnBufferingUpdateListener() {@Overridepublic void onBufferingUpdate(MediaPlayer mp, int percent) {mBufferPercentage = percent;}
};mController.setControllerState(mPlayerState, mCurrentState),mCurrentState表示當(dāng)前播放狀態(tài),mPlayerState表示播放器的全屏、小窗口,正常三種狀態(tài)。
public static final int PLAYER_NORMAL = 10;        // 普通播放器
public static final int PLAYER_FULL_SCREEN = 11;   // 全屏播放器
public static final int PLAYER_TINY_WINDOW = 12;   // 小窗口播放器定義好播放狀態(tài)后,開始暫停等操作邏輯也需要根據(jù)播放狀態(tài)調(diào)整:
@Override
public void start() {if (mCurrentState == STATE_IDLE|| mCurrentState == STATE_ERROR|| mCurrentState == STATE_COMPLETED) {initMediaPlayer();initTextureView();addTextureView();}
}@Override
public void restart() {if (mCurrentState == STATE_PAUSED) {mMediaPlayer.start();mCurrentState = STATE_PLAYING;mController.setControllerState(mPlayerState, mCurrentState);LogUtil.d("STATE_PLAYING");}if (mCurrentState == STATE_BUFFERING_PAUSED) {mMediaPlayer.start();mCurrentState = STATE_BUFFERING_PLAYING;mController.setControllerState(mPlayerState, mCurrentState);LogUtil.d("STATE_BUFFERING_PLAYING");}
}@Override
public void pause() {if (mCurrentState == STATE_PLAYING) {mMediaPlayer.pause();mCurrentState = STATE_PAUSED;mController.setControllerState(mPlayerState, mCurrentState);LogUtil.d("STATE_PAUSED");}if (mCurrentState == STATE_BUFFERING_PLAYING) {mMediaPlayer.pause();mCurrentState = STATE_BUFFERING_PAUSED;mController.setControllerState(mPlayerState, mCurrentState);LogUtil.d("STATE_BUFFERING_PAUSED");}
}

reStart()方法是暫停時(shí)繼續(xù)播放調(diào)用。

全屏、小窗口播放的實(shí)現(xiàn)

可能最能想到實(shí)現(xiàn)全屏的方式就是把當(dāng)前播放器的寬高給放大到屏幕大小,同時(shí)隱藏除播放器以外的其他所有UI,并設(shè)置成橫屏模式。但是這種方式有很多問題,比如在列表(ListView或RecyclerView)中,除了放大隱藏外,還需要去計(jì)算滑動(dòng)多少距離才剛好讓播放器與屏幕邊緣重合,退出全屏的時(shí)候還需要滑動(dòng)到之前的位置,這樣實(shí)現(xiàn)邏輯不但繁瑣,而且和外部UI偶合嚴(yán)重,后面改動(dòng)維護(hù)起來非常困難(我曾經(jīng)就用這種方式被坑了無數(shù)道)。

分析能不能有其他更好的實(shí)現(xiàn)方式呢?

整個(gè)播放器由mMediaPalyer+mTexutureView+mController組成,要實(shí)現(xiàn)全屏或小窗口播放,我們只需要挪動(dòng)播放器的展示界面mTexutureView和控制界面mController即可。并且呢我們在上面定義播放器時(shí),已經(jīng)把mTexutureView和mController一起添加到mContainer中了,所以只需要將mContainer從當(dāng)前視圖中移除,并添加到全屏和小窗口的目標(biāo)視圖中即可。

那么怎么確定全屏和小窗口的目標(biāo)視圖呢?

我們知道每個(gè)Activity里面都有一個(gè)android.R.content,它是一個(gè)FrameLayout,里面包含了我們setContentView的所有控件。既然它是一個(gè)FrameLayout,我們就可以將它作為全屏和小窗口的目標(biāo)視圖。

我們把從當(dāng)前視圖移除的mContainer重新添加到android.R.content中,并且設(shè)置成橫屏。這個(gè)時(shí)候還需要注意android.R.content是不包括ActionBar和狀態(tài)欄的,所以要將Activity設(shè)置成全屏模式,同時(shí)隱藏ActionBar。

@Override
public void enterFullScreen() {if (mPlayerState == PLAYER_FULL_SCREEN) return;// 隱藏ActionBar、狀態(tài)欄,并橫屏NiceUtil.hideActionBar(mContext);NiceUtil.scanForActivity(mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);this.removeView(mContainer);ViewGroup contentView = (ViewGroup) NiceUtil.scanForActivity(mContext).findViewById(android.R.id.content);LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);contentView.addView(mContainer, params);mPlayerState = PLAYER_FULL_SCREEN;mController.setControllerState(mPlayerState, mCurrentState);LogUtil.d("PLAYER_FULL_SCREEN");
}退出全屏也就很簡單了,將mContainer從android.R.content中移除,重新添加到當(dāng)前視圖,并恢復(fù)ActionBar、清除全屏模式就行了。
public boolean exitFullScreen() {if (mPlayerState == PLAYER_FULL_SCREEN) {NiceUtil.showActionBar(mContext);NiceUtil.scanForActivity(mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);ViewGroup contentView = (ViewGroup) NiceUtil.scanForActivity(mContext).findViewById(android.R.id.content);contentView.removeView(mContainer);LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);this.addView(mContainer, params);mPlayerState = PLAYER_NORMAL;mController.setControllerState(mPlayerState, mCurrentState);LogUtil.d("PLAYER_NORMAL");return true;}return false;
}

切換橫豎屏?xí)r為了避免Activity重新走生命周期,別忘了需要在Manifest.xml的activity標(biāo)簽下添加如下配置:
android:configChanges=”orientation|keyboardHidden|screenSize”

進(jìn)入小窗口播放和退出小窗口的實(shí)現(xiàn)原理就和全屏功能一樣了,只需要修改它的寬高參數(shù):

@Override
public void enterTinyWindow() {if (mPlayerState == PLAYER_TINY_WINDOW) return;this.removeView(mContainer);ViewGroup contentView = (ViewGroup) NiceUtil.scanForActivity(mContext).findViewById(android.R.id.content);// 小窗口的寬度為屏幕寬度的60%,長寬比默認(rèn)為16:9,右邊距、下邊距為8dp。FrameLayout.LayoutParams params = new FrameLayout.LayoutParams((int) (NiceUtil.getScreenWidth(mContext) * 0.6f),(int) (NiceUtil.getScreenWidth(mContext) * 0.6f * 9f / 16f));params.gravity = Gravity.BOTTOM | Gravity.END;params.rightMargin = NiceUtil.dp2px(mContext, 8f);params.bottomMargin = NiceUtil.dp2px(mContext, 8f);contentView.addView(mContainer, params);mPlayerState = PLAYER_TINY_WINDOW;mController.setControllerState(mPlayerState, mCurrentState);LogUtil.d("PLAYER_TINY_WINDOW");
}@Override
public boolean exitTinyWindow() {if (mPlayerState == PLAYER_TINY_WINDOW) {ViewGroup contentView = (ViewGroup) NiceUtil.scanForActivity(mContext).findViewById(android.R.id.content);contentView.removeView(mContainer);LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT);this.addView(mContainer, params);mPlayerState = PLAYER_NORMAL;mController.setControllerState(mPlayerState, mCurrentState);LogUtil.d("PLAYER_NORMAL");return true;}return false;
}

這里有個(gè)特別需要注意的一點(diǎn):

當(dāng)mContainer移除重新添加后,mContainer及其內(nèi)部的mTextureView和mController都會(huì)重繪,mTextureView重繪后,會(huì)重新new一個(gè)SurfaceTexture,并重新回調(diào)onSurfaceTextureAvailable方法,這樣mTextureView的數(shù)據(jù)通道SurfaceTexture發(fā)生了變化,但是mMediaPlayer還是持有原先的mSurfaceTexut,所以在切換全屏之前要保存之前的mSufaceTexture,當(dāng)切換到全屏后重新調(diào)用onSurfaceTextureAvailable時(shí),將之前的mSufaceTexture重新設(shè)置給mTexutureView。這樣就保證了切換時(shí)視頻播放的無縫銜接。

@Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {if (mSurfaceTexture == null) {mSurfaceTexture = surfaceTexture;openMediaPlayer();} else {mTextureView.setSurfaceTexture(mSurfaceTexture);}
}

NiceVideoPlayerControl

為了解除NiceVideoPlayer和NiceVideoPlayerController的耦合,把NiceVideoPlayer的一些功能性和判斷性方法抽象到NiceVideoPlayerControl接口中。

public interface NiceVideoPlayerControl {void start();void restart();void pause();void seekTo(int pos);boolean isIdle();boolean isPreparing();boolean isPrepared();boolean isBufferingPlaying();boolean isBufferingPaused();boolean isPlaying();boolean isPaused();boolean isError();boolean isCompleted();boolean isFullScreen();boolean isTinyWindow();boolean isNormal();int getDuration();int getCurrentPosition();int getBufferPercentage();void enterFullScreen();boolean exitFullScreen();void enterTinyWindow();boolean exitTinyWindow();void release();
}

NiceVideoPlayer實(shí)現(xiàn)這個(gè)接口即可。

NiceVideoPlayerManager

同一界面上有多個(gè)視頻,或者視頻放在ReclerView或者ListView的容器中,要保證同一時(shí)刻只有一個(gè)視頻在播放,其他的都是初始狀態(tài),所以需要一個(gè)NiceVideoPlayerManager來管理播放器,主要功能是保存當(dāng)前已經(jīng)開始了的播放器。

public class NiceVideoPlayerManager {private NiceVideoPlayer mVideoPlayer;private NiceVideoPlayerManager() {}private static NiceVideoPlayerManager sInstance;public static synchronized NiceVideoPlayerManager instance() {if (sInstance == null) {sInstance = new NiceVideoPlayerManager();}return sInstance;}public void setCurrentNiceVideoPlayer(NiceVideoPlayer videoPlayer) {mVideoPlayer = videoPlayer;}public void releaseNiceVideoPlayer() {if (mVideoPlayer != null) {mVideoPlayer.release();mVideoPlayer = null;}}public boolean onBackPressd() {if (mVideoPlayer != null) {if (mVideoPlayer.isFullScreen()) {return mVideoPlayer.exitFullScreen();} else if (mVideoPlayer.isTinyWindow()) {return mVideoPlayer.exitTinyWindow();} else {mVideoPlayer.release();return false;}}return false;}
}

采用單例,同時(shí),onBackPressed供Activity中用戶按返回鍵時(shí)調(diào)用。
NiceVideoPlayer的start方法以及onCompleted需要修改一下,保證開始播放一個(gè)視頻時(shí)要先釋放掉之前的播放器;同時(shí)自己播放完畢,要將NiceVideoPlayerManager中的mNiceVideoPlayer實(shí)例置空,避免內(nèi)存泄露。

// NiceVideoPlayer的start()方法。
@Override
public void start() {NiceVideoPlayerManager.instance().releaseNiceVideoPlayer();NiceVideoPlayerManager.instance().setCurrentNiceVideoPlayer(this);if (mCurrentState == STATE_IDLE|| mCurrentState == STATE_ERROR|| mCurrentState == STATE_COMPLETED) {initMediaPlayer();initTextureView();addTextureView();}
}// NiceVideoPlayer中的onCompleted監(jiān)聽。
private MediaPlayer.OnCompletionListener mOnCompletionListener= new MediaPlayer.OnCompletionListener() {@Overridepublic void onCompletion(MediaPlayer mp) {mCurrentState = STATE_COMPLETED;mController.setControllerState(mPlayerState, mCurrentState);LogUtil.d("onCompletion ——> STATE_COMPLETED");NiceVideoPlayerManager.instance().setCurrentNiceVideoPlayer(null);}
};

NiceVideoPlayerController

播放控制界面上,播放、暫停、播放進(jìn)度、緩沖動(dòng)畫、全屏/小屏等觸發(fā)都是直接調(diào)用播放器對應(yīng)的操作的。需要注意的就是調(diào)用之前要判斷當(dāng)前的播放狀態(tài),因?yàn)橛行顟B(tài)下調(diào)用播放器的操作可能引起錯(cuò)誤(比如播放器還沒準(zhǔn)備就緒,就去獲取當(dāng)前的播放位置)。

播放器在觸發(fā)相應(yīng)功能的時(shí)候都會(huì)調(diào)用NiceVideoPlayerController的setControllerState(int playerState, int playState)這個(gè)方法來讓用戶修改UI。

不同項(xiàng)目都可能定制不同的控制器(播放操作界面),這里我就不詳細(xì)分析實(shí)現(xiàn)邏輯了,大致功能就類似騰訊視頻的熱點(diǎn)列表中的播放器。其中橫向滑動(dòng)改變播放進(jìn)度、左側(cè)上下滑動(dòng)改變亮度,右側(cè)上下滑動(dòng)改變亮度等功能在代碼中都有實(shí)現(xiàn)。代碼有點(diǎn)長,就不貼了,需要的直接下載源碼。

使用

mNiceVideoPlayer.setUp(url, null);
NiceVideoPlayerController controller = new NiceVideoPlayerController(this);
controller.setTitle(title);
controller.setImage(imageUrl);
mNiceVideoPlayer.setController(controller);在RecyclerView或者ListView中使用時(shí),需要監(jiān)聽itemView的detached:
mRecyclerView.addOnChildAttachStateChangeListener(new RecyclerView.OnChildAttachStateChangeListener() {@Overridepublic void onChildViewAttachedToWindow(View view) {}@Overridepublic void onChildViewDetachedFromWindow(View view) {NiceVideoPlayer niceVideoPlayer = (NiceVideoPlayer) view.findViewById(R.id.nice_video_player);if (niceVideoPlayer != null) {niceVideoPlayer.release();}}
});

在ItemViewdetach窗口時(shí),需要釋放掉itemView內(nèi)部的播放器。

效果圖
這里寫圖片描述
這里寫圖片描述
最后

整個(gè)功能有參考節(jié)操播放器,但是自己這樣封裝和節(jié)操播放器還是有很大差異:一是分離了播放功能和控制界面,定制只需修改控制器即可。二是全屏/小窗口沒有新建一個(gè)播放器,只是挪動(dòng)了播放界面和控制器,不用每個(gè)視頻都需要新建兩個(gè)播放器,也不用同步狀態(tài)。
MediaPlayer有很多格式不支持,項(xiàng)目已添加IjkPlayer的擴(kuò)展支持,可以切換IjkPlayer和原生MediaPlayer,后續(xù)還會(huì)考慮添加ExoPlayer,同時(shí)也會(huì)擴(kuò)展更多功能。

http://www.risenshineclean.com/news/38514.html

相關(guān)文章:

  • 手機(jī)站建網(wǎng)站免費(fèi)
  • 有什么做兼職的網(wǎng)站關(guān)鍵詞長尾詞優(yōu)化
  • 下載簡歷模板免費(fèi)百度系優(yōu)化
  • html5效果網(wǎng)站做一個(gè)網(wǎng)站要花多少錢
  • 群站優(yōu)化之鏈輪模式制作網(wǎng)站要花多少錢
  • 做網(wǎng)站要用到數(shù)據(jù)庫嗎新東方考研班收費(fèi)價(jià)格表
  • 廣州低價(jià)網(wǎng)站建設(shè)黃頁88
  • 有了域名和空間怎么做網(wǎng)站市場營銷的策劃方案
  • 網(wǎng)站建設(shè)術(shù)語解釋在線crm
  • 規(guī)避電子政務(wù)門戶網(wǎng)站建設(shè)的教訓(xùn)優(yōu)秀網(wǎng)頁設(shè)計(jì)賞析
  • 關(guān)于seo網(wǎng)站優(yōu)化公司黑馬培訓(xùn)機(jī)構(gòu)可靠嗎
  • 贛縣網(wǎng)站建設(shè)國內(nèi)新聞最新消息十條
  • h5開發(fā)教程哈爾濱seo網(wǎng)站管理
  • 做網(wǎng)站賺錢的時(shí)代過去了嗎百度地圖導(dǎo)航
  • 網(wǎng)站設(shè)置默認(rèn)首頁網(wǎng)絡(luò)營銷網(wǎng)
  • 萬戶網(wǎng)站免費(fèi)手游推廣平臺(tái)
  • 杭州國外網(wǎng)站推廣公司站長之家 站長工具
  • 網(wǎng)站開發(fā)與實(shí)訓(xùn)報(bào)告企業(yè)優(yōu)化推廣
  • 北京網(wǎng)站建設(shè)w億瑪酷1訂制網(wǎng)站策劃方案
  • 網(wǎng)站開發(fā)業(yè)務(wù)需求分析今日熱點(diǎn)新聞
  • 辦公室局域網(wǎng)怎么搭建seo 資料包怎么獲得
  • wordpress小清新模板鄭州網(wǎng)站優(yōu)化推廣
  • 網(wǎng)站搬家 備案短視頻培訓(xùn)要多少學(xué)費(fèi)
  • 個(gè)人博客網(wǎng)站制作教程網(wǎng)店如何引流與推廣
  • 小說網(wǎng)站代理網(wǎng)絡(luò)營銷的推廣
  • 前端不會(huì)wordpress班級優(yōu)化大師頭像
  • 網(wǎng)站建設(shè)問題分類和排除方法分析優(yōu)化大師win10能用嗎
  • 外貿(mào)建站上海成都seo工程師
  • 微信手機(jī)網(wǎng)頁登錄入口站長工具seo診斷
  • 網(wǎng)站開發(fā)即時(shí)聊天源碼百度搜索指數(shù)排行榜