以前做的一个项目,与流媒体相关的,那时学了好多相关知识,还写了一个自定义的 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>