Android多媒体之视频播放器高级开发

来源:互联网 发布:网络暴力赚钱项目 编辑:程序博客网 时间:2024/05/22 01:56



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 查询方法二

SimpleQueryHandlerqueryHandler = 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);}@Overrideprotected 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);//相当于notify}}}

边里是调用的异步查询的方法,查询到的视频的相关信息也是包含在cursor中的,其中传入的adapter对象是继承于CursorAdapter


1.1.3 建立保存视频信息的info对象

public class VideoItem implements Serializable{public String title;//视频的名称public long size;//视频的大小public long duration;//视频的时长public String path;//视频在手机中的路径/** * 将cursor中的数据封装为一个info * @param cursor * @return */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);}/** * 直接从布局中加载view返回,这里就是ListView中显示的item布局,可以自定义随便布局 */@Overridepublic View newView(Context context, Cursor cursor, ViewGroup parent) {return View.inflate(context, R.layout.adapter_video_list, null);}ViewHolder holder;/** * 将数据设置给view */@Overridepublic void bindView(View view, Context context, Cursor cursor) {holder = getHolder(view);//获取数据,这里的数据是保存在cursor中,通过方法fromCursor直接转换成VideoItem对象保存的信息VideoItem videoItem = VideoItem.fromCursor(cursor);holder.tv_title.setText(videoItem.title);//这里获取到的时长是毫秒,可以通过 自定义的时间格式来对时间进行格式化操作holder.tv_duration.setText(StringUtil.formatVideoDuration(videoItem.duration()));//将视频的大小格式化为M来进行数据显示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() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic void onClick(View v) {if(currentPosition>0){currentPosition - -;playVideo();}}});
btn_pre 是播放上一节视频的触发按钮

这里的currentPosition是当前播放的视频在视频集合中的位置,调用playVideo,会将对应位置的视频信息获取出来,然后设置给VideoView,然后当资源准备完成后,会调用到监听OnPreparedListener中的onPrepared方法,来进行相应的视频播放


4.4 设置点击播放下一节视频的方法

btn_next.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {if(currentPosition<(videoList.size()-1)){currentPosition ++;playVideo();}}}});
btn_next是播放下一节视频的触发按钮,播放原理逻辑与播放上一节视频内容是一致的



4.5 为播放视频设置其他的相监听

//当视频播放完成时进行的操作监听video_view.setOnCompletionListener(new OnCompletionListener() {@Overridepublic void onCompletion(MediaPlayer mp) {//例如可以更新播放进度的指示大小,以及其他相关的UI操作}});//视频缓冲进度的监听video_view.setOnBufferingUpdateListener(new OnBufferingUpdateListener() {@Overridepublic void onBufferingUpdate(MediaPlayer mp, int percent) {//percent:0-100long bufferProgress = (long) ((video_view.getDuration()/100f)*percent);//这里设置显示视频缓冲进度条的显示   video_seekbar 是指示视频播放的进度条video_seekbar.setSecondaryProgress((int) bufferProgress);}});//视频播放卡顿时进行的操作video_view.setOnInfoListener(new OnInfoListener() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic void onStopTrackingTouch(SeekBar seekBar) {}@Overridepublic void onStartTrackingTouch(SeekBar seekBar) {}@Overridepublic void onProgressChanged(SeekBar seekBar, int progress,boolean fromUser) {if(fromUser){//fromUser =true;表示是用户手动拖动//播放相应位置的视频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:0-15   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() {@Overridepublic void onStopTrackingTouch(SeekBar seekBar) {}@Overridepublic void onStartTrackingTouch(SeekBar seekBar) {}@Overridepublic void onProgressChanged(SeekBar seekBar, int progress,boolean fromUser) {if(fromUser){//表示是用户手动拖动isMute = false;//记录当前的音量currentVolume = progress;//更新系统音量的操作updateSystemVolume();}}});

       /** * 更新系统音量 */private void updateSystemVolume(){if(isMute){//静音模式,设置音量大小为0,并将指示音量大小的进度条进度设置为0audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 0, 0);voice_seekbar.setProgress(0);}else {//设置当前改变到的音量大小的进度voice_seekbar.setProgress(currentVolume);//第三个参数如果是1,会显示音量改变的浮动面板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{@Overridepublic void onLongPress(MotionEvent e) {super.onLongPress(e);}@Overridepublic boolean onDoubleTap(MotionEvent e) {return super.onDoubleTap(e);}@Overridepublic boolean onSingleTapConfirmed(MotionEvent e) {return super.onSingleTapConfirmed(e);}}

捕捉触摸事件,并将事件传递给gestureDetector;

private float downY;@Overridepublic 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);//这个值一定是0if(moveDistance>0){//减小音量currentVolume -= 1;}else {//增大音量currentVolume += 1;}updateSystemVolume();downY = moveY;break;case MotionEvent.ACTION_UP:break;}return super.onTouchEvent(event);}








0 0
原创粉丝点击