android 自定义MP4播放器
昨天,在網(wǎng)上找了好多資料,終于做了一個(gè)自定義的播發(fā)器。
視頻播放方式
在Android中播放視頻的方式有兩種:
1、使用MediaPlayer結(jié)合SurfaceView進(jìn)行播放。其中通過SurfaceView顯示視頻的畫面,通過MediaPlayer來設(shè)置播放參數(shù)、并控制視頻的播放操作。
該方式的好處是靈活性強(qiáng),可隨意定制。缺點(diǎn)是編碼復(fù)雜,連開始/暫停的按鈕都要自己實(shí)現(xiàn)。
2、使用VideoView結(jié)合MediaController進(jìn)行播放。VideoView其實(shí)是從SurfaceView擴(kuò)展而來,并在內(nèi)部集成了MediaPlayer,從而實(shí)現(xiàn)視頻畫面與視頻操作的統(tǒng)一管理;而MediaController則是一個(gè)簡單的播放控制條,它實(shí)現(xiàn)了基本的控制按鈕,如開始/暫停按鈕、上一個(gè)/下一個(gè)按鈕、快進(jìn)/快退按鈕,以及進(jìn)度條等控件;把VideoView與MediaController關(guān)聯(lián)起來,便是一個(gè)類似于Window Media Player的精簡版播放器。
該方式的好處是簡單易用,編碼容易。缺點(diǎn)是可定制差,難以擴(kuò)展,想給按鈕換個(gè)樣式都不行。
一。自定義一個(gè)activity繼承SurfaceHolder和SeekBarMediaPlayer的所以接口。然后初始化界面。
package com.mymp4player; import android.app.Activity; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.media.AudioManager; import android.media.MediaPlayer; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.Message; import android.provider.Settings; import android.support.v7.app.AppCompatActivity; import android.util.DisplayMetrics; import android.util.Log; import android.view.Gravity; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.SeekBar; import android.widget.TextView; import android.widget.Toast; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.HashMap; /** * 創(chuàng)建:dongshuaijun * 日期:2016/7/1 * 注釋:視屏播放 */ public class MainActivity extends Activity implements SurfaceHolder.Callback, View.OnClickListener , SeekBar.OnSeekBarChangeListener, MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener, MediaPlayer.OnBufferingUpdateListener, MediaPlayer.OnPreparedListener, MediaPlayer.OnSeekCompleteListener {//surfaceView private SurfaceView surfaceView; //視頻最外層layout private RelativeLayout videoLayout; //控制臺layout private LinearLayout controlLayout; //播放、全屏button private ImageButton playBtn, screenBtn; //進(jìn)度條 private SeekBar seekBar; //加載視頻進(jìn)度progressBar private ProgressBar progressBar; //當(dāng)前時(shí)間,總時(shí)間 private TextView currTime, countTime; //surface holder private SurfaceHolder mHolder; //媒體控制 mediaPlayer private MediaPlayer mediaPlayer; //是否全屏 private boolean isFullScreen = false; //是否正在播放 private boolean isPlay = false; //控制臺是否顯示 private boolean isControl = false; //是否正在拖動seekBar private boolean isSetProgress = false; //是否播放完成 private boolean isPlayCom = false; //是否是第一次加載視頻 private boolean isFirstLoadVideo = true; //是否銷毀activity private boolean isOnDestroy = false; //是否可見 private boolean isPause = false; //媒體音量管理 private AudioManager audioManager; //點(diǎn)擊縱坐標(biāo) private float dY = 0; //點(diǎn)擊橫坐標(biāo) private float dX = 0; //抬起縱坐標(biāo) private float uY = 0; //抬起橫坐標(biāo) private float uX = 0; //屏幕當(dāng)前亮度 private float f = 0; //手機(jī)當(dāng)前亮度模式 0 1 private int countLight; //系統(tǒng)當(dāng)前亮度 1-255 private int currLight; private String width, height; private static final int HIDE_CONTROL_LAYOUT = -1; //這個(gè)地址是我抓的某平臺的,我發(fā)現(xiàn)這個(gè)地址是變化的,所以有可能不能使用,如果不能播放,換個(gè)正常的就可以運(yùn)行了,不要用模擬器運(yùn)行 private static final String BASE_STORAGE_PATH = Environment.getExternalStorageDirectory().getAbsolutePath(); public static final String RECORD_VIDEO_PATH = BASE_STORAGE_PATH + "/p2pviewcam/videos/"; private static final String VIDEO_URL = RECORD_VIDEO_PATH+"BZWYZN-031016-CBGTK_2017_06_12_17_01_37_CH_1.mp4"; private Handler handler = new Handler() {@Override public void handleMessage(Message msg) {super.handleMessage(msg); if (msg.what == HIDE_CONTROL_LAYOUT) {refreshControlLayout(); } else {currTime.setText(formatTime(msg.what)); seekBar.setProgress(msg.what); }}}; @Override protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initView(); initVideoSize(); initSurface(); setListener(); }private void initView() {surfaceView = (SurfaceView) findViewById(R.id.surface_view); videoLayout = (RelativeLayout) findViewById(R.id.video_layout); controlLayout = (LinearLayout) findViewById(R.id.control_layout); playBtn = (ImageButton) findViewById(R.id.playBtn); screenBtn = (ImageButton) findViewById(R.id.screenBtn); seekBar = (SeekBar) findViewById(R.id.seekBar); progressBar = (ProgressBar) findViewById(R.id.load_bar); currTime = (TextView) findViewById(R.id.curr_time); countTime = (TextView) findViewById(R.id.count_time); mHolder = surfaceView.getHolder(); mediaPlayer = new MediaPlayer(); audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); initScreenLight(); refreshControlLayout(); }//初始化屏幕亮度 private void initScreenLight() {try {//獲取亮度模式 0:手動 1:自動 countLight = Settings.System.getInt(getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_MODE); //設(shè)置手動設(shè)置 Settings.System.putInt(getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_MODE, Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL); //獲取屏幕亮度,獲取失敗則返回255 currLight = android.provider.Settings.System.getInt(getContentResolver(), android.provider.Settings.System.SCREEN_BRIGHTNESS, 255); f = currLight / 255f; } catch (Settings.SettingNotFoundException e) {e.printStackTrace(); }}//刷新控制臺 顯示則隱藏 隱藏則顯示 并5S之后隱藏 private void refreshControlLayout() {if (isControl) {controlLayout.setVisibility(View.INVISIBLE); isControl = false; } else {controlLayout.setVisibility(View.VISIBLE); isControl = true; handler.removeMessages(HIDE_CONTROL_LAYOUT); handler.sendEmptyMessageDelayed(HIDE_CONTROL_LAYOUT, 5000); }}private void setListener() {playBtn.setOnClickListener(this); screenBtn.setOnClickListener(this); seekBar.setOnSeekBarChangeListener(this); mediaPlayer.setOnCompletionListener(this); mediaPlayer.setOnErrorListener(this); mediaPlayer.setOnBufferingUpdateListener(this); mediaPlayer.setOnPreparedListener(this); mediaPlayer.setOnSeekCompleteListener(this); surfaceView.setOnTouchListener(new View.OnTouchListener() {@Override public boolean onTouch(View v, MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:dX = event.getX(); dY = event.getY(); refreshControlLayout(); break; case MotionEvent.ACTION_UP:break; case MotionEvent.ACTION_MOVE:if (isFullScreen) {uY = event.getY(); if (dX > getWidth() / 2) {//聲音控制 if (Math.abs(uY - dY) > 25)setVolume(uY - dY); } else if (dX <= getWidth() / 2) {//亮度控制 setLight(dY - uY); }}break; }return true; }}); }//手勢調(diào)節(jié)音量 private void setVolume(float vol) {if (vol < 0) {//增大音量 audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_RAISE, AudioManager.FX_FOCUS_NAVIGATION_UP); } else if (vol > 0) {//降低音量 audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_LOWER, AudioManager.FX_FOCUS_NAVIGATION_UP); }}/** * 手勢設(shè)置屏幕亮度 * 設(shè)置當(dāng)前的屏幕亮度值,及時(shí)生效 0.004-1 * 該方法僅對當(dāng)前應(yīng)用屏幕亮度生效 */ private void setLight(float vol) {Window localWindow = getWindow(); WindowManager.LayoutParams localLayoutParams = localWindow.getAttributes(); f += vol / getWidth(); if (f > 1) {f = 1f; } else if (f <= 0) {f = 0.004f; }localLayoutParams.screenBrightness = f; localWindow.setAttributes(localLayoutParams); }//初始化surfaceView private void initSurface() {//設(shè)置回調(diào)參數(shù) mHolder.addCallback(this); //設(shè)置SurfaceView自己不管理的緩沖區(qū) mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); //顯示的分辨率,不設(shè)置為視頻默認(rèn) // mHolder.setFixedSize(320, 220); }private void playUrl(String url) {try {//使mediaPlayer重新進(jìn)入ide狀態(tài) mediaPlayer.reset(); //設(shè)置媒體類型 mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); //將影像輸出到surfaceView mediaPlayer.setDisplay(mHolder); //設(shè)置 視頻資源 可以是本地視頻 也可是網(wǎng)絡(luò)資源 // mediaPlayer.setDataSource("/storage/sdcard1/DCIM/Camera/VID_20160629_164144.mp4"); mediaPlayer.setDataSource(url); //同步準(zhǔn)備 // mediaPlayer.prepare(); //因?yàn)槭蔷W(wǎng)絡(luò)視頻 這里用異步準(zhǔn)備 mediaPlayer.prepareAsync(); } catch (IOException e) {e.printStackTrace(); }}//初始化視頻顯示的大小 private void initVideoSize() {RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); getPlayTime(VIDEO_URL); // params.height = getWidth() / 10 * 6; if (width!=null && height!=null){params.height = getWidth() *Integer.parseInt(height)/Integer.parseInt(width); }else {params.height = getWidth() / 10 * 6; }params.addRule(RelativeLayout.CENTER_IN_PARENT); surfaceView.setLayoutParams(params); }//surfaceView創(chuàng)建完成 @Override public void surfaceCreated(SurfaceHolder holder) {Log.e("TAG", "surfaceCreated"); //等surfaceView創(chuàng)建完成再開始播放視頻 if (!isPause) {playUrl(VIDEO_URL); } else {isPause = false; mediaPlayer.setDisplay(holder); if (isPlay) mediaPlayer.start(); }}//surfaceView改變 @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {Log.e("TAG", "surfaceChanged"); }//surfaceView銷毀 @Override public void surfaceDestroyed(SurfaceHolder holder) {Log.e("TAG", "surfaceDestroyed"); }@Override public void onClick(View v) {isControl = false; refreshControlLayout(); if (isFirstLoadVideo) {return; }switch (v.getId()) {case R.id.playBtn:if (mediaPlayer.isPlaying()) {mediaPlayer.pause(); isPlay = false; playBtn.setBackgroundResource(R.mipmap.play); } else if (isPlayCom) {mediaPlayer.seekTo(0); isPlay = true; isPlayCom = false; playBtn.setBackgroundResource(R.mipmap.pause); } else {mediaPlayer.start(); isPlay = true; playBtn.setBackgroundResource(R.mipmap.pause); }break; case R.id.screenBtn:if (isFullScreen) {smallScreen(); screenBtn.setBackgroundResource(R.mipmap.large_screen); } else {fullScreen(); screenBtn.setBackgroundResource(R.mipmap.small_screen); }break; }}//橫豎屏切換 @Override public void onConfigurationChanged(Configuration newConfig) {if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {Log.e("TAG", "當(dāng)前屏幕為橫屏"); isFullScreen = true; fullScreen(); screenBtn.setBackgroundResource(R.mipmap.small_screen); } else {Log.e("TAG", "當(dāng)前屏幕為豎屏"); isFullScreen = false; smallScreen(); screenBtn.setBackgroundResource(R.mipmap.large_screen); }super.onConfigurationChanged(newConfig); }//全屏 private void fullScreen() {getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);//設(shè)置成全屏模式 setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);//強(qiáng)制為橫屏 showFullSurface(); }//豎屏 private void smallScreen() {getWindow().setFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);//強(qiáng)制為豎屏 showSmallSurface(); }private void showFullSurface() {RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); params.width=getHeight() *Integer.parseInt(width)/Integer.parseInt(height); params.addRule(RelativeLayout.CENTER_IN_PARENT); surfaceView.setLayoutParams(params); }private void showSmallSurface() {RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); // params.height = getWidth() / 10 * 6; if (width!=null && height!=null){params.height = getWidth() *Integer.parseInt(height)/Integer.parseInt(width); }else {params.height = getWidth() / 10 * 6; }params.addRule(RelativeLayout.CENTER_IN_PARENT); surfaceView.setLayoutParams(params); }//進(jìn)度改變 @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {currTime.setText(formatTime(seekBar.getProgress())); if (isSetProgress) {Log.e("TAG", "onProgressChanged:refreshControlLayout"); isControl = false; refreshControlLayout(); }}//開始拖動 @Override public void onStartTrackingTouch(SeekBar seekBar) {currTime.setText(formatTime(seekBar.getProgress())); isSetProgress = true; isControl = false; refreshControlLayout(); }//停止拖動 @Override public void onStopTrackingTouch(SeekBar seekBar) {isSetProgress = false; isControl = false; refreshControlLayout(); if (isFirstLoadVideo) {return; }mediaPlayer.seekTo(seekBar.getProgress()); currTime.setText(formatTime(seekBar.getProgress())); }public int getWidth() {WindowManager manager = getWindowManager(); DisplayMetrics outMetrics = new DisplayMetrics(); manager.getDefaultDisplay().getMetrics(outMetrics); return outMetrics.widthPixels; }public int getHeight() {WindowManager manager = getWindowManager(); DisplayMetrics outMetrics = new DisplayMetrics(); manager.getDefaultDisplay().getMetrics(outMetrics); return outMetrics.heightPixels; }//更新進(jìn)度 private void updateSeekBar() {new Thread(new Runnable() {@Override public void run() {while (!isOnDestroy) { //結(jié)束線程標(biāo)示 if (isPlay && !isPause) {try {Message message = new Message(); message.what = mediaPlayer.getCurrentPosition(); handler.sendMessage(message); Log.e("TAG", "while"); Thread.sleep(1000); } catch (Exception e) {e.printStackTrace(); }}}}}).start(); }//播放完成 @Override public void onCompletion(MediaPlayer mp) { // Log.e("TAG", "播放完成"); playBtn.setBackgroundResource(R.mipmap.play); isPlay = false; isPlayCom = true; isControl = false; Message message = new Message(); message.what = mediaPlayer.getDuration(); handler.sendMessage(message); refreshControlLayout(); }//播放出錯 @Override public boolean onError(MediaPlayer mp, int what, int extra) {isPlay = false; return false; }private String formatTime(long time) {SimpleDateFormat format = new SimpleDateFormat("mm:ss"); return format.format(time); }@Override public void onBufferingUpdate(MediaPlayer mp, int percent) {Log.e("TAG", "onBufferingUpdate" + ",percent:" + percent); }//準(zhǔn)備完成 @Override public void onPrepared(MediaPlayer mp) {//設(shè)置最大進(jìn)度 seekBar.setMax(mediaPlayer.getDuration()); //設(shè)置按鈕背景圖片 playBtn.setBackgroundResource(R.mipmap.pause); //設(shè)置視頻最大時(shí)間 countTime.setText(formatTime(mediaPlayer.getDuration())); //隱藏加載進(jìn)度條 progressBar.setVisibility(View.INVISIBLE); //開始播放 mediaPlayer.start(); //更改播放狀態(tài) isPlay = true; //更改狀態(tài) if (isFirstLoadVideo)isFirstLoadVideo = false; //開啟線程更新進(jìn)度 updateSeekBar(); }@Override protected void onDestroy() {Log.e("TAG", "onDestroy"); isOnDestroy = true; if (mediaPlayer.isPlaying()) {mediaPlayer.stop(); isPlay = false; }mediaPlayer.release(); super.onDestroy(); }//seekTo()是異步的方法 在此監(jiān)聽是否執(zhí)行完畢 @Override public void onSeekComplete(MediaPlayer mp) {Log.e("TAG", "onSeekComplete"); if (!isPlay) {mediaPlayer.pause(); } else {mediaPlayer.start(); }}//監(jiān)聽返回鍵 如果是全屏狀態(tài)則返回豎屏 否則直接返回 @Override public boolean onKeyDown(int keyCode, KeyEvent event) {if (isFullScreen) {smallScreen(); return false; }return super.onKeyDown(keyCode, event); }@Override protected void onPause() {Log.e("TAG", "onPause"); isPause = true; if (mediaPlayer.isPlaying()) {mediaPlayer.pause(); }super.onPause(); }@Override protected void onResume() {super.onResume(); if (isPause && isPlay && mHolder.getSurface().isValid()) {isPause = false; mediaPlayer.start(); }}private void getPlayTime(String mUri){android.media.MediaMetadataRetriever mmr = new android.media.MediaMetadataRetriever(); try {if (mUri != null){HashMap<String, String> headers = null; if (headers == null){headers = new HashMap<String, String>(); headers.put("User-Agent", "Mozilla/5.0 (Linux; U; Android 4.4.2; zh-CN; MW-KW-001 Build/JRO03C) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 UCBrowser/1.0.0.001 U4/0.8.0 Mobile Safari/533.1"); }mmr.setDataSource(mUri, headers); } else {//mmr.setDataSource(mFD, mOffset, mLength); }String duration = mmr.extractMetadata(android.media.MediaMetadataRetriever.METADATA_KEY_DURATION);//時(shí)長(毫秒) width = mmr.extractMetadata(android.media.MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);//寬 height = mmr.extractMetadata(android.media.MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);//高 Toast.makeText(MainActivity.this, "playtime:"+ duration+"w="+width+"h="+height, Toast.LENGTH_SHORT).show(); } catch (Exception ex){Log.e("TAG", "MediaMetadataRetriever exception " + ex); } finally {mmr.release(); }}}
這里我是用的MediaPlayer結(jié)合SurfaceView進(jìn)行播放,首先,定義一個(gè)SurfaceView,SurfaceHolder,MediaPlayer,這個(gè)是視頻播放用到的關(guān)鍵技術(shù)。
由于activity繼承了SurfaceHolder.Callback,所以必須實(shí)現(xiàn)三個(gè)接口代碼。
//surfaceView創(chuàng)建完成@Overridepublic void surfaceCreated(SurfaceHolder holder) {Log.e("TAG", "surfaceCreated");//等surfaceView創(chuàng)建完成再開始播放視頻if (!isPause) {playUrl(VIDEO_URL);} else {isPause = false;mediaPlayer.setDisplay(holder);if (isPlay) mediaPlayer.start();}}//surfaceView改變@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {Log.e("TAG", "surfaceChanged");}//surfaceView銷毀@Overridepublic void surfaceDestroyed(SurfaceHolder holder) {Log.e("TAG", "surfaceDestroyed");}
這里當(dāng)SurfaceHolder初始化完成時(shí),調(diào)用surfaceCreated代碼,然后把MediaPlayer與SurfaceHolder進(jìn)行關(guān)聯(lián),通過代碼
??????????? mediaPlayer.setDisplay(mHolder);
??????????? mediaPlayer.setDataSource(url);?????????????? //設(shè)置播放的路徑
然后調(diào)用了mediaPlayer.prepareAsync();這個(gè)會回調(diào)public void onPrepared(MediaPlayer mp) ,在這個(gè)代碼里,
//準(zhǔn)備完成 @Override public void onPrepared(MediaPlayer mp) {//設(shè)置最大進(jìn)度 seekBar.setMax(mediaPlayer.getDuration()); //設(shè)置按鈕背景圖片 playBtn.setBackgroundResource(R.mipmap.pause); //設(shè)置視頻最大時(shí)間 countTime.setText(formatTime(mediaPlayer.getDuration())); //隱藏加載進(jìn)度條 progressBar.setVisibility(View.INVISIBLE); //開始播放 mediaPlayer.start(); //更改播放狀態(tài) isPlay = true; //更改狀態(tài) if (isFirstLoadVideo)isFirstLoadVideo = false; //開啟線程更新進(jìn)度 updateSeekBar(); }
就進(jìn)行了視頻的播放。
這個(gè)內(nèi)容講起來比較麻煩,自己也感覺沒有講明白,大家有興趣可以自己查找資料。
源碼下載:http://download.csdn.net/detail/bzlj2912009596/9868742
這是第一次上傳源碼,資源分比較貴,大家需要就下載。
android 自定義MP4播放器就講完了。
就這么簡單。
總結(jié)
以上是生活随笔為你收集整理的android 自定义MP4播放器的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: android 布局适配虚拟键适配
- 下一篇: android 获取MP4文件的图片大小