以前做的一个项目,与流媒体相关的,那时学了好多相关知识,还写了一个自定义的 MediaPlayer Controller。现在稍微有点时间,简单整理一下,顺便分享出来。你要做的只是实现 Controller.ControlOper,并把 Controller 设置为你的 MediaPlayer 的控制器即可。注意那个mPlayer.setDataSource()里面那个 url,是个开放的
rtsp 测试地址,可能会失效哦,如果失效的话,会看到“黑屏”,出现这种情况的话,请自己换个别的测试地址哦。
另外,在 onDestory() 里面要记得释放相关资源哦!
@Override protected void onDestroy() { super.onDestroy(); releaseMediaPlayer(); } private void releaseMediaPlayer() { mController.removeHandlerCallback(); if (mPlayer != null) { mPlayer.stop(); mPlayer.release(); } } public void removeHandlerCallback() { if(mHandler != null) { mHandler.removeCallbacksAndMessages(null); mHandler = null; } }
一、MediaplayerController
MediaplayerController(以下简称 Controller)是一个用于 MediaPlayer 的 view 类。该 view 上有通用的控件,如播放/暂停,前进/后退等。你需要做的只是实现 Controller.ControlOper 接口,并把该控制器设置为你的 MediaPlayer 的控制器即可。控制器的 view 都抽出来了,你可以根据需要进行修改,做出漂亮的控制器来了。
public class Controller extends FrameLayout { private static final String LOG_TAG = Controller.class.getName(); private static final int FADE_OUT = 1; private static final int DEFTIMEOUT = 3000; private static final int SHOW_PROGRESS = 2; private ImageButton mBtnPause; private ImageButton mBtnFfwd; private ImageButton mBtnRew; private ImageButton mBtnNext; private ImageButton mBtnPrev; private ImageButton mBtnFullscreen; private Handler mHandler = new MsgHandler(this); private ControlOper mPlayerCtrl; private Context mContext; private ViewGroup mAnchorVGroup; private View mRootView; private ProgressBar mProgress; private TextView mEndTime, mCurTime; private boolean mIsShowing; private boolean mIsDragging; private boolean mUseFastForward; private boolean mFromXml; private boolean mIsListenersSet; private View.OnClickListener mNextListener, mPrevListener; StringBuilder mStrBuilder; Formatter mFormatter; public Controller(Context context) { this(context, true); } public Controller(Context context, AttributeSet attrs) { super(context, attrs); mRootView = null; mContext = context; mUseFastForward = true; mFromXml = true; } public Controller(Context context, boolean useFastForward) { super(context); mContext = context; mUseFastForward = useFastForward; } public void removeHandlerCallback() { if(mHandler != null) { mHandler.removeCallbacksAndMessages(null); mHandler = null; } } @Override public void onFinishInflate() { if (mRootView != null) { initCtrlView(mRootView); } } public void setMediaPlayer(ControlOper player) { mPlayerCtrl = player; updatePausePlay(); updateFullScreen(); } public void setAnchorView(ViewGroup view) { mAnchorVGroup = view; FrameLayout.LayoutParams frameParams = new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ); removeAllViews(); View v = createCtrlView(); addView(v, frameParams); } protected View createCtrlView() { LayoutInflater inflate = (LayoutInflater) mContext .getSystemService(Context.LAYOUT_INFLATER_SERVICE); mRootView = inflate.inflate(R.layout.controller, null); initCtrlView(mRootView); return mRootView; } private void initCtrlView(View v) { mBtnPause = (ImageButton) v.findViewById(R.id.pause); if (mBtnPause != null) { mBtnPause.requestFocus(); mBtnPause.setOnClickListener(mPauseListener); } mBtnFullscreen = (ImageButton) v.findViewById(R.id.fullscreen); if (mBtnFullscreen != null) { mBtnFullscreen.requestFocus(); mBtnFullscreen.setOnClickListener(mFullscreenListener); } mBtnFfwd = (ImageButton) v.findViewById(R.id.ffwd); if (mBtnFfwd != null) { mBtnFfwd.setOnClickListener(mFfwdListener); if (!mFromXml) { mBtnFfwd.setVisibility(mUseFastForward ? View.VISIBLE : View.GONE); } } mBtnRew = (ImageButton) v.findViewById(R.id.rew); if (mBtnRew != null) { mBtnRew.setOnClickListener(mRewListener); if (!mFromXml) { mBtnRew.setVisibility(mUseFastForward ? View.VISIBLE : View.GONE); } } // By default these are hidden. They will be enabled when setPrevNextListeners() is called mBtnNext = (ImageButton) v.findViewById(R.id.next); if (mBtnNext != null && !mFromXml && !mIsListenersSet) { mBtnNext.setVisibility(View.GONE); } mBtnPrev = (ImageButton) v.findViewById(R.id.prev); if (mBtnPrev != null && !mFromXml && !mIsListenersSet) { mBtnPrev.setVisibility(View.GONE); } mProgress = (ProgressBar) v.findViewById(R.id.mediacontroller_progress); if (mProgress != null) { if (mProgress instanceof SeekBar) { SeekBar seeker = (SeekBar) mProgress; seeker.setOnSeekBarChangeListener(mSeekListener); } mProgress.setMax(1000); } mEndTime = (TextView) v.findViewById(R.id.time); mCurTime = (TextView) v.findViewById(R.id.time_current); mStrBuilder = new StringBuilder(); mFormatter = new Formatter(mStrBuilder, Locale.getDefault()); installPrevNextListeners(); } /** * Show the controller on screen. It will go away automatically after * 3 seconds of inactivity. */ public void show() { show(DEFTIMEOUT); } /** * Disable pause or seek buttons if the stream cannot be paused or seeked. * This requires the control interface to be a MediaPlayerControlExt */ private void disableUnsupportedButtons() { if (mPlayerCtrl == null) { return; } try { if (mBtnPause != null && !mPlayerCtrl.canPause()) { mBtnPause.setEnabled(false); } if (mBtnRew != null && !mPlayerCtrl.canSeekBackward()) { mBtnRew.setEnabled(false); } if (mBtnFfwd != null && !mPlayerCtrl.canSeekForward()) { mBtnFfwd.setEnabled(false); } } catch (IncompatibleClassChangeError ex) { } } public void show(int timeout) { if (!mIsShowing && mAnchorVGroup != null) { setProgress(); if (mBtnPause != null) { mBtnPause.requestFocus(); } disableUnsupportedButtons(); FrameLayout.LayoutParams tlp = new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.BOTTOM ); mAnchorVGroup.addView(this, tlp); mIsShowing = true; } updatePausePlay(); updateFullScreen(); mHandler.sendEmptyMessage(SHOW_PROGRESS); Message msg = mHandler.obtainMessage(FADE_OUT); if (timeout != 0) { mHandler.removeMessages(FADE_OUT); mHandler.sendMessageDelayed(msg, timeout); } } public boolean isShowing() { return mIsShowing; } /** * Remove the controller from the screen. */ public void hide() { if (mAnchorVGroup == null) { return; } try { mAnchorVGroup.removeView(this); if(mHandler != null) { mHandler.removeMessages(SHOW_PROGRESS); } } catch (IllegalArgumentException ex) { Log.w("MediaController", "already removed"); } mIsShowing = false; } private String stringForTime(int timeMs) { int totalSeconds = timeMs / 1000; int seconds = totalSeconds % 60; int minutes = (totalSeconds / 60) % 60; int hours = totalSeconds / 3600; mStrBuilder.setLength(0); if (hours > 0) { return mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString(); } else { return mFormatter.format("%02d:%02d", minutes, seconds).toString(); } } private int setProgress() { if (mPlayerCtrl == null || mIsDragging) { return 0; } int position = mPlayerCtrl.getCurPosition(); int duration = mPlayerCtrl.getDuration(); if (mProgress != null) { if (duration > 0) { // use long to avoid overflow long pos = 1000L * position / duration; mProgress.setProgress( (int) pos); } int percent = mPlayerCtrl.getBufPercent(); mProgress.setSecondaryProgress(percent * 10); } if (mEndTime != null) mEndTime.setText(stringForTime(duration)); if (mCurTime != null) mCurTime.setText(stringForTime(position)); return position; } @Override public boolean onTouchEvent(MotionEvent event) { show(DEFTIMEOUT); return true; } @Override public boolean onTrackballEvent(MotionEvent ev) { show(DEFTIMEOUT); return false; } @Override public boolean dispatchKeyEvent(KeyEvent event) { if (mPlayerCtrl == null) { return true; } int keyCode = event.getKeyCode(); final boolean uniqueDown = event.getRepeatCount() == 0 && event.getAction() == KeyEvent.ACTION_DOWN; if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE || keyCode == KeyEvent.KEYCODE_SPACE) { if (uniqueDown) { doPauseResume(); show(DEFTIMEOUT); if (mBtnPause != null) { mBtnPause.requestFocus(); } } return true; } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) { if (uniqueDown && !mPlayerCtrl.isPlaying()) { mPlayerCtrl.start(); updatePausePlay(); show(DEFTIMEOUT); } return true; } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) { if (uniqueDown && mPlayerCtrl.isPlaying()) { mPlayerCtrl.pause(); updatePausePlay(); show(DEFTIMEOUT); } return true; } else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_MUTE) { // don't show the controls for volume adjustment return super.dispatchKeyEvent(event); } else if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU) { if (uniqueDown) { hide(); } return true; } show(DEFTIMEOUT); return super.dispatchKeyEvent(event); } private View.OnClickListener mPauseListener = new View.OnClickListener() { public void onClick(View v) { doPauseResume(); show(DEFTIMEOUT); } }; private View.OnClickListener mFullscreenListener = new View.OnClickListener() { public void onClick(View v) { doToggleFullscreen(); show(DEFTIMEOUT); } }; public void updatePausePlay() { if (mRootView == null || mBtnPause == null || mPlayerCtrl == null) { return; } if (mPlayerCtrl.isPlaying()) { mBtnPause.setImageResource(R.drawable.pause); } else { mBtnPause.setImageResource(R.drawable.play); } } public void updateFullScreen() { if (mRootView == null || mBtnFullscreen == null || mPlayerCtrl == null) { return; } if (mPlayerCtrl.isFullScreen()) { mBtnFullscreen.setImageResource(R.drawable.nscale); } else { mBtnFullscreen.setImageResource(R.drawable.scale); } } private void doPauseResume() { if (mPlayerCtrl == null) { return; } if (mPlayerCtrl.isPlaying()) { mPlayerCtrl.pause(); } else { mPlayerCtrl.start(); } updatePausePlay(); } private void doToggleFullscreen() { if (mPlayerCtrl == null) { return; } Log.i(LOG_TAG, "doToggleFullscreen"); mPlayerCtrl.fullScreen(); } private OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() { public void onStartTrackingTouch(SeekBar bar) { show(3600000); mIsDragging = true; mHandler.removeMessages(SHOW_PROGRESS); } public void onProgressChanged(SeekBar bar, int progress, boolean fromuser) { if (mPlayerCtrl == null) { return; } if (!fromuser) { // We're not interested in programmatically generated changes to // the progress bar's position. return; } long duration = mPlayerCtrl.getDuration(); long newposition = (duration * progress) / 1000L; mPlayerCtrl.seekTo( (int) newposition); if (mCurTime != null) mCurTime.setText(stringForTime( (int) newposition)); } public void onStopTrackingTouch(SeekBar bar) { mIsDragging = false; setProgress(); updatePausePlay(); show(DEFTIMEOUT); mHandler.sendEmptyMessage(SHOW_PROGRESS); } }; @Override public void setEnabled(boolean enabled) { if (mBtnPause != null) { mBtnPause.setEnabled(enabled); } if (mBtnFfwd != null) { mBtnFfwd.setEnabled(enabled); } if (mBtnRew != null) { mBtnRew.setEnabled(enabled); } if (mBtnNext != null) { mBtnNext.setEnabled(enabled && mNextListener != null); } if (mBtnPrev != null) { mBtnPrev.setEnabled(enabled && mPrevListener != null); } if (mProgress != null) { mProgress.setEnabled(enabled); } disableUnsupportedButtons(); super.setEnabled(enabled); } @SuppressLint("NewApi") @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); event.setClassName(Controller.class.getName()); } @SuppressLint("NewApi") @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); info.setClassName(Controller.class.getName()); } private View.OnClickListener mRewListener = new View.OnClickListener() { public void onClick(View v) { if (mPlayerCtrl == null) { return; } int pos = mPlayerCtrl.getCurPosition(); pos -= 5000; // milliseconds mPlayerCtrl.seekTo(pos); setProgress(); show(DEFTIMEOUT); } }; private View.OnClickListener mFfwdListener = new View.OnClickListener() { public void onClick(View v) { if (mPlayerCtrl == null) { return; } int pos = mPlayerCtrl.getCurPosition(); pos += 15000; // milliseconds mPlayerCtrl.seekTo(pos); setProgress(); show(DEFTIMEOUT); } }; private void installPrevNextListeners() { if (mBtnNext != null) { mBtnNext.setOnClickListener(mNextListener); mBtnNext.setEnabled(mNextListener != null); } if (mBtnPrev != null) { mBtnPrev.setOnClickListener(mPrevListener); mBtnPrev.setEnabled(mPrevListener != null); } } public void setPrevNextListeners(View.OnClickListener next, View.OnClickListener prev) { mNextListener = next; mPrevListener = prev; mIsListenersSet = true; if (mRootView != null) { installPrevNextListeners(); if (mBtnNext != null && !mFromXml) { mBtnNext.setVisibility(View.VISIBLE); } if (mBtnPrev != null && !mFromXml) { mBtnPrev.setVisibility(View.VISIBLE); } } } public interface ControlOper { void start(); void pause(); int getDuration(); int getCurPosition(); void seekTo(int pos); boolean isPlaying(); int getBufPercent(); boolean canPause(); boolean canSeekBackward(); boolean canSeekForward(); boolean isFullScreen(); void fullScreen(); } private static class MsgHandler extends Handler { private final WeakReference<Controller> mView; MsgHandler(Controller view) { mView = new WeakReference<Controller>(view); } @Override public void handleMessage(Message msg) { Controller view = mView.get(); if (view == null || view.mPlayerCtrl == null) { return; } int pos; switch (msg.what) { case FADE_OUT: { view.hide(); break; } case SHOW_PROGRESS: { if(view.mPlayerCtrl.isPlaying()) { pos = view.setProgress(); } else { return; } if (!view.mIsDragging && view.mIsShowing && view.mPlayerCtrl.isPlaying()) { msg = obtainMessage(SHOW_PROGRESS); sendMessageDelayed(msg, 1000 - (pos % 1000)); } break; } } } } }
二、MainActivity
package mediaplayer; import java.io.IOException; import com.demo.player.R; import android.app.Activity; import android.media.AudioManager; import android.media.MediaPlayer; import android.net.Uri; import android.os.Bundle; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.widget.FrameLayout; import android.widget.Toast; public class Player extends Activity { MediaPlayer mPlayer; SurfaceView mSurfaceView; Controller mController; SurfaceHolder.Callback mCallback; MediaPlayer.OnPreparedListener mPreparedListener; Controller.ControlOper mPlayerControl; private long mExitTime = 0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_player); initListeners(); mSurfaceView = (SurfaceView) findViewById(R.id.surfaceview); SurfaceHolder surfaceHolder = mSurfaceView.getHolder(); surfaceHolder.addCallback(mCallback); mPlayer = new MediaPlayer(); mController = new Controller(this); try { mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mPlayer.setDataSource(this, Uri.parse("rtsp://184.72.239.149" + "/vod/mp4:BigBuckBunny_115k.mov")); mPlayer.setOnPreparedListener(mPreparedListener); mPlayer.prepareAsync(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (SecurityException e) { e.printStackTrace(); } catch (IllegalStateException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } private void releaseMediaPlayer() { mController.removeHandlerCallback(); if (mPlayer != null) { mPlayer.stop(); mPlayer.release(); } } @Override public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); mController.show(); return false; } @Override protected void onDestroy() { super.onDestroy(); releaseMediaPlayer(); } @Override protected void onPause() { super.onPause(); mPlayer.pause(); } @Override public void onBackPressed() { if ((System.currentTimeMillis() - mExitTime) > 1500) { Toast.makeText(this, "再按一次退出程序", Toast.LENGTH_SHORT).show(); mExitTime = System.currentTimeMillis(); } else { finish(); } return; } private void initListeners() { mCallback = new SurfaceHolder.Callback() { public void surfaceCreated(SurfaceHolder holder) { mPlayer.setDisplay(holder); } public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } public void surfaceDestroyed(SurfaceHolder holder) { } }; mPreparedListener = new MediaPlayer.OnPreparedListener() { public void onPrepared(MediaPlayer mp) { mController.setMediaPlayer(mPlayerControl); mController.setAnchorView((FrameLayout)findViewById(R.id.surfacecontainer)); mPlayer.start(); } }; mPlayerControl = new Controller.ControlOper() { public void start() { mPlayer.start(); } public void pause() { mPlayer.pause(); } public int getDuration() { return mPlayer.getDuration(); } public int getCurPosition() { return mPlayer.getCurrentPosition(); } public void seekTo(int pos) { mPlayer.seekTo(pos); } public boolean isPlaying() { return mPlayer.isPlaying(); } public int getBufPercent() { return 0; } public boolean canPause() { return true; } public boolean canSeekBackward() { return true; } public boolean canSeekForward() { return true; } public boolean isFullScreen() { return false; } public void fullScreen() { } }; } }
四、controller.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#CC000000" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:paddingTop="4dip" android:orientation="horizontal"> <ImageButton android:id="@+id/prev" style="@android:style/MediaButton.Previous" android:contentDescription="@string/description" /> <ImageButton android:id="@+id/rew" style="@android:style/MediaButton.Rew" android:contentDescription="@string/description" /> <ImageButton android:id="@+id/pause" style="@android:style/MediaButton.Play" android:contentDescription="@string/description" /> <ImageButton android:id="@+id/ffwd" style="@android:style/MediaButton.Ffwd" android:contentDescription="@string/description" /> <ImageButton android:id="@+id/next" style="@android:style/MediaButton.Next" android:contentDescription="@string/description" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <TextView android:id="@+id/time_current" android:textSize="14sp" android:textStyle="bold" android:paddingTop="4dip" android:paddingLeft="4dip" android:layout_gravity="center_horizontal" android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingRight="4dip" /> <SeekBar android:id="@+id/mediacontroller_progress" style="?android:attr/progressBarStyleHorizontal" android:layout_width="0dip" android:layout_weight="1" android:layout_height="21dip" /> <TextView android:id="@+id/time" android:textSize="14sp" android:textStyle="bold" android:paddingTop="4dip" android:paddingRight="4dip" android:layout_gravity="center_horizontal" android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingLeft="4dip" /> <ImageButton android:id="@+id/fullscreen" android:layout_marginTop="-7dp" android:paddingTop="4dip" android:paddingBottom="4dip" android:paddingLeft="10dip" android:paddingRight="4dip" android:layout_gravity="top" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@android:color/transparent" android:contentDescription="@string/description" /> </LinearLayout> </LinearLayout>
五、activity_player.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/black" android:gravity="center_horizontal|center_vertical" android:orientation="vertical" > <FrameLayout android:id="@+id/surfacecontainer" android:layout_width="match_parent" android:layout_height="wrap_content" > <SurfaceView android:id="@+id/surfaceview" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </FrameLayout> </LinearLayout>