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



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);//相当于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布局,可以自定义随便布局
		 */
		@Override
		public View newView(Context context, Cursor cursor, ViewGroup parent) {
			return View.inflate(context, R.layout.adapter_video_list, null);
		}
		
		ViewHolder holder;
		/**
		 * 将数据设置给view
		 */
		@Override
		public 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() {

			@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) {
			//例如可以更新播放进度的指示大小,以及其他相关的UI操作
				
			}
		});
//视频缓冲进度的监听
		video_view.setOnBufferingUpdateListener(new OnBufferingUpdateListener() {
			@Override
			public void onBufferingUpdate(MediaPlayer mp, int percent) {
				//percent:0-100
				long bufferProgress = (long) ((video_view.getDuration()/100f)*percent);
				//这里设置显示视频缓冲进度条的显示   video_seekbar 是指示视频播放的进度条
				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){
					//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() {
			@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){
		//静音模式,设置音量大小为0,并将指示音量大小的进度条进度设置为0
			audioManager.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{
		@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);//这个值一定是0
			
			if(moveDistance>0){
				//减小音量
				currentVolume -= 1;
			}else {
				//增大音量
				currentVolume += 1;
			}
			updateSystemVolume();
			
			downY = moveY;
			break;
		case MotionEvent.ACTION_UP:
			
			break;
		}
		return super.onTouchEvent(event);
	}








早起的年轻人 CSDN认证博客专家 移动开发 项目管理 Java
只要用心去做,每一件事情还是有可能成功的,当然成功是没有界限的,只不过是达到自己心里的那个目标,公众号:我的大前端生涯,一个爱喝茶的程序员,通常会搞搞SpringBoot 、Herbinate、Mybatiys、Android、iOS、Flutter、Vue、小程序等.
©️2020 CSDN 皮肤主题: 代码科技 设计师:Amelia_0503 返回首页