MediaPlayer 与SurfaceHolder的使用:
下面我先例举一个简单的小demo
java代码:
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import java.io.File;
import java.io.IOException;
/*
*
*1.VideoView本身就是对SurfaceView和MediaPlayer做了一个封装
*2.实现视频列表播放
*
* 如果读取本地文件,和网络的话 需要添加权限
*
* */
public class MainActivity extends AppCompatActivity {
private MediaPlayer mediaPlayer;
private SurfaceView surfaceView;
//读取本地文件
private File file=new File("/storage/sdcard1/音乐/", "这里就是放本地文件.mp4");
//访问网络视频
private String uri="这里就放一个视频的url地址";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
surfaceView = (SurfaceView) findViewById(R.id.surfaceView);
mediaPlayer = new MediaPlayer();
//获取SurfaceHolder 可以通过该接口来操作SurfaceView中的Surface
SurfaceHolder surfaceHolder = surfaceView.getHolder();
//设置Meiaplayer的准备监听
mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
//准备完成后播放
mediaPlayer.start();
}
});
surfaceHolder.addCallback(new SurfaceHolder.Callback() {
//当SurfaceView中Surface创建时回掉
//该方法表示Surface已经创建完成,可以在该方法中进行绘图操作
@Override
public void surfaceCreated(SurfaceHolder holder) {
mediaPlayer.reset();
try {
//设置视屏文件图像的显示参数
mediaPlayer.setDisplay(holder);
//file.getAbsolutePath()本地视频
//uri 网络视频
mediaPlayer.setDataSource(MainActivity.this, Uri.parse(uri));
//prepare();表示准备工作同步进行,(准备工作在UI线程中进行)
//当播放网络视频时,如果网络不要 会报ARN 所以不采用该方法
//mediaPlayer.prepare();
//异步准备 准备工作在子线程中进行 当播放网络视频时候一般采用此方法
mediaPlayer.prepareAsync();
} catch (IOException e) {
e.printStackTrace();
}
}
//当SurfaceView的大小发生改变时候触发该方法
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
//Surface销毁时回掉
//当Surface销毁时候,同时把MediaPlayer也销毁
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (mediaPlayer!=null) {
mediaPlayer.stop();
//释放资源
mediaPlayer.release();
}
}
});
//设置 surfaceView点击监听
surfaceView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (mediaPlayer.isPlaying()) {
mediaPlayer.pause();
} else {
mediaPlayer.start();
}
break;
}
//返回True代表事件已经处理了
return true;
}
});
}
}
xml代码:
<?xml version="1.0" encoding="utf-8"?>
<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:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin">
<!--第一步 在布局文件中添加SurfaceView控件-->
<SurfaceView
android:layout_centerInParent="true"
android:layout_width="match_parent"
android:layout_height="200dp"
android:id="@+id/surfaceView" />
</RelativeLayout>
上面就是一个简单的小例子
在这里我说两句,其实我们开发做视频呢,大多不是直接拿到url直接设置数据源来进行播放的,我们其实是通过拿到url先通过断点续传现在到本地,然后再进行播放的,这样操作会是视频不卡顿,而且会根据第一次下载的进度继续下载其中涉及的很多关键的一些功能,我就不一一列举,下面请看代码:
/**
* @Auther:Liuhai on 2017/11/27 19:38
* 判断网络类型状态工具类
*/
public class NetStateUtils {
/**
*
* @return 是否有活动的网络连接
*/
public static final boolean hasNetWorkConnection(Context context){
//获取连接活动管理器
final ConnectivityManager connectivityManager= (ConnectivityManager) context.
getSystemService(Context.CONNECTIVITY_SERVICE);
//获取链接网络信息
final NetworkInfo networkInfo=connectivityManager.getActiveNetworkInfo();
return (networkInfo!= null && networkInfo.isAvailable());
}
/**
* @return 返回boolean ,是否为wifi网络
*
*/
public static final boolean hasWifiConnection(Context context)
{
final ConnectivityManager connectivityManager= (ConnectivityManager) context.
getSystemService(Context.CONNECTIVITY_SERVICE);
final NetworkInfo networkInfo=connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
//是否有网络并且已经连接
return (networkInfo!=null&& networkInfo.isConnectedOrConnecting());
}
/**
* @return 返回boolean,判断网络是否可用,是否为移动网络
*
*/
public static final boolean hasGPRSConnection(Context context){
//获取活动连接管理器
final ConnectivityManager connectivityManager= (ConnectivityManager) context.
getSystemService(Context.CONNECTIVITY_SERVICE);
final NetworkInfo networkInfo=connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
return (networkInfo!=null && networkInfo.isAvailable());
}
/**
* @return 判断网络是否可用,并返回网络类型,ConnectivityManager.TYPE_WIFI,ConnectivityManager.TYPE_MOBILE,不可用返回-1
*/
public static final int getNetWorkConnectionType(Context context){
final ConnectivityManager connectivityManager=(ConnectivityManager) context.
getSystemService(Context.CONNECTIVITY_SERVICE);
final NetworkInfo wifiNetworkInfo=connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
final NetworkInfo mobileNetworkInfo=connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
if(wifiNetworkInfo!=null &&wifiNetworkInfo.isAvailable())
{
return ConnectivityManager.TYPE_WIFI;
}
else if(mobileNetworkInfo!=null &&mobileNetworkInfo.isAvailable())
{
return ConnectivityManager.TYPE_MOBILE;
}
else {
return -1;
}
}
简易的多线程断点续传的java代码,我项目中写的代码更加优化,效率更加的高,此处我为了方便,没有自己写找到了一篇简易的代码:
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URL;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.app.Activity;
import android.util.Log;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.TextView;
public class MainActivity extends Activity {
//下载文件时开启线程的个数
static int ThreadCount = 3;
//下载结束的线程的个数
static int finishedThread = 0;
//用于记录下载进度
int currentProgress;
//下载文件的文件名
String fileName = "python-2.7.5.amd64.msi";
//确定下载地址
String path = "http://192.168.0.101:8080/app/" + fileName;
//pb对象用于在进度条中设置下载进度
private ProgressBar pb;
//tv对象用于在TextView中显示下载进度
TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
pb = (ProgressBar) findViewById(R.id.pb);
tv = (TextView) findViewById(R.id.tv);
}
//创建一个消息处理器对象
Handler handler = new Handler() {
//在主线程中处理从子线程中发送过来的消息
public void handleMessage(android.os.Message msg) {
//刷新TextView中显示的下载进度
tv.setText((long)pb.getProgress() * 100 / pb.getMax() + "%");
}
};
//下载文件按钮响应函数
public void download(View v){
//创建一个子线程,用于下载文件
Thread t = new Thread() {
//执行子线程(下载文件)
@Override
public void run() {
try {
//将下载地址封装成URL对象
URL url = new URL(path);
//创建连接对象,此时未建立连接
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
//设置请求方式为get请求
conn.setRequestMethod("GET");
//设置连接超时
conn.setConnectTimeout(5000);
//设置读取超时
conn.setReadTimeout(5000);
//如果请求成功
if(conn.getResponseCode() == 200) {
//获得需要下载的文件的长度
int length = conn.getContentLength();
//设置进度条的最大值就是原文件的总长度
pb.setMax(length);
//创建File对象
File file = new File(Environment.getExternalStorageDirectory(), fileName);
//创建临时文件
RandomAccessFile raf = new RandomAccessFile(file, "rwd");
//设置临时文件的大小
raf.setLength(length);
//关闭临时文件
raf.close();
//计算出每个线程应该下载多少字节
int size = length / ThreadCount;
//遍历下载线程
for (int i = 0; i < ThreadCount; i++) {
//计算线程下载的开始位置
int startIndex = i * size;
//计算线程下载的结束位置
int endIndex = (i + 1) * size - 1;
//如果是最后一个线程,那么结束位置写死
if(i == ThreadCount - 1) {
endIndex = length - 1;
}
//创建下载线程
DownLoadThread thread = new DownLoadThread(startIndex, endIndex, i);
//启动下载线程
thread.start();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
};
t.start();
}
//创建一个继承自线程类的下载线程类(其实是一个内部类)
class DownLoadThread extends Thread {
//下载开始的位置
int startIndex;
//下载结束的位置
int endIndex;
//下载线程的id
int threadId;
//下载线程类的构造方法
public DownLoadThread(int startIndex, int endIndex, int threadId) {
super();
this.startIndex = startIndex;
this.endIndex = endIndex;
this.threadId = threadId;
}
//执行下载线程
@Override
public void run() {
try {
//创建进度临时文件
File progressFile = new File(Environment.getExternalStorageDirectory(), threadId + ".txt");
//如果SD卡中存在进度临时文件
if(progressFile.exists()) {
//创建文件输出流
FileInputStream fis = new FileInputStream(progressFile);
//InputStreamReader:创建输输入流缓冲区
//BufferedReader:创建读取缓冲区
BufferedReader br = new BufferedReader(new InputStreamReader(fis));
//从进度临时文件中读取出上一次下载的总进度,然后与原本的开始位置相加,得到新的开始位置
int lastProgress = Integer.parseInt(br.readLine());
//设置初始位置
startIndex += lastProgress;
//把上次下载的进度显示至进度条
currentProgress += lastProgress;
pb.setProgress(currentProgress);
//发送消息,让主线程刷新文本进度
handler.sendEmptyMessage(1);
//关闭文件输入流
fis.close();
}
System.out.println("线程" + threadId + "的下载区间是:" + startIndex + "---" + endIndex);
//将下载地址封装成URL对象
URL url = new URL(path);
//创建连接对象,此时未建立连接
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
//设置请求方式为get请求
conn.setRequestMethod("GET");
//设置连接超时
conn.setConnectTimeout(5000);
//设置读取超时
conn.setReadTimeout(5000);
//设置本次http请求所请求的数据的区间
conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);
//请求部分数据,相应码是206
if(conn.getResponseCode() == 206) {
//流里此时只有1/3原文件的数据
InputStream is = conn.getInputStream();
byte[] b = new byte[1024];
int len = 0;
int total = 0;
//拿到临时文件的输出流
File file = new File(Environment.getExternalStorageDirectory(), fileName);
//使用临时文件输出流创建临时文件
RandomAccessFile raf = new RandomAccessFile(file, "rwd");
//把文件的写入位置移动至startIndex
raf.seek(startIndex);
while((len = is.read(b)) != -1) {
//每次读取流里数据之后,同步把数据写入临时文件
raf.write(b, 0, len);
total += len;
System.out.println("线程" + threadId + "下载了" + total);
//每次读取流里数据之后,把本次读取的数据的长度显示至进度条
currentProgress += len;
pb.setProgress(currentProgress);
//发送消息,让主线程刷新文本进度
handler.sendEmptyMessage(1);
//生成一个专门用来记录下载进度的临时文件
RandomAccessFile progressRaf = new RandomAccessFile(progressFile, "rwd");
//每次读取流里数据之后,同步把当前线程下载的总进度写入进度临时文件中
progressRaf.write((total + "").getBytes());
//关闭临时文件
progressRaf.close();
}
System.out.println("线程" + threadId + "下载完毕-------------------小志参上!");
//关闭临时文件
raf.close();
//下载结束的进程个数加1
finishedThread++;
synchronized (path) {
//如果所有的下载进程都下载结束
if(finishedThread == ThreadCount) {
//遍历下载进度
for (int i = 0; i < ThreadCount; i++) {
//获取下载进度所在的文件
File f = new File(Environment.getExternalStorageDirectory(), i + ".txt");
//删除保存下载进度的临时文件
f.delete();
}
//下载结束的下载线程的个数设置为0
finishedThread = 0;
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
xml代码:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.fyt.mobilemultidownload.MainActivity"
android:orientation="vertical">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="开始下载"
android:onClick="download"/>
<ProgressBar
android:id="@+id/pb"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@android:style/Widget.ProgressBar.Horizontal"/>
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
这个多线程的断点续传,各位看看就行了,基本上网络上百度都有很多工具类,别人已经给你封装很好了,很优化了,无需自己再动手去写,我这里没有什么优化,只是实现了这个功能而已,时间原因。
视频总时长倒计时代码完整:(例子:就像这个视频总长度为15秒,使用下面这个功能就是这种效果 15->14->13->12…..->0依次递减)
//取得播放时长 计算
int duration = mediaPlayer.getDuration();
int time = duration / 1000;
DLog.d(" fineadboost video time==>: " + duration);
Message msg = mHandler.obtainMessage();
msg.what = START_COUNTING;
msg.obj = time;
mHandler.sendMessageDelayed(msg, time);
class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case START_COUNTING:
int count = (int) msg.obj;
mTextView.setText(count + "");
if (mTextView.getText().equals("0")){
mTextView.setVisibility(View.GONE);
}
if (count > 0) {
Message msg1 = obtainMessage();
msg1.what = START_COUNTING;
msg1.obj = count - 1;
sendMessageDelayed(msg1, 1000);
}
break;
default:
break;
}
}
}
总体效果图为下列:
啥都不需要,点个赞就行了。