在Android中,我们有三种方式来实现视频的播放:
Vitamio 框架 使用起来和VideoView基本一样,区别在于,我们自己写的和系统自带的控件无法做到所有格式的视频都可以播放,但是这个框架基本可以实现,我用真机测试了mp4,flv,mkv,rmvb,3gp,avi这5中格式的视频,其中只有avi的只有声音没有影像,其他都可以正常播放
vitamio的library下载地址是:https://www.vitamio.org/Download/
Uri uri = Uri.parse(Environment.getExternalStorageDirectory().getPath()+"/Test_Movie.mp4");
//调用系统自带的播放器
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(uri, "video/mp4");
startActivity(intent);
Uri uri = Uri.parse(Environment.getExternalStorageDirectory().getPath()+"/Test_Movie.mp4");
VideoView videoView = this.findViewById(R.id.video_view);
videoView.setMediaController(new MediaController(this));
videoView.setVideoURI(uri);
videoView.start();
videoView.requestFocus();
代码实现:
package com.example.administrator;
import java.io.File;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener;
import android.media.MediaPlayer.OnPreparedListener;
import android.os.Bundle;
import android.app.Activity;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Button;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.Toast;
public class MainActivity extends Activity {
private final String TAG = "main";
private SurfaceView sv;
private Button btn_play, btn_pause, btn_replay, btn_stop;
private MediaPlayer mediaPlayer;
private SeekBar seekBar;
private int currentPosition = 0;
private boolean isPlaying;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
seekBar = findViewById(R.id.seekBar);
sv = findViewById(R.id.sv);
btn_play = findViewById(R.id.btn_play);
btn_pause = findViewById(R.id.btn_pause);
btn_replay = findViewById(R.id.btn_replay);
btn_stop = findViewById(R.id.btn_stop);
btn_play.setOnClickListener(click);
btn_pause.setOnClickListener(click);
btn_replay.setOnClickListener(click);
btn_stop.setOnClickListener(click);
// 为SurfaceHolder添加回调
sv.getHolder().addCallback(callback);
// 4.0版本之下需要设置的属性
// 设置Surface不维护自己的缓冲区,而是等待屏幕的渲染引擎将内容推送到界面
// sv.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
// 为进度条添加进度更改事件
seekBar.setOnSeekBarChangeListener(change);
}
private Callback callback = new Callback() {
// SurfaceHolder被修改的时候回调
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
Log.i(TAG, "SurfaceHolder 被销毁");
// 销毁SurfaceHolder的时候记录当前的播放位置并停止播放
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
currentPosition = mediaPlayer.getCurrentPosition();
mediaPlayer.stop();
}
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
Log.i(TAG, "SurfaceHolder 被创建");
if (currentPosition > 0) {
// 创建SurfaceHolder的时候,如果存在上次播放的位置,则按照上次播放位置进行播放
play(currentPosition);
currentPosition = 0;
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Log.i(TAG, "SurfaceHolder 大小被改变");
}
};
private OnSeekBarChangeListener change = new OnSeekBarChangeListener() {
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
// 当进度条停止修改的时候触发
// 取得当前进度条的刻度
int progress = seekBar.getProgress();
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
// 设置当前播放的位置
mediaPlayer.seekTo(progress);
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
}
};
private View.OnClickListener click = new View.OnClickListener() {
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_play:
play(0);
break;
case R.id.btn_pause:
pause();
break;
case R.id.btn_replay:
replay();
break;
case R.id.btn_stop:
stop();
break;
default:
break;
}
}
};
/*
* 停止播放
*/
protected void stop() {
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer = null;
btn_play.setEnabled(true);
isPlaying = false;
}
}
/**
* 开始播放
*
* @param msec 播放初始位置
*/
protected void play(final int msec) {
// 获取视频文件地址
//String path = et_path.getText().toString().trim();
String path = "/storage/emulated/0/Test_Movie.mp4";
File file = new File(path);
if (!file.exists()) {
Toast.makeText(this, "视频文件路径错误", Toast.LENGTH_SHORT).show();
return;
}
try {
mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
// 设置播放的视频源
mediaPlayer.setDataSource(file.getAbsolutePath());
// 设置显示视频的SurfaceHolder
mediaPlayer.setDisplay(sv.getHolder());
Log.i(TAG, "开始装载");
mediaPlayer.prepareAsync();
mediaPlayer.setOnPreparedListener(new OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
Log.i(TAG, "装载完成");
mediaPlayer.start();
// 按照初始位置播放
mediaPlayer.seekTo(msec);
// 设置进度条的最大进度为视频流的最大播放时长
seekBar.setMax(mediaPlayer.getDuration());
// 开始线程,更新进度条的刻度
new Thread() {
@Override
public void run() {
try {
isPlaying = true;
while (isPlaying) {
int current = mediaPlayer.getCurrentPosition();
seekBar.setProgress(current);
sleep(500);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
btn_play.setEnabled(false);
}
});
mediaPlayer.setOnCompletionListener(new OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
// 在播放完毕被回调
btn_play.setEnabled(true);
}
});
mediaPlayer.setOnErrorListener(new OnErrorListener() {
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
// 发生错误重新播放
play(0);
isPlaying = false;
return false;
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 重新开始播放
*/
protected void replay() {
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.seekTo(0);
Toast.makeText(this, "重新播放", Toast.LENGTH_SHORT).show();
btn_pause.setText("暂停");
return;
}
isPlaying = false;
play(0);
}
/**
* 暂停或继续
*/
protected void pause() {
if (btn_pause.getText().toString().trim().equals("继续")) {
btn_pause.setText("暂停");
mediaPlayer.start();
Toast.makeText(this, "继续播放", Toast.LENGTH_SHORT).show();
return;
}
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.pause();
btn_pause.setText("继续");
Toast.makeText(this, "暂停播放", Toast.LENGTH_SHORT).show();
}
}}
Android得到视频缩略图,可以通过接口类 MediaMetadataRetriever 来实现
其中函数getFrameAtTime()有其他重载函数,该函数会随机选择一帧抓取,
如果想要指定具体时间的缩略图,可以用函数getFrameAtTime(long timeUs), getFrameAtTime(long timeUs, int option)
代码实现:
package com.example.administrator;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.media.MediaMetadataRetriever;
import android.net.Uri;
import android.os.Environment;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Main2Activity extends Activity {
private ListView lv;
private Map pathMap = new HashMap<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
lv = findViewById(R.id.lv);
//检查当前权限(若没有该权限,值为-1;若有该权限,值为0)
int hasReadExternalStoragePermission = ContextCompat.checkSelfPermission(getApplication(), Manifest.permission.READ_EXTERNAL_STORAGE);
if (hasReadExternalStoragePermission == PackageManager.PERMISSION_GRANTED) {
setData();
} else {
//若没有授权,会弹出一个对话框(这个对话框是系统的,开发者不能自己定制),用户选择是否授权应用使用系统权限
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 1);
}
}
private void setData() {
MyAdapter adapter = new MyAdapter();
lv.setAdapter(adapter);
lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView> parent, View view, int position, long id) {
Uri uri = Uri.parse(pathMap.get(position));
//调用系统自带的播放器
Intent intent = new Intent(Intent.ACTION_VIEW);
Log.v("URI:::::::::", uri.toString());
intent.setDataAndType(uri, "video/mp4");
startActivity(intent);
}
});
}
//用户选择是否同意授权后,会回调这个方法
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == 1) {
if (permissions[0].equals(Manifest.permission.READ_EXTERNAL_STORAGE) && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//用户同意授权,执行读取文件的代码
setData();
} else {
//若用户不同意授权,直接暴力退出应用。
// 当然,这里也可以有比较温柔的操作。
finish();
}
}
}
public List getList(Context context) {
List sysVideoList = new ArrayList<>();
// MediaStore.Video.Thumbnails.DATA:视频缩略图的文件路径
String[] thumbColumns = {MediaStore.Video.Thumbnails.DATA, MediaStore.Video.Thumbnails.VIDEO_ID};
// 视频其他信息的查询条件
String[] mediaColumns = {MediaStore.Video.Media._ID, MediaStore.Video.Media.DATA, MediaStore.Video.Media.DURATION};
Cursor cursor = context.getContentResolver().query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, mediaColumns, null, null, null);
if (cursor == null) {
return sysVideoList;
}
if (cursor.moveToFirst()) {
do {
EntityVideo info = new EntityVideo();
int id = cursor.getInt(cursor.getColumnIndex(MediaStore.Video.Media._ID));
Cursor thumbCursor = context.getContentResolver().query(MediaStore.Video.Thumbnails.EXTERNAL_CONTENT_URI, thumbColumns, MediaStore.Video.Thumbnails.VIDEO_ID + "=" + id, null, null);
if (thumbCursor.moveToFirst()) {
info.setThumbPath(thumbCursor.getString(thumbCursor.getColumnIndex(MediaStore.Video.Thumbnails.DATA)));
}
info.setPath(cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA)));
info.setDuration(cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATION)));
sysVideoList.add(info);
} while (cursor.moveToNext());
}
return sysVideoList;
}
private class MyAdapter extends BaseAdapter {
List videoList = getList(Main2Activity.this);
@Override
public int getCount() {
return videoList.size();
}
@Override
public Object getItem(int position) {
return videoList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = View.inflate(Main2Activity.this, R.layout.item, null);
TextView thumPath = view.findViewById(R.id.thumPath);
String thumbPath = (videoList.get(position).getThumbPath());
if (thumbPath != null) {
thumPath.setText(thumbPath);
}
TextView path = view.findViewById(R.id.path);
String videoPath = videoList.get(position).getPath();
if (videoPath != null) {
pathMap.put(position,videoPath);
path.setText(videoPath);
ImageView iv = view.findViewById(R.id.iv);
iv.setImageBitmap(getVideoThumbnail(videoPath));
}
return view;
}
}
/**
* Android得到视频缩略图,可以通过接口类 MediaMetadataRetriever 来实现
* 其中函数getFrameAtTime()有其他重载函数,该函数会随机选择一帧抓取,
* 如果想要指定具体时间的缩略图,可以用函数getFrameAtTime(long timeUs), getFrameAtTime(long timeUs, int option)
* @param filePath
* @return
*/
public Bitmap getVideoThumbnail(String filePath) {
Bitmap bitmap = null;
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
try {
retriever.setDataSource(filePath);
bitmap = retriever.getFrameAtTime();
}
catch(IllegalArgumentException e) {
e.printStackTrace();
}
catch (RuntimeException e) {
e.printStackTrace();
}
finally {
try {
retriever.release();
}
catch (RuntimeException e) {
e.printStackTrace();
}
}
return bitmap;
}}