1. 系统播放控件VideoView使用
1. videoview.setVideoURI(uri); // 设置播放地址,可以是网络地址http、rtsp、mms
2. videoview.setMediaController(new MediaController(this)); // 设置播放控制面板,系统自带的
3. 播放监听,异步加载,可能是网络地址
videoview.setOnPreparedListener(new OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
videoview.start();
//handler.sendEmptyMessage(PROGRESS);
//1.视频的总时长,关联总长度
int duration = videoview.getDuration();
seekbar_video.setMax(duration);
//2.发消息
handler.sendEmptyMessage(PROGRESS);
}
});
4. 播放出错监听:
videoview.setOnErrorListener(new OnErrorListener() {
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
// TODO Auto-generated method stub
// 播放出错,释放资源,跳入往万能播放器
if(videoview != null){
videoview.stopPlayback();
}
finish();
// 返回false,会弹出对话框出错对话框系统的
return false;
}
});
5. 播放完成监听:
videoview.setOnCompletionListener(new OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
//切换到下一个
Toast.makeText(VideoViewActivity.this, "播放完成", Toast.LENGTH_LONG).show();
}
});6. 视频SeekBar的更新:
在 hander中每秒获取videoview.getCurrentPosition()当前播放进度,当前播放的毫秒数,除上视频总的秒数, 更新进度
// 设置播放进度
seekbar_video.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
})7. 播放缓冲:
系统videoView默认支持缓存,获取缓冲值,获取当前缓存的毫秒数,更新到进度条上即可
int buffer = videoview.getBufferPercentage();//0~100
8. // 监听视频播放卡
// 系统api,如何是m3u8直播,无法获取当前播放进度,用这个
// 系统apibug,有时候卡了,不卡了,不卡了的方法没有回调
videoview.setOnInfoListener(new MediaPlayer.OnInfoListener() {
@Override
public boolean onInfo(MediaPlayer mp, int what, int extra) {
}
}
如果可以获取当前播放进度,用下面的, 直播m3u8获取当前进度为0,把视频切成一段一段,每一段都是完整的有头
比如一个mp4,有一个总的头,记录视频信息
int buffer = currentPosition - precurrentPosition;
//currentPosition 当前播放的进度,已经播放的时间毫秒数
// 如何判断是否卡顿,在1S内,当前进度-上次进度<1000ms,那么在1S内,没有达到播放要求,卡顿
if (buffer < 500) {
//视频卡了
ll_buffer.setVisibility(View.VISIBLE);
} else {
//视频不卡了
ll_buffer.setVisibility(View.GONE);
}
9. 获取网速:
如何实现:每隔两秒调用一次网速api,更新UI
基于 FFmpeg 封装开源Android播放器 Vitamio | VLC
=====================================================================
代码实现:
package com.itheima.videoplay;
import android.app.Activity;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
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.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.os.Vibrator;
import android.support.annotation.RequiresApi;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.MediaController;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.VideoView;
import java.text.SimpleDateFormat;
import java.util.Date;
public class VideoViewActivity extends Activity {
VideoView videoview;
Uri uri;
TextView info,playtime,netspeed,tvSystemTime;
private LinearLayout ll_buffer;
SeekBar seekbar_video,seekbar_voice;
Utils utils;
private int precurrentPosition; // 上一次播放进度
private static final int SHOW_SPEED = 3; // 网速
// 视频更新进度
private static final int PROGRESS = 1;
private int currentVoice; // 当前的音量
private boolean isNetUri;
Handler handler=new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case PROGRESS:
// //6. 视频SeekBar的更新:
//1.得到当前的视频播放进程,就是毫秒
int currentPosition = videoview.getCurrentPosition();//0
// playtime.setText("当前播放时间是:"+currentPosition);
playtime.setText("当前播放时间是:"+utils.stringForTime(currentPosition));
//3.SeekBar.setProgress(当前进度);
seekbar_video.setProgress(currentPosition);
// 7. 播放缓冲:
if (isNetUri) {
//只有网络资源才有缓存效果
int buffer = videoview.getBufferPercentage();//0~100
// 系统videoView默认支持缓存,获取缓冲,更新到进度条上即可
int totalBuffer = buffer * seekbar_video.getMax();
int secondaryProgress = totalBuffer / 100;
seekbar_video.setSecondaryProgress(secondaryProgress);
} else {
//本地视频没有缓冲效果
seekbar_video.setSecondaryProgress(0);
}
// 8. // 监听视频播放卡
if(videoview.isPlaying()){
int buffer = currentPosition - precurrentPosition;
// 原理:
// currentPosition 当前播放的进度,已经播放的时间毫秒数
// 如何判断是否卡顿,在1S内,当前进度-上次进度<1000ms,那么在1S内,没有达到播放要求
if (buffer < 500) {
//视频卡了
ll_buffer.setVisibility(View.VISIBLE);
} else {
//视频不卡了
ll_buffer.setVisibility(View.GONE);
}
}else{
ll_buffer.setVisibility(View.GONE);
}
precurrentPosition = currentPosition;
tvSystemTime.setText("系统时间:"+getSysteTime()+"");
//3.每秒更新一次
handler.removeMessages(PROGRESS);
handler.sendEmptyMessageDelayed(PROGRESS, 1000);
break;
case SHOW_SPEED://显示网速
//9. 获取网速:
//1.得到网络速度
String netSpeed = utils.getNetSpeed(VideoViewActivity.this);
//显示网络速
// tv_laoding_netspeed.setText("玩命加载中..."+netSpeed);
// tv_buffer_netspeed.setText("缓存中..."+netSpeed);
netspeed.setText("网速:"+netSpeed);
//2.每两秒更新一次
handler.removeMessages(SHOW_SPEED);
handler.sendEmptyMessageDelayed(SHOW_SPEED, 2000);
break;
}
}
};
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_video_view);
info = (TextView) findViewById(R.id.info);
ll_buffer = (LinearLayout) findViewById(R.id.ll_buffer);
playtime= findViewById(R.id.playtime); // 当前播放时间
netspeed= findViewById(R.id.netspeed); // 网络速度
seekbar_video = findViewById(R.id.seekbar_video); // 进度条,缓存进度条
tvSystemTime = findViewById(R.id.tvSystemTime); // 系统时间
seekbar_voice = findViewById(R.id.seekbar_voice); // 设置音量
utils=new Utils();
// 获取点击视频,选择自己播放器的地址url
uri = getIntent().getData();//文件夹,图片浏览器
if(uri==null){
uri=Uri.parse("http://vfx.mtime.cn/Video/2018/10/22/mp4/181022083653874222.mp4");
}
if(utils.isNetUri(uri.toString())){
isNetUri=true;
}
videoview= findViewById(R.id.videoview);
String savePath = Environment.getExternalStorageDirectory().getAbsolutePath()+"/1moviestest/hello.mp4";
// videoview.setVideoPath(savePath);
///1. videoview.setVideoURI(uri); // 设置播放地址,可以是网络地址http、rtsp、mms
videoview.setVideoURI(uri);
info.setText(uri.toString());
// 2. videoview.setMediaController(new MediaController(this)); // 设置播放控制面板,系统自带的
videoview.setMediaController(new MediaController(this));
handler.sendEmptyMessage(SHOW_SPEED);
// 3. 播放监听,异步加载,可能是网络地址
videoview.setOnPreparedListener(new OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
videoview.start();
//handler.sendEmptyMessage(PROGRESS);
//1.视频的总时长,关联总长度
int duration = videoview.getDuration();
seekbar_video.setMax(duration);
//2.发消息
handler.sendEmptyMessage(PROGRESS);
}
});
// 4. 播放出错监听:
videoview.setOnErrorListener(new OnErrorListener() {
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
// TODO Auto-generated method stub
// 播放出错,释放资源,跳入往万能播放器
/***
* 播放出错原因:
* 1. 播放视频格式不支持, 利用万能播放器
* 2. 播放网络视频,网络中断,
* 2.1. 网路确实断了,提示用户网络断了
* 2.2. 网络断断续续, 尝试重新播放,重新设置地址,设置进度
* 3. 播放本地视频,中间有空白: 重新下载资源
*/
if(videoview != null){
videoview.stopPlayback();
}
finish();
// 返回false,会弹出对话框出错对话框系统的
return false;
}
});
// 5. 播放完成监听:
videoview.setOnCompletionListener(new OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
//切换到下一个
Toast.makeText(VideoViewActivity.this, "播放完成", Toast.LENGTH_LONG).show();
}
});
// 监听视频播放卡, Android4.2 以后才出现
// 卡顿以后显示加载圈圈
// 系统apibug多次测试,有时候卡了,不卡了,不卡了的方法没有回调
//
/*videoview.setOnInfoListener(new MediaPlayer.OnInfoListener() {
@Override
public boolean onInfo(MediaPlayer mp, int what, int extra) {
switch (what) {
case MediaPlayer.MEDIA_INFO_BUFFERING_START://视频卡了,拖动卡
Toast.makeText(VideoViewActivity.this, "卡了", Toast.LENGTH_SHORT).show();
ll_buffer.setVisibility(View.VISIBLE);
break;
case MediaPlayer.MEDIA_INFO_BUFFERING_END://视频卡结束了,拖动卡结束了
Toast.makeText(VideoViewActivity.this, "卡结束了", Toast.LENGTH_SHORT).show();
ll_buffer.setVisibility(View.GONE);
break;
}
return true;
}
});*/
//6. 视频SeekBar的更新:
seekbar_video.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
// 当手指滑动的时候会引起进度变化,会回调这个方法
// 自动更新,上面hander设置progress, fromUser是flase
// 当fromUser=true,用户手动拖动更新
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if(fromUser){
videoview.seekTo(progress);
}
}
// 当手指触碰回调这个方法
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
// 当手指离开的时候回调这个方法
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
}
@Override
protected void onStop() {
super.onStop(); //细节,把super.stop放在下面,首先关闭自己,然后关闭系统的
}
@Override
protected void onDestroy() {
handler.removeCallbacksAndMessages(null);
super.onDestroy();
}
/**
* 得到系统时间
*
* @return
*/
public String getSysteTime() {
SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");
return format.format(new Date());
}
}
xml布局:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#000"
>
<VideoView
android:id="@+id/videoview"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_centerInParent="false"
/>
<!-- 显示文件信息 -->
<TextView
android:id="@+id/info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/videoview"
android:maxLines="1"
android:textColor="#fff"
android:text="地址:"
/>
<!-- 显示当前播放时间 -->
<TextView
android:id="@+id/playtime"
android:ellipsize="end"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#fff"
android:text="时长:"
android:layout_below="@+id/info"/>
<!-- 显示播放网速 -->
<TextView
android:id="@+id/netspeed"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/playtime"
android:text="网速:"
android:textColor="#fff"/>
<!-- SeekBar: thumb指示器头部-->
<!-- 如何修改,在android-sdk-windows6\platforms\android-24\data\res\values
下styles.xml中找Widget.SeekBar 找到progressDrawable,
同理: 系统Progress的样式也可以这样修改,在sdk的源码下找到对应的Style,然后找到样式,修改即可
问题:seekbar设置thumb后在真机出现黑边(不透明区域)
https://blog.csdn.net/qq_19269585/article/details/77461311
解决:设置 android:splitTrack="false"
-->
<LinearLayout
android:id="@+id/seekbar_video_ll"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_player_bottom_seekbar"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_below="@+id/netspeed">
<SeekBar
android:id="@+id/seekbar_video"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:maxHeight="6dp"
android:minHeight="6dp"
android:progressDrawable="@drawable/progress_horizontal"
android:thumb="@drawable/progress_thumb"
android:splitTrack="false"
/>
</LinearLayout>
<LinearLayout
android:id="@+id/powersource"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_below="@+id/seekbar_video_ll"
>
<TextView
android:id="@+id/tvSystemTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="当前时间:"
android:textColor="#fff"
android:layout_toRightOf="@+id/iv_battery"
android:layout_marginLeft="20dp"
/>
</LinearLayout>
<!-- 原型加载进度 -->
<!--<include layout="@layout/ll_buffer" android:id="@+id/ll_buffer"/>-->
<LinearLayout
android:id="@+id/ll_buffer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:gravity="center"
android:orientation="horizontal"
android:padding="3dp"
android:visibility="gone">
<ProgressBar
style="?android:attr/progressBarStyle"
android:layout_width="60dp"
android:layout_height="60dp" />
<TextView
android:id="@+id/tv_buffer_netspeed"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="3dp"
android:gravity="center"
android:text="缓存中...30kb/s"
android:textColor="#f00"
android:textSize="10sp" />
</LinearLayout>
</RelativeLayout>
效果图:
2. 把自己播放器注册为系统播放器:
功能清单配置文件:
<activity
android:name=".VideoViewActivity"
android:label="置哥播放器">
<!-- 视频播放意图过滤器 -->
<!-- 源码:android_source2.3\packages\apps\Gallery\AndroidManifest.xml下 com.android.camera.MovieView中 -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="rtsp" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="video/*" />
<data android:mimeType="application/sdp" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
<data android:mimeType="video/mp4" />
<data android:mimeType="video/3gp" />
<data android:mimeType="video/3gpp" />
<data android:mimeType="video/3gpp2" />
</intent-filter>
</activity>
Activity代码实现:
public class VideoViewActivity extends Activity {
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_video_view);
// 获取点击视频,选择自己播放器的地址url
uri = getIntent().getData();//文件夹,图片浏览器
if(uri==null){
uri=Uri.parse("http://vfx.mtime.cn/Video/2018/10/22/mp4/181022083653874222.mp4");
}
videoview.setVideoURI(uri);
}
}
3. MediaPlayer(播放器)+ SurfaceView(幕布) 实现视频播放:
// mediaPlayer 解码以后把画面播放到幕布上
mediaPlayer.setDisplay(sv.getHolder());
int max=mediaPlayer.getDuration(); // 获取播放进度,播放监听
SurfaceView: 电影屏幕
双缓冲机制:
A线程 加载数据
B线程 显示界面
SurfceHolder: 投影仪
Thread: 工作人员,不断的摇
package com.itheima.videoplay;
import android.Manifest;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.SystemClock;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.SeekBar;
import android.widget.Toast;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
public class MainActivity extends Activity implements OnClickListener{
private EditText edt1;
private Button bt_play;
private Button bt_pause;
private Button bt_replay;
private Button bt_stop;
private MediaPlayer mediaPlayer;
private SurfaceView sv;
private int currentPositon;
private SeekBar sb;
private boolean isplaying;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
copyVideo("hello.mp4");
edt1=(EditText) findViewById(R.id.edt1);
bt_play=(Button) findViewById(R.id.bt_play);
bt_pause=(Button) findViewById(R.id.bt_pause);
bt_replay=(Button) findViewById(R.id.bt_replay);
bt_stop=(Button) findViewById(R.id.bt_stop);
sv=(SurfaceView) findViewById(R.id.sv);
sb=(SeekBar) findViewById(R.id.sb);
sb.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
int process=seekBar.getProgress();
if(mediaPlayer!=null && mediaPlayer.isPlaying()){
mediaPlayer.seekTo(process);
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
}
});
/* 为了解决低版本模拟式添加的 */
/* 下面设置Surface 不维护自己的 缓冲区,而是等待屏幕渲染引擎内容推动到客户端 */
/* 2.2、2.3有问题模拟器,用1.6 */
sv.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
// getHolder监听器
// 当播放进入后台的时候,在重新进来,画面出现黑色,推出的时候SurfaceView.holder销毁了,重新设置一下sv的holder
sv.getHolder().addCallback(new Callback() {
// 退出时候,点击back或者home键
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
if(mediaPlayer!=null && mediaPlayer.isPlaying()){
currentPositon=mediaPlayer.getCurrentPosition();
stop();
}
}
// 如果在次进来的时候,或者第一次创建surfaceView的时候
@Override
public void surfaceCreated(SurfaceHolder holder) {
if(currentPositon>0){
play(currentPositon);
}
}
// 画布大小改变的时候
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
});
bt_play.setOnClickListener(this);
bt_pause.setOnClickListener(this);
bt_replay.setOnClickListener(this);
bt_stop.setOnClickListener(this);
}
// 数据准备
private void copyVideo(final String dbname) {
new Thread(){
public void run() {
try {
File file = new File(getFilesDir(),dbname);
if(file.exists()&&file.length()>0){
Log.e("denganzhi1","数据库是存在的。无需拷贝");
return ;
}
InputStream is = getAssets().open(dbname);
FileOutputStream fos = openFileOutput(dbname, MODE_PRIVATE);
byte[] buffer = new byte[1024];
int len = 0;
while((len = is.read(buffer))!=-1){
fos.write(buffer, 0, len);
}
is.close();
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
};
}.start();
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.bt_play:
play(0);
break;
case R.id.bt_pause:
pause();
break;
case R.id.bt_replay:
replay();
break;
case R.id.bt_stop:
stop();
break;
}
}
// 在服务中写
private void play(final int currentPositon) {
// String path=edt1.getText().toString().trim();
final String savePath = Environment.getExternalStorageDirectory().getAbsolutePath()+"/1moviestest/hello.mp4";
File file = new File(getFilesDir(),"hello.mp4");
if(file.exists()==false){
Toast.makeText(MainActivity.this,"视频文件不存在",Toast.LENGTH_SHORT).show();
return;
}else{
try {
mediaPlayer = new MediaPlayer();
// 音频流的类型 STREAM_Ring:铃声 STREAM_alarm:更短
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
// 把视频显示在 SurfaceView 的容器中
// 把画面播放到 SurfaceView的幕布上
mediaPlayer.setDisplay(sv.getHolder());
// 这里路径:可以使网络路径,http://xxx:8080/aa.3gp
mediaPlayer.setDataSource(file.getAbsolutePath());
// 这里是同步的 prepare()没有准备完毕,start()不能执行,一般不用
// mediaPlayer.prepare();
// mediaPlayer.start();
// 这样做就不会造成主线程的柱塞,如果数据比较大,在子线程中准备
mediaPlayer.prepareAsync();//异步的,在另一个线程中跑, 什么时候准备好,在回调中
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
mediaPlayer.start();
// 设置最大的总长度
int max=mediaPlayer.getDuration();
sb.setMax(max);
mediaPlayer.seekTo(currentPositon);
// 在子线程中跟新进度
new Thread(){
public void run() {
isplaying=true;
while(isplaying){
int positon=mediaPlayer.getCurrentPosition();
sb.setProgress(positon);
SystemClock.sleep(500);
}
}
}.start();
}
});
// 播放完以后的监听器
mediaPlayer.setOnCompletionListener(new OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
bt_play.setEnabled(true);
}
});
// 如果音频有问题,播着挂了
mediaPlayer.setOnErrorListener(new OnErrorListener() {
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
bt_play.setEnabled(true);
isplaying=false;
return false;
}
});
// 播放的时候不可以点击
bt_play.setEnabled(false);
} catch (Exception e) {
Toast.makeText(this, "视频播放失败", 1).show();
e.printStackTrace();
}
}
}
// 暂停
private void pause() {
// 如果是继续,那么播放即可
if(bt_pause.getText().toString().trim().equals("继续")){
mediaPlayer.start();
bt_pause.setText("暂停");
Toast.makeText(this, "继续", 1).show();
return;
}
if(mediaPlayer!=null && mediaPlayer.isPlaying()){
mediaPlayer.pause();
bt_pause.setText("继续");
isplaying=false;
return;
}
}
// 如果是一个网络资源播放
/* 重播: stop() -> release() -play()
*/
private void replay() {
if(mediaPlayer!=null && mediaPlayer.isPlaying()){
mediaPlayer.seekTo(0);
return ;
}
play(0);
}
private void stop() {
if(mediaPlayer!=null && mediaPlayer.isPlaying()){
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer=null;
}
}
}
xml布局:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<EditText
android:id="@+id/edt1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:text="/sdcard/mm.mp4">
<requestFocus />
</EditText>
<SeekBar
android:id="@+id/sb"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<Button
android:id="@+id/bt_play"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="播放" />
<Button
android:id="@+id/bt_pause"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="暂停" />
<Button
android:id="@+id/bt_replay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="重播" />
<Button
android:id="@+id/bt_stop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="停止" />
</LinearLayout>
<SurfaceView android:id="@+id/sv"
android:layout_width="match_parent"
android:layout_height="fill_parent"/>
</LinearLayout>
效果图:
案例2:MediaPlayer+ SurfaceView使用
* 1. 设置播放属性
* 2. 2. 设置 高度 和宽度 等比例 横屏
package com.denganzhi.camera2;
import android.Manifest;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.media.AudioAttributes;
import android.media.MediaPlayer;
import android.os.Environment;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageButton;
import android.widget.RelativeLayout;
import java.io.IOException;
/***
* MediaPlayer+ SurfaceView使用
* 1. 设置播放属性
* 2. 2. 设置 高度 和宽度 等比例 横屏
*/
public class MediaPlayActivity extends AppCompatActivity {
private SurfaceView surfaceView;
private MediaPlayer mPlayer;
private ImageButton playBn, pauseBn, stopBn;
// 记录当前视频的播放位置
int position = 0;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_media_play);
// 创建MediaPlayer
mPlayer = new MediaPlayer();
surfaceView = this.findViewById(R.id.surfaceView);
// 设置播放时打开屏幕
surfaceView.getHolder().setKeepScreenOn(true);
surfaceView.getHolder().addCallback(new SurfaceListener());
// 获取界面上的三个按钮
playBn = findViewById(R.id.play);
pauseBn = findViewById(R.id.pause);
stopBn = findViewById(R.id.stop);
requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 0x123);
}
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions, @NonNull int[] grantResults)
{
if (requestCode == 0x123 && grantResults[0] ==
PackageManager.PERMISSION_GRANTED) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
View.OnClickListener listener = source -> {
switch (source.getId())
{
// “播放”按钮被单击
case R.id.play:
play();
break;
// “暂停”按钮被单击
case R.id.pause:
if (mPlayer.isPlaying()) {
mPlayer.pause();
} else {
mPlayer.start();
}
break;
// “停止”按钮被单击
case R.id.stop:
if (mPlayer.isPlaying())
mPlayer.stop();
}
};
// 为三个按钮的单击事件绑定事件监听器
playBn.setOnClickListener(listener);
pauseBn.setOnClickListener(listener);
stopBn.setOnClickListener(listener);
}
}
private void play()
{
mPlayer.reset();
// 1. 设置播放属性
AudioAttributes audioAttributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build();
mPlayer.setAudioAttributes(audioAttributes);
try {
// 设置需要播放的视频
mPlayer.setDataSource(Environment.getExternalStorageDirectory().toString() + "/1moviestest/hello.mp4");
// 把视频画面输出到SurfaceView
mPlayer.setDisplay(surfaceView.getHolder()); // ①
mPlayer.prepare();
}
catch(IOException e)
{ e.printStackTrace(); }
// 获取窗口管理器
WindowManager wManager = getWindowManager();
DisplayMetrics metrics = new DisplayMetrics();
// 获取屏幕大小
wManager.getDefaultDisplay().getMetrics(metrics);
// 设置视频保持纵横比缩放到占满整个屏幕
//2. 设置 高度 和宽度 等比例 横屏
surfaceView.setLayoutParams(new RelativeLayout.LayoutParams(metrics.widthPixels,
mPlayer.getVideoHeight() * metrics.widthPixels / mPlayer.getVideoWidth()));
mPlayer.start();
}
private class SurfaceListener implements SurfaceHolder.Callback
{
@Override
public void surfaceCreated(SurfaceHolder holder)
{
if (position > 0) {
// 开始播放
play();
// 并直接从指定位置开始播放
mPlayer.seekTo(position);
position = 0;
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
{
}
@Override
public void surfaceDestroyed(SurfaceHolder holder)
{
}
}
// 当其他Activity被打开时,暂停播放
@Override
public void onPause()
{
super.onPause();
if (mPlayer.isPlaying()) {
// 保存当前的播放位置
position = mPlayer.getCurrentPosition();
mPlayer.stop();
}
}
@Override
public void onDestroy()
{
super.onDestroy();
// 停止播放
if (mPlayer.isPlaying()) mPlayer.stop();
// 释放资源
mPlayer.release();
}
}
布局文件xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<SurfaceView
android:id="@+id/surfaceView"
android:layout_width="match_parent"
android:layout_height="360dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:gravity="center_horizontal"
android:orientation="horizontal">
<ImageButton
android:id="@+id/play"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/play" />
<ImageButton
android:id="@+id/pause"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/pause" />
<ImageButton
android:id="@+id/stop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/stop" />
</LinearLayout>
</RelativeLayout>
4. android 系统提供 MediaPlayer 来实现音乐播放:
Mediapalyer生命周期图:音视频框架都要根据生命周期来封装
音乐播放实现核心代码:
1. 服务中音乐播放
2. 歌词解析显示 canvas.setTranslateY 移动
package com.itheima.videoplay;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.graphics.drawable.AnimationDrawable;
import android.media.audiofx.Visualizer;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.TextView;
import com.itheima.videoplay.words.LyricUtils;
import com.itheima.videoplay.words.MyLyricView;
import java.io.File;
import static com.itheima.videoplay.MusicPlayerService.OPENAUDIO;
public class AudioActivity extends Activity {
private ImageView ivIcon;
private Button playandpause,pausemusic;
private ServiceConnection conn=null;
private IMusicPlayerService musicPlayerService;
MyReceiver myReceiver;
TextView tv_artist,tv_name,currentplaytext;
SeekBar seekbar_audio;
private Utils utils;
private static final int PROGRESS = 1; //进度更新
private static final int SHOW_LYRIC = 2; // 显示歌词
private MyLyricView myLyricView;
private BaseVisualizerView baseVisualizerView;
Handler handler=new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case PROGRESS :
//Log.e("dengnazhi1","---------------过来了--》");
try {
int currentPosition = musicPlayerService.getCurrentPosition();
seekbar_audio.setProgress(currentPosition);
//3.时间进度跟新
String time = utils.stringForTime(currentPosition)+"/"+utils.stringForTime(musicPlayerService.getDuration());
currentplaytext.setText(time);
// Log.e("denganzhi1","curretnPostion:"+currentPosition+"time:"+time);
// 每一秒钟更新一次
handler.removeMessages(PROGRESS);
handler.sendEmptyMessageDelayed(PROGRESS,1000);
} catch (RemoteException e) {
e.printStackTrace();
Log.e("dengnazhi1",e.getMessage()+"");
}
break;
case SHOW_LYRIC:
// 3. 当前播放歌曲进度实时更新歌词绘制位置
// 1. 得到当前播放进度
// 2. 根据当前播放进度,在Lyric歌词类中计算出哪一句高亮显示
// 当前播放的那一句
// 3. 实时发送消息,更新LyricView
try {
//1.得到当前的进度
int currentPosition = musicPlayerService.getCurrentPosition();
//2.把进度传入ShowLyricView控件,并且计算该高亮哪一句
//然后根据当前播放位置,找出播放的那一句,高亮显示
myLyricView.setshowNextLyric(currentPosition);
//3.实时的发消息
handler.removeMessages(SHOW_LYRIC);
handler.sendEmptyMessage(SHOW_LYRIC);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_audio);
myReceiver=new MyReceiver();
IntentFilter intentFilter=new IntentFilter();
intentFilter.addAction(OPENAUDIO);
registerReceiver(myReceiver,intentFilter);
utils = new Utils();
//ivIcon = (ImageView)findViewById( R.id.iv_icon );
tv_name = (TextView)findViewById(R.id.tv_name);
tv_artist = (TextView)findViewById(R.id.tv_artist);
playandpause = (Button)findViewById(R.id.playandpause);
pausemusic = (Button)findViewById(R.id.pausemusic);
seekbar_audio = (SeekBar)findViewById(R.id.seekbar_audio);
myLyricView = findViewById(R.id.myLyricView);
currentplaytext = (TextView)findViewById(R.id.currentplaytext);
baseVisualizerView = findViewById(R.id.baseVisualizerView);
// ivIcon.setBackgroundResource(R.drawable.animation_list);
// AnimationDrawable rocketAnimation = (AnimationDrawable) ivIcon.getBackground();
// rocketAnimation.start();
// 拖动播放音乐
seekbar_audio.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if(fromUser) {
try {
musicPlayerService.seekTo(progress);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
// 绑定服务
if(conn==null){
conn=new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
musicPlayerService=(IMusicPlayerService) service;
if(musicPlayerService!=null ){
try {
// 从通知栏进来或者关闭这个这个页面以后从列表页面进来
if(musicPlayerService.isPlaying()==false){
musicPlayerService.openAudio(10000);
}else{
// 退出Acticity重新进来,重新绑定,此时正在播放音乐,直接更新UI即可
// 也可以不发送广告,这里是在主线程,可以直接更新UI
// Intent intent = new Intent(OPENAUDIO);
// sendBroadcast(intent);
receiverUpateUI();
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
};
bindService(new Intent(AudioActivity.this, MusicPlayerService.class), conn, Context.BIND_AUTO_CREATE );
startService(new Intent(AudioActivity.this,MusicPlayerService.class));
}
// 播放下一首,开始播放
// playandpause.setOnClickListener(new View.OnClickListener() {
// @Override
// public void onClick(View v) {
//
// try {
// musicPlayerService.openAudio(10000);
// } catch (RemoteException e) {
// e.printStackTrace();
// }
//
// }
// });
// 在当前音乐播放,暂停
pausemusic.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
if(musicPlayerService != null && musicPlayerService.isPlaying()){
musicPlayerService.pause();
pausemusic.setText("播放");
}else{
musicPlayerService.start();
pausemusic.setText("暂停");
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
class MyReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
// 歌曲准备好
// Toast.makeText(AudioActivity.this,"歌曲准备好了",Toast.LENGTH_SHORT).show();
receiverUpateUI();
//Android应用源码音乐实时跳动频谱显示,网上找的 , 必须New 的时候有问题
// https://www.cnblogs.com/Free-Thinker/p/4746938.html
// setupVisualizerFxAndUi();
}
}
public void receiverUpateUI(){
try {
tv_name.setText(musicPlayerService.getName());
tv_artist.setText(musicPlayerService.getArtist());
// 设置音乐播放器进度条最大值
seekbar_audio.setMax(musicPlayerService.getDuration());
// 开始更新进度
handler.sendEmptyMessage(PROGRESS);
String lyricPath = Environment.getExternalStorageDirectory().getAbsolutePath()+"/1moviestest/235319.lrc";
LyricUtils lyricUtils=new LyricUtils();
lyricUtils.readLyricFile(new File(lyricPath));
// lyrics=lyricUtils.getLyrics();
myLyricView.setLyrics(lyricUtils.getLyrics());
if(lyricUtils.isExistsLyric()){
// 显示歌词
handler.sendEmptyMessage(SHOW_LYRIC);
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
private Visualizer mVisualizer;
/**
* 生成一个VisualizerView对象,使音频频谱的波段能够反映到 VisualizerView上
*/
private void setupVisualizerFxAndUi()
{
try {
int audioSessionid = musicPlayerService.getAudioSessionId();
Log.e("denganzhi","audioSessionid=="+audioSessionid);
mVisualizer = new Visualizer(audioSessionid);
// 参数内必须是2的位数
mVisualizer.setCaptureSize(Visualizer.getCaptureSizeRange()[1]);
// 设置允许波形表示,并且捕获它
baseVisualizerView.setVisualizer(mVisualizer);
mVisualizer.setEnabled(true);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
protected void onPause() {
super.onPause();
if(mVisualizer != null){
mVisualizer.release();
}
}
// public void pausemusic(View view) throws RemoteException {
// musicPlayerService.pause();
// }
//
// public void pausemusicplay(View view) throws RemoteException {
// musicPlayerService.start();
// }
public void stopMusice(View view) throws RemoteException {
musicPlayerService.stop();
}
@Override
protected void onDestroy() {
super.onDestroy();
// 移除消息
handler.removeCallbacksAndMessages(null);
// Activity死了,但是音乐还在播放
// 绑定解除了,但是服务还是启动的,Activity和 服务器的核心关系
if(conn!=null){
unbindService(conn);
conn=null;
}
if(myReceiver!=null){
unregisterReceiver(myReceiver);
myReceiver=null;
}
}
}
MusicPlayerService 服务中播放核心类
package com.itheima.videoplay;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.Environment;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import java.io.File;
import java.io.IOException;
public class MusicPlayerService extends Service {
public static final String OPENAUDIO = "com.atguigu.mobileplayer_OPENAUDIO";
private MediaPlayer mediaPlayer;
public MusicPlayerService() {
}
@Override
public void onCreate() {
super.onCreate();
Log.e("denganzhi1","service---->oncreate-->");
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
return new MyMusicBinder();
}
private void openAudio(int position){
if (mediaPlayer != null) { //必须这么做,没播放下一个之前必须重置化,取消上一个
// mediaPlayer.release();
mediaPlayer.reset();
}
try {
mediaPlayer = new MediaPlayer();
// 准备好
mediaPlayer.setOnPreparedListener(new MyOnPreparedListener());
mediaPlayer.setOnErrorListener(new MyOnErrorListener());
mediaPlayer.setOnCompletionListener(new MyOnCompletionListener());
// String savePath = Environment.getExternalStorageDirectory().getAbsolutePath()+"/1moviestest/water_hander.mp3";
// String savePath = Environment.getExternalStorageDirectory().getAbsolutePath()+"/1moviestest/235319.mp3";
File file = new File(getFilesDir(),"235319.mp3");
mediaPlayer.setDataSource(file.getAbsolutePath());
mediaPlayer.prepareAsync(); //准备好了在start
} catch (IOException e) {
e.printStackTrace();
}
}
// 播暂停音乐
private void pause() {
if(mediaPlayer!=null && mediaPlayer.isPlaying()){
mediaPlayer.pause();
}
// manager.cancel(1);
}
// 停止
private void stop() {
if(mediaPlayer!=null){
mediaPlayer.stop();
mediaPlayer.reset(); // 重置
mediaPlayer.release(); //释放资源
mediaPlayer=null;
}
manager.cancel(1);
}
//是否在播放音频
private boolean isPlaying(){
if(mediaPlayer!=null && mediaPlayer.isPlaying()){
return true;
}else {
return false;
}
}
private String getName() {
return "歌曲名称";
}
private String getArtist() {
// return mediaItem.getArtist();
return "得到艺术家";
}
private NotificationManager manager;
private void start() {
if(mediaPlayer!=null){
mediaPlayer.start();
}
//当播放歌曲的时候,在状态显示正在播放,点击的时候,可以进入音乐播放页面
manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
//最主要
Intent intent = new Intent(this, AudioActivity.class);
intent.putExtra("notification",true);//标识来自状态拦
PendingIntent pendingIntent = PendingIntent.getActivity(this,1,intent,PendingIntent.FLAG_UPDATE_CURRENT);
Notification notification = new Notification.Builder(this)
.setSmallIcon(R.drawable.notification_music_playing)
.setContentTitle("321音乐")
.setContentText("正在播放:"+getName())
.setContentIntent(pendingIntent)
.build();
manager.notify(1, notification);
}
//得到当前的播放进度
private int getCurrentPosition() {
if(mediaPlayer!=null && mediaPlayer.isPlaying()){
return mediaPlayer.getCurrentPosition();
}else{
return 0;
}
}
//得到当前音频的总时长
private int getDuration() {
if(mediaPlayer!= null && mediaPlayer.isPlaying()){
return mediaPlayer.getDuration();
}
return 0;
}
/**==========================================================*/
class MyOnErrorListener implements MediaPlayer.OnErrorListener {
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
// next();
return true;
}
}
class MyOnCompletionListener implements MediaPlayer.OnCompletionListener {
@Override
public void onCompletion(MediaPlayer mp) {
// next();
}
}
class MyOnPreparedListener implements MediaPlayer.OnPreparedListener {
@Override
public void onPrepared(MediaPlayer mp) {
//通知Activity来获取信息--广播
// notifyChange(OPENAUDIO);
// EventBus.getDefault().post(mediaItem);
Intent intent = new Intent(OPENAUDIO);
sendBroadcast(intent);
start();
}
}
/**==========================================================*/
// AIDL接口定义
class MyMusicBinder extends IMusicPlayerService.Stub{
MusicPlayerService service = MusicPlayerService.this;
@Override
public void openAudio(int position) {
service.openAudio(position);
}
@Override
public void start() {
service.start();
}
@Override
public void pause() {
service.pause();
}
@Override
public void stop() {
service.stop();
}
@Override
public int getCurrentPosition() {
return service.getCurrentPosition();
}
@Override
public int getDuration() {
return service.getDuration();
}
@Override
public String getArtist() {
return service.getArtist();
}
@Override
public String getName() {
return service.getName();
}
@Override
public String getAudioPath() {
return null;
}
@Override
public void next() {
}
@Override
public void pre() {
}
@Override
public void setPlayMode(int playmode) {
}
@Override
public int getPlayMode() {
return 0;
}
// 用于播放暂停按钮切换判断
@Override
public boolean isPlaying() {
return service.isPlaying();
}
@Override
public void seekTo(int position) {
mediaPlayer.seekTo(position);
}
@Override
public int getAudioSessionId() throws RemoteException {
return mediaPlayer.getAudioSessionId();
}
}
@Override
public void onDestroy() {
super.onDestroy();
if(mediaPlayer!=null){
mediaPlayer.stop();//停止
mediaPlayer.reset();//重置
mediaPlayer.release();//释放资源
mediaPlayer = null;//赋空,如果不赋空那么会出现bug在播的时候
}
}
}
效果图:
第三方优秀播放器:JieCaoVideoPlayer, VideoView第三方封装、播放出错:UniversalVideoView
源码地址: https://download.csdn.net/download/dreams_deng/12254622
5. android 音效播放api
如果需要 播放密集、短促音效的时候,MediaPlayer不合适,缺点:
1. 资源占用过大 ,延迟时间比较长 2. 不支持同时播放 多个音效
public class MainActivity extends AppCompatActivity {
SoundPool.Builder soundPoolBuild=null;
int soundId;
SoundPool soundPool;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
sv= (SurfaceView) findViewById(R.id.sv);
// 声音池
soundPoolBuild=new SoundPool.Builder();
soundPool= soundPoolBuild.build();
// 加载声音到声音池
soundId = soundPool.load(this,R.raw.abc,1);
}
public void playSound(View view) {
/**
* play(int soundID, float leftVolume, float rightVolume,
int priority, int loop, float rate)
*
* 播放哪个声音
* leftVolume、rightVolume 指定左、右的音量
* priority : 指定播放声音优先级,数值越大,优先级越高
* loop : 指定是否循环 0不循环 -1循环
* rate: 播放比率 数值从0.5到 2 1为正常
*/
soundPool.play(soundId,1.0f,1.0f,0,-1,1.0f);
}
}