您当前的位置: 首页 > 学无止境 > 心得笔记 网站首页心得笔记
70_android下的视频播放器&surfaceview&holder
发布时间:2021-03-11 23:31:12编辑:雪饮阅读()
这次要实现的是视频播放器
视频播放对布局文件中的SurfaceView依赖性较强,曾经因为代码部署到设备中时候因为布局文件中好像是对这个SurfaceView影响了,然后播放时候都出现了类似“Caused by: java.lang.IllegalArgumentException: The surface has been released”这样的异常。
但是其实当时有可能也是我部署代码到设备中时候没有完全彻底的部署吧,反正个人感觉是因为SurfaceView布局view被影响了的。当然网上有说要用异步监听之类的,其实我代码本来就已经这样实现了。
SurfaceView
内部维护了一个对缓冲的机制提高画面显示的速度和质量
因为视频播放基本上都是每秒就执行了n个帧,你比如我记得好像有个速率是每秒25帧的,一帧就相当于一个画面,所以画面显示速度与质量是尤为重要的。所以要单独做一个view来处理这事情,它就是SurfaceView
视频的播放,,比较占用资源
每秒/单位时间内显示出来很多帧涉及到很多图形的生成和渲染
一般的vi ew对象都是直接被画到屏幕上的.
每秒/单位时间内显示出来很多帧涉及到很多图形的生成和渲染
一般的vi ew对象都是直接被画到屏幕上的.
SurfaceView内部有两个缓冲区,这里假定是A和B区吧,这是双缓冲机制
1. 把A缓冲区内容埴满.
把A显示到界面上
于此同时,准备B缓冲区里面的内容
把A显示到界面上
于此同时,准备B缓冲区里面的内容
2. 把B缓冲区的内容移到前台
把A缓冲区的内容移到后台,埴充A缓存区
把A缓冲区的内容移到后台,埴充A缓存区
这个机制导致该视图的图形生成与渲染性能强劲
简单来说就是这样的原理,实则原理很深,这里不做探究。
好了废话不多说,看看如何实现吧。
首先布局文件中至少要有几个用于控制视频播放的按钮、以及输入视频地址(这里用sdcard中的文件吧),然后就是上面这个很重要的SurfaceView。所以布局文件activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:id="@+id/et_path"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="/sdcard/blmsyb.mp4"
android:hint="请输入视频的路径"
/>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="horizontal">
<Button
android:id="@+id/bt_start"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:text="开始"
android:onClick="onClick"
/>
<Button
android:id="@+id/bt_restart"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:text="重置"
android:onClick="onClick"
/>
<Button
android:id="@+id/bt_stop"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:text="停止"
android:onClick="onClick"
/>
<Button
android:id="@+id/bt_pause"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:text="暂停"
android:onClick="onClick"
/>
</LinearLayout>
<SurfaceView
android:id="@+id/sv"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:id="@+id/et_path"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="/sdcard/blmsyb.mp4"
android:hint="请输入视频的路径"
/>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="horizontal">
<Button
android:id="@+id/bt_start"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:text="开始"
android:onClick="onClick"
/>
<Button
android:id="@+id/bt_restart"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:text="重置"
android:onClick="onClick"
/>
<Button
android:id="@+id/bt_stop"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:text="停止"
android:onClick="onClick"
/>
<Button
android:id="@+id/bt_pause"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:text="暂停"
android:onClick="onClick"
/>
</LinearLayout>
<SurfaceView
android:id="@+id/sv"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
然后实现逻辑的主程序MainActivity.java:
package com.example.videoplayer;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import java.io.File;
public class MainActivity extends AppCompatActivity {
private EditText et_path;
private Button bt_start, bt_pause, bt_restart, bt_stop;
private SurfaceView sv;
private SurfaceHolder holder;
MediaPlayer mediaPlayer;
boolean ispause = false;
int position;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
et_path=this.findViewById(R.id.et_path);
bt_start=this.findViewById(R.id.bt_start);
bt_pause=this.findViewById(R.id.bt_pause);
bt_restart=this.findViewById(R.id.bt_restart);
bt_stop=this.findViewById(R.id.bt_stop);
sv = (SurfaceView) this.findViewById(R.id.sv);
// 视频播放的填充器
holder = sv.getHolder();
/* 下面设置Surface不维护自己的缓冲区,而是等待屏幕的渲染引擎将内容推送到用户面前 */
holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
holder.addCallback(new MyHolderCallback());
}
private class MyHolderCallback implements SurfaceHolder.Callback {
public void surfaceCreated(SurfaceHolder holder) {
System.out.println("holder 被创建 ");
// 当视频播放最小化之后 下一次 必须在holder被创建后 再去播放视频
if (position > 0) {
String path = et_path.getText().toString().trim();
try {
// 重新设置显示的holder
MainActivity.this.holder = holder;
play(path, position);
} catch (Exception e) {
Toast.makeText(getApplicationContext(), "播放出现一次", Toast.LENGTH_SHORT).show();
e.printStackTrace();
}
}
}
@Override
public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
//实现SurfaceHolder.Callback接口,则surfaceChanged不实现不行,实际上这个方法可以根据情况编写一些逻辑
//这里暂时用不上
System.out.println("holder 改变:format"+format+"width:"+width+"height:"+height);
}
public void surfaceDestroyed(SurfaceHolder holder) {
//一般如按home键最小化后会触发这个方法
System.out.println("holder 被销毁 ");
// 记录最小化之后播放器播放的位置
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
position = mediaPlayer.getCurrentPosition();
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer = null;
}
}
}
public void onClick(View v) {
String path = et_path.getText().toString().trim();
try {
switch (v.getId()) {
case R.id.bt_start:
play(path, 0);
break;
case R.id.bt_restart:
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.seekTo(0);
} else {
play(path, 0);
}
break;
case R.id.bt_stop:
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer = null;
bt_start.setEnabled(true);
}
break;
case R.id.bt_pause:
pause();
break;
}
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, "视频播放失败", Toast.LENGTH_SHORT).show();
}
}
private void pause() {
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.pause();
bt_pause.setText("继续");
ispause = true;
return;
}
if (mediaPlayer != null && ispause) {
mediaPlayer.start();
bt_pause.setText("暂停");
ispause = false;
}
}
/**
* 根据路径 进行播放
*
* @param path
*/
private void play(String path, int position) throws Exception {
if ("".equals(path)) {
Toast.makeText(this, "路径不能为空", Toast.LENGTH_SHORT).show();
return;
}
File file = new File(path);
if (file.exists()) {
mediaPlayer = new MediaPlayer();
mediaPlayer.setDataSource(path);
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDisplay(holder);
// 播放视频之前必须要初始化 视频的播放器
mediaPlayer.prepare();
mediaPlayer.start();
mediaPlayer.seekTo(position);
bt_start.setEnabled(false);
mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
public void onCompletion(MediaPlayer mp) {
// TODO Auto-generated method stub
mediaPlayer.release();
mediaPlayer = null;
bt_start.setEnabled(true);
}
});
// 当播放出现错误的时候的回调方法
mediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
public boolean onError(MediaPlayer mp, int what, int extra) {
mp.release();
mediaPlayer = null;
bt_start.setEnabled(true);
return false;
}
});
} else {
Toast.makeText(this, "文件不存在", Toast.LENGTH_SHORT).show();
return;
}
}
}
由于我们读取的是sdcard卡上的文件,所以外存读取权限还是要的,所以清单文件则如AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.videoplayer">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Videoplayer">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
那么最后我们部署到设备中,并将对应视频文件,例如一个标准的mp4文件上传到sdcard,然后输入文件路径就可以正常播放了,当然前提是你要先把上面清单文件所配置的外存读取权限给勾选上才行。那么大致效果如:
关键字词:surfaceview,android,视频播放器