android中凡是网络请求均必须在线程中
权限
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<!-- 以下权限为允许访问移动网络数据信息 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
**/
1.Downloader.java
package com.studio32a.canplayer;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.util.Log;
public class Downloader {
public final static int DOWNLOAD_FINISHTED = 1;
public final static int HASEXCEPTION = 2;
private final static int INTERVAL = 200;// 请求下载索引的时间间隔200毫秒
private Context context;
private long startTime = 0;// 刷新开始计时
public String saveFullPath;
private boolean isStopDownload = false;
public Handler handler;
private MainActivity mainActivity;
public Downloader(Context context) {
this.context = context;
mainActivity = (MainActivity) Downloader.this.context;
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
String inf = (String) msg.obj;
switch (msg.what) {
case DOWNLOAD_FINISHTED:
if (mainActivity.isRecording) {// 下载全部完成而非手动停止时
mainActivity.isRecording = false;
mainActivity.iv_rec.setImageResource(R.drawable.recorder);
isStopDownload = true;
}
mainActivity.showInfoDialog("视频保存在", inf);
break;
case HASEXCEPTION:
mainActivity.isRecording = false;
mainActivity.iv_rec.setImageResource(R.drawable.recorder);
isStopDownload = true;
mainActivity.showInfoDialog("异常提示", "下载失败:\n" + inf);
break;
}
}
};
}
public void setStopDownload(boolean isStopDownload) {
this.isStopDownload = isStopDownload;
}
// 下载入口方法
public void downloadVideo(String saveDir, String srcUrl, boolean isM3u8) {
if (!isM3u8)
new DownLoadNotM3u8Thread(saveDir, srcUrl).start();
else
new DownLoadM3u8SegmentsThread(srcUrl, saveDir).start();
}
// 发出异常消息
private void handleException(Exception e) {
Message message = handler.obtainMessage();
message.what = HASEXCEPTION;
message.obj = e.getMessage();
handler.sendMessage(message);
}
// =========m3u8视频的下载方法及线程=========
// 下载索引文件
private String getIndexFile(String srcUrl) throws Exception {
URL url = new URL(srcUrl);
InputStream is = url.openStream();
InputStreamReader isr = new InputStreamReader(is, "UTF-8");
BufferedReader br = new BufferedReader(isr);
String content = "";
String line = null;
while ((line = br.readLine()) != null) {
content += line + "\n";
}
br.close();
return content;
}
// 解析索引文件,得到首片段的路径,可能是长路径,也可能是短径
private String getFirstSegmentPath(String srcUrl) throws Exception {
String newfirstSegmentPath = "";
String content = getIndexFile(srcUrl);
Pattern pattern_ts = Pattern.compile(".*ts");// 正则中.不包括\n,因此可分别匹配多行
Matcher matcher_ts = pattern_ts.matcher(content);
Pattern pattern_m3u8 = Pattern.compile(".*m3u8.*");// 正则中.不包括\n,因此可分别匹配多行
Matcher matcher_m3u8 = pattern_m3u8.matcher(content);
if (matcher_ts.find()) {
newfirstSegmentPath = matcher_ts.group();
// 在形如/live/cctv2_2中取出/live
int splitIndex = newfirstSegmentPath.indexOf(File.separator, 1);// 从第二个字符开始找
String splitStr = "";
String prePath="";
if (splitIndex == -1)
prePath = srcUrl.substring(0, srcUrl.lastIndexOf(File.separator) + 1);
else {
splitStr = newfirstSegmentPath.substring(0, splitIndex);
prePath = srcUrl.substring(0, srcUrl.lastIndexOf(splitStr));
}
// 如果不是完整路径则补全
if (!newfirstSegmentPath.contains("://")) {
newfirstSegmentPath = prePath + newfirstSegmentPath;
}
if(!newfirstSegmentPath.contains("://"))newfirstSegmentPath="http://"+newfirstSegmentPath;
} else if (matcher_m3u8.find()) {
String m3u8Path = matcher_m3u8.group();
// 在形如/live/cctv2_2中取出/live
int splitIndex = m3u8Path.indexOf(File.separator, 1);// 从第二个字符开始找
String splitStr = "";
String prePath="";
if (splitIndex == -1) {
prePath = srcUrl.substring(0, srcUrl.lastIndexOf(File.separator) + 1);
} else {
splitStr = m3u8Path.substring(0, splitIndex);
prePath = srcUrl.substring(0, srcUrl.lastIndexOf(splitStr));
}
if (!m3u8Path.contains("://")) {
m3u8Path = prePath + m3u8Path;
}
if(!m3u8Path.contains("://"))m3u8Path="http://"+m3u8Path;
newfirstSegmentPath = getFirstSegmentPath(m3u8Path);
if(!newfirstSegmentPath.contains("://"))newfirstSegmentPath="http://"+newfirstSegmentPath;
}
return newfirstSegmentPath;
}
// 解析索引文件,得到多个片段的路径的列表,可能是长路径,也可能是短径
private ArrayList<String> getSegmentPathList(String srcUrl) throws Exception {
ArrayList<String> list = new ArrayList<String>();
String content = getIndexFile(srcUrl);
Pattern pattern_ts = Pattern.compile(".*ts");// 正则中.不包括\n,因此可分别匹配多行
Matcher matcher_ts = pattern_ts.matcher(content);
Pattern pattern_m3u8 = Pattern.compile(".*m3u8.*");// 正则中.不包括\n,因此可分别匹配多行
Matcher matcher_m3u8 = pattern_m3u8.matcher(content);
while (matcher_ts.find()) {
String newfirstSegmentPath = matcher_ts.group();
// 在形如/live/cctv2_2中取出/live
int splitIndex = newfirstSegmentPath.indexOf(File.separator, 1);// 从第二个字符开始找
String splitStr = "";
String prePath="";
if (splitIndex == -1)
prePath = srcUrl.substring(0, srcUrl.lastIndexOf(File.separator) + 1);
else {
splitStr = newfirstSegmentPath.substring(0, splitIndex);
prePath = srcUrl.substring(0, srcUrl.lastIndexOf(splitStr));
}
// 如果不是完整路径则补全
if (!newfirstSegmentPath.contains("://")) {
newfirstSegmentPath = prePath + newfirstSegmentPath;
}
if(!newfirstSegmentPath.contains("://"))newfirstSegmentPath="http://"+newfirstSegmentPath;
list.add(newfirstSegmentPath);
}
if (matcher_m3u8.find()) {
String m3u8Path = matcher_m3u8.group();
// 在形如/live/cctv2_2中取出/live
int splitIndex = m3u8Path.indexOf(File.separator, 1);// 从第二个字符开始找
String splitStr = "";
String prePath="";
if (splitIndex == -1) {
prePath = srcUrl.substring(0, srcUrl.lastIndexOf(File.separator) + 1);
} else {
splitStr = m3u8Path.substring(0, splitIndex);
prePath = srcUrl.substring(0, srcUrl.lastIndexOf(splitStr));
}
if (!m3u8Path.contains("://")) {
m3u8Path = prePath + m3u8Path;
}
if(!m3u8Path.contains("://"))m3u8Path="http://"+m3u8Path;
list = getSegmentPathList(m3u8Path);
}
return list;
}
// 下载单个片段到流,(凡是请求网络均必须在线程中)
private InputStream getM3u8Instream(String segmentFullUrl) {
InputStream in = null;
try {
URL url = new URL(segmentFullUrl);
in = url.openStream();
//Log.i("luo", "成功:" + segmentFullUrl);
} catch (Exception e) {
handleException(e);
}
return in;
}
public class DownLoadM3u8SegmentsThread extends Thread {
private String saveDir;
private String srcUrl;
public DownLoadM3u8SegmentsThread(String srcUrl, String saveDir) {
this.srcUrl = srcUrl;
this.saveDir = saveDir;
}
// 合并视频片段(循环获取)
public boolean mergeVideo(String savePath) throws Exception {
String newfirstSegmentPath = getFirstSegmentPath(srcUrl);
if (newfirstSegmentPath.isEmpty()) {
return false;
}
String oldfirstSegmentPathath = "";
FileOutputStream fos = new FileOutputStream(savePath, true);// 采用追加模式
byte[] bytes = new byte[1024 * 5];
int length = -1;
while (!isStopDownload) {
if (!oldfirstSegmentPathath.equals(newfirstSegmentPath)) {
oldfirstSegmentPathath = newfirstSegmentPath;
if (!newfirstSegmentPath.isEmpty()) {
InputStream in = getM3u8Instream(newfirstSegmentPath);
while ((length = in.read(bytes)) != -1) {
fos.write(bytes, 0, length);
}
in.close();
startTime = SystemClock.uptimeMillis();
}
}
Thread.sleep(INTERVAL);// 每间隔INTERVAL毫秒请求一次
// 已经有多长时间没有得到新的索引
long noUpdateTimeLong = SystemClock.uptimeMillis() - startTime;
// 如果1分钟没有得到新索引,就认为已没有新的索引,将剩余索引下载完就停止下载
if (noUpdateTimeLong > 1000 * 60) {
// 下载将剩余的片段
ArrayList<String> segmentPathList = getSegmentPathList(srcUrl);
for (int i = 1; i < segmentPathList.size(); i++) {// 注意:0已在之前下载(一直未更新),所以从1开始
String newSegmentPath = segmentPathList.get(i);
if (newSegmentPath != null && !newSegmentPath.isEmpty()) {
InputStream in2 = getM3u8Instream(newSegmentPath);
while ((length = in2.read(bytes)) != -1) {
fos.write(bytes, 0, length);
}
in2.close();
}
}
isStopDownload = true;
break;
}
// 每下载一个,更新
newfirstSegmentPath = getFirstSegmentPath(srcUrl);
}
fos.flush();
fos.close();
return true;
}
@Override
public void run() {
try {
// 获取时间截及路径
SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmssSSS");// SSS为毫秒
Date curDate = new Date(System.currentTimeMillis());// 获取当前时间
String timeStr = formatter.format(curDate);
String savePath = saveDir + File.separator + timeStr + ".ts";
Downloader.this.saveFullPath = savePath;
boolean isOk = mergeVideo(savePath);
// 合并函数运行完,即意味着手动停止、程序退出、
// 进入后台超时、或真正下载完毕,已保存完毕,此时发消息
Message message = handler.obtainMessage();
message.what = DOWNLOAD_FINISHTED;
if (isOk) {
message.obj = savePath;
} else {
message.obj = "录制未成功";
}
handler.sendMessage(message);
} catch (Exception e) {
Log.i("luo", e.getMessage());
handleException(e);
}
}
}
// =========m3u8之外的普通视频的下载线程=========
public class DownLoadNotM3u8Thread extends Thread {
private String savePath;
private String srcUrl;
public DownLoadNotM3u8Thread(String saveDir, String srcUrl) {
// 获取时间截
SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmssSSS");// SSS为毫秒
Date curDate = new Date(System.currentTimeMillis());// 获取当前时间
String timeStr = formatter.format(curDate);
this.savePath = saveDir + File.separator + timeStr + ".mp4";
Downloader.this.saveFullPath = savePath;
this.srcUrl = srcUrl;
}
@Override
public void run() {
try {
URL url = new URL(srcUrl);
InputStream in = url.openStream();
BufferedInputStream bis = new BufferedInputStream(in);
FileOutputStream fos = new FileOutputStream(savePath);
BufferedOutputStream bos = new BufferedOutputStream(fos);
byte[] bytes = new byte[1024 * 8];
int len = -1;
while ((len = bis.read(bytes)) != -1 && !isStopDownload) {
bos.write(bytes, 0, len);
}
bos.flush();
bos.close();// 关闭输出流
bis.close();// 关闭输入流
Message message = handler.obtainMessage();
message.what = DOWNLOAD_FINISHTED;
message.obj = savePath;
handler.sendMessage(message);
} catch (Exception e) {
handleException(e);
}
}
}
}
2.在MainActivity中
(1)新建Downloader对象,并在下载前设置downloader.setStopDownload(false);,调用其入口方法外
(2)在相关情形下,比如打开新视频时、点击停录按钮,ownloader.setStopDownload(false)设停止下载
(3)退出程序、进入后台的处理方法
①handler
private Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case STOP_DOWNLOAD_DELAY:
downloader.setStopDownload(true);
break;
}
};
};
②
视频下载停止延迟,参数如果为-1则移除停止消息下载
private void recordDelayStop(long delayMillis){
if(delayMillis==-1){
handler.removeMessages(STOP_DOWNLOAD_DELAY);
}else{
handler.sendEmptyMessageDelayed(STOP_DOWNLOAD_DELAY, delayMillis);
}
}
③onDestroy/onStop/onResume中
@Override
protected void onResume() {
super.onResume();
if(isRecording)recordDelayStop(-1);//移除停止录播的消息
}
@Override
protected void onStop() {
super.onStop();
if(isRecording)recordDelayStop(1000*60*5);//5分钟后如不恢复将停止录播
}
@Override
protected void onDestroy() {
super.onDestroy();
if(isRecording){//立即停止下载
recordDelayStop(0);
}
}