1.获取播放的数据源
播放视频的数据源一般有两个,一个是请求网络,从服务器后台直接获取播放的视频信息,另一种是播放手机中本地的视频,这里我们采用的播放源为播放手机本地的视频
1.1 查询获取手机中的视频的信息
1.1.1 查询方法一
定义要查询到的视频的信息,包括视频的名称,视频大小,视频播放时长以及在手机中的储存位置
String[] projection = {Media._ID,Media.TITLE,Media.SIZE,Media.DURATION,Media.DATA};
进行数据的查询操作
Cursor cursor = getActivity().getContentResolver().query(Media.EXTERNAL_CONTENT_URI, projection, null, null, null);
这里查询到的信息是包含在cursor对象中的
1.1.2 查询方法二
SimpleQueryHandler queryHandler = new SimpleQueryHandler(getActivity().getContentResolver());
String[] projection = {Media._ID,Media.TITLE,Media.SIZE,Media.DURATION,Media.DATA};
queryHandler.startQuery(0, adapter, Media.EXTERNAL_CONTENT_URI, projection, null, null, null);
public class SimpleQueryHandler extends AsyncQueryHandler{
public SimpleQueryHandler(ContentResolver cr) {
super(cr);
}
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
super.onQueryComplete(token, cookie, cursor);
if(cookie!=null && cookie instanceof CursorAdapter){
CursorAdapter adapter = (CursorAdapter) cookie;
adapter.changeCursor(cursor);
}
}
}
边里是调用的异步查询的方法,查询到的视频的相关信息也是包含在cursor中的,其中传入的adapter对象是继承于CursorAdapter
1.1.3 建立保存视频信息的info对象
public class VideoItem implements Serializable{
public String title;
public long size;
public long duration;
public String path;
public static VideoItem fromCursor(Cursor cursor){
VideoItem videoItem = new VideoItem();
videoItem.duration=cursor.getLong(cursor.getColumnIndex(Media.DURATION)));
videoItem.path=cursor.getString(cursor.getColumnIndex(Media.DATA)));
videoItem.size=cursor.getLong(cursor.getColumnIndex(Media.SIZE)));
videoItem.title=cursor.getString(cursor.getColumnIndex(Media.TITLE)));
return videoItem;
}
}
2.以列表的方式将视频信息显示出来
2.1 创建数据适配器
这里使用到的是ListView来设置显示数据,所以需要给ListView准备一个适配器,来设置显示数据,由于我们查询到的数据是封装在Cursor中的,所以我们这里可以创建继承于CursorAdapter的适配器
public class VideoListAdapter extends CursorAdapter{
public VideoListAdapter(Context context, Cursor c) {
super(context, c);
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return View.inflate(context, R.layout.adapter_video_list, null);
}
ViewHolder holder;
@Override
public void bindView(View view, Context context, Cursor cursor) {
holder = getHolder(view);
VideoItem videoItem = VideoItem.fromCursor(cursor);
holder.tv_title.setText(videoItem.title);
holder.tv_duration.setText(StringUtil.formatVideoDuration(videoItem.duration()));
holder.tv_size.setText(Formatter.formatFileSize(context, videoItem.size()));
}
private ViewHolder getHolder(View view){
ViewHolder viewHolder = (ViewHolder) view.getTag();
if(viewHolder==null){
viewHolder = new ViewHolder(view);
view.setTag(viewHolder);
}
return viewHolder;
}
class ViewHolder{
TextView tv_title,tv_duration,tv_size;
public ViewHolder(View view){
tv_title = (TextView) view.findViewById(R.id.tv_title);
tv_duration = (TextView) view.findViewById(R.id.tv_duration);
tv_size = (TextView) view.findViewById(R.id.tv_size);
}
}
}
2.2 为ListView设置显示数据
VideoListAdapter adapter = new VideoListAdapter(getActivity(), null);
listView.setAdapter(adapter);
注:到这里,我们就 可以将手机中的视频的信息显示到一个ListView中了
3.点击信息条目,跳转到视频播放页面
3.1 为ListView设置条目的点击事件,并获取到相应位置的视频信息
listView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Cursor cursor = (Cursor) adapter.getItem(position);
Bundle bundle = new Bundle();
bundle.putInt("currentPosition", position);
bundle.putSerializable("videoList", cursorToList(cursor));
Intent intent = new Intent(getActivity(),VitamioPlayerActivity.class);
intent.putExtras(bundle);
getActivity().startActivity(intent);
}
});
其中调用了一个方法,是将cursor中的数据转换成集合中保存对象的方法保存信息,以方便数据的传递
private ArrayList<VideoItem> cursorToList(Cursor cursor){
ArrayList<VideoItem> list = new ArrayList<VideoItem>();
cursor.moveToPosition(-1);
while(cursor.moveToNext()){
list.add(VideoItem.fromCursor(cursor));
}
return list;
}
其中调用的VideoItem.fromCursor方法是对象中封装的转换cursor对象数据的方法
4.视频播放页面
4.1 获取传递过来的视频信息
private int currentPosition;
private ArrayList<VideoItem> videoList;
currentPosition = getIntent().getExtras().getInt("currentPosition");
videoList = (ArrayList<VideoItem>) getIntent().getExtras().getSerializable("videoList");
4.2 设置视频播放并设置监听
这里使用到的是控件VideoView,
playVideo();
video_view.setOnPreparedListener(new OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
video_view.start();
updatePlayProgress();
video_seekbar.setMax((int) video_view.getDuration());
tv_current_position.setText("00:00");
tv_duration.setText(StringUtil.formatVideoDuration(video_view.getDuration()));
btn_play.setBackgroundResource(R.drawable.selector_btn_pause);
}
});
调用的方法playVideo
private void playVideo(){
btn_pre.setEnabled(currentPosition!=0);
btn_next.setEnabled(currentPosition!=(videoList.size()-1));
VideoItem videoItem = videoList.get(currentPosition);
tv_name.setText(videoItem.getTitle());
video_view.setVideoURI(Uri.parse(videoItem.getPath()));
}
playVideo方法为videoview设置了播放的数据源,当videoView加载准备好后,由于设置了监听OnPreparedListener方法,所以会调用其中的onPrepared,在这里进行开始播放视频的操作,并可以获取到当前要播放的视频的总时长,以设置指示进度的进度条显示的最大值,以及更新其他相关的UI等
4.3 设置点击播放上一节视频的方法
btn_pre.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if(currentPosition>0){
currentPosition - -;
playVideo();
}
}
});
btn_pre 是播放上一节视频的触发按钮
这里的currentPosition是当前播放的视频在视频集合中的位置,调用playVideo,会将对应位置的视频信息获取出来,然后设置给VideoView,然后当资源准备完成后,会调用到监听OnPreparedListener中的onPrepared方法,来进行相应的视频播放
4.4 设置点击播放下一节视频的方法
btn_next.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if(currentPosition<(videoList.size()-1)){
currentPosition ++;
playVideo();
}
}
}
});
btn_next是播放下一节视频的触发按钮,播放原理逻辑与播放上一节视频内容是一致的
4.5 为播放视频设置其他的相监听
video_view.setOnCompletionListener(new OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
}
});
video_view.setOnBufferingUpdateListener(new OnBufferingUpdateListener() {
@Override
public void onBufferingUpdate(MediaPlayer mp, int percent) {
long bufferProgress = (long) ((video_view.getDuration()/100f)*percent);
video_seekbar.setSecondaryProgress((int) bufferProgress);
}
});
video_view.setOnInfoListener(new OnInfoListener() {
@Override
public boolean onInfo(MediaPlayer mp, int what, int extra) {
switch (what) {
case MediaPlayer.MEDIA_INFO_BUFFERING_START :
ll_buffer.setVisibility(View.VISIBLE);
break;
case MediaPlayer.MEDIA_INFO_BUFFERING_END :
ll_buffer.setVisibility(View.GONE);
break;
}
return true;
}
});
video_view.setOnErrorListener(new OnErrorListener() {
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
switch (what) {
case MediaPlayer.MEDIA_ERROR_UNKNOWN :
Toast.makeText(VitamioPlayerActivity.this, "视频格式不支持", 0).show();
break;
}
return true;
}
});
}
4.6 对播放中指示播放进度的进度条(video_seekbar)的操作
4.6.1 指示缓存进度的操作
在setOnBufferingUpdateListener这个监听中操作了video_seekbar的缓存进行更新
4.6.2 实时更新播放的指示进度
private void updatePlayProgress(){
tv_current_position.setText(StringUtil.formatVideoDuration(video_view.getCurrentPosition()));
video_seekbar.setProgress((int) video_view.getCurrentPosition());
handler.sendEmptyMessageDelayed(1, 500);
}
private Handler handler = new Handler(){
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case 1:
updatePlayProgress();
break;
};
};
可以看到,这里使用的是handler消息机制,循环发送消息
4.6.3 手动拖动进度条控制视频播放的进度
video_seekbar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
if(fromUser){
video_view.seekTo(progress);
tv_current_position.setText(StringUtil.formatVideoDuration(progress));
}
}
});
4.7 对播放中指示播放音量的进度条操作
4.7.1 初始化显示系统的音量
private AudioManager audioManager;
private int currentVolume;
private boolean isMute = false;
private int maxVolume;
private SeekBar voice_seekbar;
private void initVolume(){
audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
voice_seekbar.setMax(maxVolume);
voice_seekbar.setProgress(currentVolume);
}
voice_seedbar 是视频页面指示播放音量大小的进度条
4.7.2 手动拖动进度条改变播放音量的大小
与拖动指示播放进度的进度条的方法一致
voice_seekbar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
if(fromUser){
isMute = false;
currentVolume = progress;
updateSystemVolume();
}
}
});
private void updateSystemVolume(){
if(isMute){
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 0, 0);
voice_seekbar.setProgress(0);
}else {
voice_seekbar.setProgress(currentVolume);
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, currentVolume, 0);
}
}
4.7.3 手动上下滑动屏幕改变播放音量的大小
涉及到手势操作,做一些初始化操作准备
int touchSlop = ViewConfiguration.getTouchSlop();
GestureDetector gestureDetector = new GestureDetector(this,new MyOnGestureListner());
可以在相关方法中做一些滑动过程中的操作
private class MyOnGestureListner extends SimpleOnGestureListener{
@Override
public void onLongPress(MotionEvent e) {
super.onLongPress(e);
}
@Override
public boolean onDoubleTap(MotionEvent e) {
return super.onDoubleTap(e);
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
return super.onSingleTapConfirmed(e);
}
}
捕捉触摸事件,并将事件传递给gestureDetector;
private float downY;
@Override
public boolean onTouchEvent(MotionEvent event) {
gestureDetector.onTouchEvent(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downY = event.getY();
break;
case MotionEvent.ACTION_MOVE:
float moveY = event.getY();
float moveDistance = moveY - downY;
if(Math.abs(moveDistance)<touchSlop)break;
isMute = false;
float totalDistance = Math.min(screenHeight, screenWidth);
float movePercent = Math.abs(moveDistance)/totalDistance;
int moveVolume = (int) (movePercent*maxVolume);
if(moveDistance>0){
currentVolume -= 1;
}else {
currentVolume += 1;
}
updateSystemVolume();
downY = moveY;
break;
case MotionEvent.ACTION_UP:
break;
}
return super.onTouchEvent(event);
}