/* * Copyright (C) 2016 AriaLyy(https://github.com/AriaLyy/Aria) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.arialyy.aria.m3u8; import com.arialyy.aria.core.common.RequestEnum; import com.arialyy.aria.core.common.SubThreadConfig; import com.arialyy.aria.core.download.DownloadEntity; import com.arialyy.aria.core.task.AbsThreadTaskAdapter; import com.arialyy.aria.exception.AriaIOException; import com.arialyy.aria.exception.TaskException; import com.arialyy.aria.http.ConnectionHelp; import com.arialyy.aria.http.HttpTaskOption; import com.arialyy.aria.util.ALog; import java.io.BufferedInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStreamWriter; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.net.URLEncoder; import java.nio.ByteBuffer; import java.nio.channels.Channels; import java.nio.channels.FileChannel; import java.nio.channels.ReadableByteChannel; import java.util.Map; import java.util.Set; /** * Created by lyy on 2017/1/18. 下载线程 */ public class M3U8ThreadTaskAdapter extends AbsThreadTaskAdapter { private final String TAG = "M3U8ThreadTask"; private HttpTaskOption mHttpTaskOption; public M3U8ThreadTaskAdapter(SubThreadConfig config) { super(config); mHttpTaskOption = (HttpTaskOption) getTaskWrapper().getTaskOption(); } @Override protected void handlerThreadTask() { if (getThreadRecord().isComplete) { handleComplete(); return; } HttpURLConnection conn = null; BufferedInputStream is = null; try { URL url = ConnectionHelp.handleUrl(getThreadConfig().url, mHttpTaskOption); conn = ConnectionHelp.handleConnection(url, mHttpTaskOption); ALog.d(TAG, String.format("分片【%s】开始下载", getThreadRecord().threadId)); ConnectionHelp.setConnectParam(mHttpTaskOption, conn); conn.setConnectTimeout(getTaskConfig().getConnectTimeOut()); conn.setReadTimeout(getTaskConfig().getIOTimeOut()); //设置读取流的等待时间,必须设置该参数 if (mHttpTaskOption.isChunked()) { conn.setDoInput(true); conn.setChunkedStreamingMode(0); } conn.connect(); // 传递参数 if (mHttpTaskOption.getRequestEnum() == RequestEnum.POST) { Map params = mHttpTaskOption.getParams(); if (params != null) { OutputStreamWriter dos = new OutputStreamWriter(conn.getOutputStream()); Set keys = params.keySet(); StringBuilder sb = new StringBuilder(); for (String key : keys) { sb.append(key).append("=").append(URLEncoder.encode(params.get(key))).append("&"); } String paramStr = sb.toString(); paramStr = paramStr.substring(0, paramStr.length() - 1); dos.write(paramStr); dos.flush(); dos.close(); } } int code = conn.getResponseCode(); if (code != HttpURLConnection.HTTP_OK) { fail(new TaskException(TAG, String.format("连接错误,http错误码:%s,url:%s", code, getThreadConfig().url)), false); return; } is = new BufferedInputStream(ConnectionHelp.convertInputStream(conn)); if (mHttpTaskOption.isChunked()) { readChunked(is); } else if (getThreadConfig().isBlock) { readDynamicFile(is); } } catch (MalformedURLException e) { fail(new TaskException(TAG, String.format("分片【%s】下载失败,filePath: %s, url: %s", getThreadRecord().threadId, getThreadConfig().tempFile.getPath(), getEntity().getUrl()), e), false); } catch (IOException e) { fail(new TaskException(TAG, String.format("分片【%s】下载失败,filePath: %s, url: %s", getThreadRecord().threadId, getThreadConfig().tempFile.getPath(), getEntity().getUrl()), e), true); } catch (Exception e) { fail(new TaskException(TAG, String.format("分片【%s】下载失败,filePath: %s, url: %s", getThreadRecord().threadId, getThreadConfig().tempFile.getPath(), getEntity().getUrl()), e), false); } finally { try { if (is != null) { is.close(); } if (conn != null) { conn.disconnect(); } } catch (IOException e) { e.printStackTrace(); } } } /** * 读取chunked数据 */ private void readChunked(InputStream is) { FileOutputStream fos = null; try { fos = new FileOutputStream(getThreadConfig().tempFile, true); byte[] buffer = new byte[getTaskConfig().getBuffSize()]; int len; while (getThreadTask().isLive() && (len = is.read(buffer)) != -1) { if (getThreadTask().isBreak()) { break; } if (mSpeedBandUtil != null) { mSpeedBandUtil.limitNextBytes(len); } fos.write(buffer, 0, len); progress(len); } handleComplete(); } catch (IOException e) { fail(new AriaIOException(TAG, String.format("文件下载失败,savePath: %s, url: %s", getThreadConfig().tempFile.getPath(), getThreadConfig().url), e), true); } finally { if (fos != null) { try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } } } /** * 动态长度文件读取方式 */ private void readDynamicFile(InputStream is) { FileOutputStream fos = null; FileChannel foc = null; ReadableByteChannel fic = null; try { int len; fos = new FileOutputStream(getThreadConfig().tempFile, true); foc = fos.getChannel(); fic = Channels.newChannel(is); ByteBuffer bf = ByteBuffer.allocate(getTaskConfig().getBuffSize()); //如果要通过 Future 的 cancel 方法取消正在运行的任务,那么该任务必定是可以 对线程中断做出响应 的任务。 while (getThreadTask().isLive() && (len = fic.read(bf)) != -1) { if (getThreadTask().isBreak()) { break; } if (mSpeedBandUtil != null) { mSpeedBandUtil.limitNextBytes(len); } bf.flip(); foc.write(bf); bf.compact(); progress(len); } handleComplete(); } catch (IOException e) { fail(new AriaIOException(TAG, String.format("文件下载失败,savePath: %s, url: %s", getThreadConfig().tempFile.getPath(), getThreadConfig().url), e), true); } finally { try { if (fos != null) { fos.flush(); fos.close(); } if (foc != null) { foc.close(); } if (fic != null) { fic.close(); } } catch (IOException e) { e.printStackTrace(); } } } private DownloadEntity getEntity() { return (DownloadEntity) getTaskWrapper().getEntity(); } /** * 处理完成配置文件的更新或事件回调 */ private void handleComplete() { if (getThreadTask().isBreak()) { return; } complete(); } }