Android视频/音频缓存框架AndroidVideoCache(Okhttp)详解

关于安卓边下边播功能,供大家参考,具体内容如下

对于视频/音频软件,音乐软件,视频软件,都有缓存这个功能,那如何实现边下边播功能:

  • 如何实现这个边下边播功能?
  • 文件是否支持同时读写?(Mediaplayer 播放文件,从网络上下载文件)
  • 播放与下载进度如何协调?
  • 已缓存的文件需及时清理

经过一番折腾,我 find 了 : [ AndroidVideoCache ],这个库是 danikula 大神写,看完源码后收益匪浅。实现流媒体边下边播原理利用socket 开启一个本机的代理服务器

结合自身需求,修改了该库,使用okhttp进行网络请求:
AndroidVideoCache (改成 okhttp 缓存)

package com.danikula.videocache;

import android.text.TextUtils;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import com.danikula.videocache.file.MyLog;

import okhttp3.Call;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

import static com.danikula.videocache.ProxyCacheUtils.DEFAULT_BUFFER_SIZE;
import static com.danikula.videocache.ProxyCacheUtils.LOG_TAG;
import static java.net.HttpURLConnection.HTTP_OK;
import static java.net.HttpURLConnection.HTTP_PARTIAL;

/**
 * {@link Source} that uses http resource as source for {@link ProxyCache}.
 *
 * @author Alexey Danilov (danikula@gmail.com).
 *
 * 从URL 获取数据
 */
public class HttpUrlSource implements Source {

  private static final int MAX_REDIRECTS = 5;
  public final String url;
  private static OkHttpClient okHttpClient = new OkHttpClient();
  private Call requestCall = null;
  private InputStream inputStream;
  private volatile int length = Integer.MIN_VALUE;
  private volatile String mime;
  private Map<String, String> headers;

  public HttpUrlSource(String url) {
    this(url, ProxyCacheUtils.getSupposablyMime(url));
  }

  public HttpUrlSource(String url, Map<String, String> headers) {
    this(url, ProxyCacheUtils.getSupposablyMime(url));
    this.headers = headers;
  }

  public HttpUrlSource(String url, String mime) {
    this.url = Preconditions.checkNotNull(url);
    this.mime = mime;
  }

  public HttpUrlSource(HttpUrlSource source) {
    this.url = source.url;
    this.mime = source.mime;
    this.length = source.length;
  }

  @Override
  public synchronized int length() throws ProxyCacheException {
    if (length == Integer.MIN_VALUE) {
      fetchContentInfo();
    }
    return length;
  }

  @Override
  public void open(int offset) throws ProxyCacheException {
    try {
      Response response = openConnection(offset, -1);
      mime = response.header("Content-Type");
      inputStream = new BufferedInputStream(response.body().byteStream(), DEFAULT_BUFFER_SIZE);
      length = readSourceAvailableBytes(response, offset, response.code());
    } catch (IOException e) {
      throw new ProxyCacheException("Error opening okHttpClient for " + url + " with offset " + offset, e);
    }
  }

  private int readSourceAvailableBytes(Response response, int offset, int responseCode) throws IOException {
    int contentLength = Integer.valueOf(response.header("Content-Length", "-1"));

    return responseCode == HTTP_OK ? contentLength
        : responseCode == HTTP_PARTIAL ? contentLength + offset : length;
  }

  @Override
  public void close() throws ProxyCacheException {
    if (okHttpClient != null && inputStream != null && requestCall != null) {
      try {
        inputStream.close();
        requestCall.cancel();
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }

  @Override
  public int read(byte[] buffer) throws ProxyCacheException {
    if (inputStream == null) {
      throw new ProxyCacheException("Error reading data from " + url + ": okHttpClient is absent!");
    }
    try {
      return inputStream.read(buffer, 0, buffer.length);
    } catch (InterruptedIOException e) {
      throw new InterruptedProxyCacheException("Reading source " + url + " is interrupted", e);
    } catch (IOException e) {
      throw new ProxyCacheException("Error reading data from " + url, e);
    }
  }

  private void fetchContentInfo() throws ProxyCacheException {
    MyLog.d(LOG_TAG, "Read content info from " + url);
    Response response = null;
    InputStream inputStream = null;
    try {
      response = openConnection(0, 20000);
      length = Integer.valueOf(response.header("Content-Length", "-1"));
      mime = response.header("Content-Type");
      inputStream = response.body().byteStream();
      MyLog.i(LOG_TAG, "Content info for `" + url + "`: mime: " + mime + ", content-length: " + length);
    } catch (IOException e) {
      MyLog.e(LOG_TAG, "Error fetching info from " + url, e);
    } finally {
      ProxyCacheUtils.close(inputStream);
      if (response != null) {
        requestCall.cancel();
      }
    }
  }

  private Response openConnection(int offset, int timeout) throws IOException, ProxyCacheException {
    boolean redirected;
    int redirectCount = 0;
    String url = this.url;
    Request request = null;
    //do {
      MyLog.d(LOG_TAG, "Open okHttpClient " + (offset > 0 ? " with offset " + offset : "") + " to " + url);
//      okHttpClient = (HttpURLConnection) new URL(url).openConnection();
      Request.Builder builder = new Request.Builder();
      builder.url(url);
      //flac
      if(headers != null) {
        //设置请求头
        for (Map.Entry<String, String> entry : headers.entrySet()) {
          MyLog.i(LOG_TAG, "请求头信息 key:" + entry.getKey() +" Value" + entry.getValue());
//          okHttpClient.setRequestProperty(entry.getKey(), entry.getValue());
          builder.addHeader(entry.getKey(), entry.getValue());
        }
      }
      if (offset > 0) {
        builder.addHeader("Range", "bytes=" + offset + "-");
      }

      request = builder.build();
      requestCall = okHttpClient.newCall(request);
      /*if (redirected) {
        url = okHttpClient.getHeaderField("Location");
        redirectCount++;
        okHttpClient.disconnect();
      }
      if (redirectCount > MAX_REDIRECTS) {
        throw new ProxyCacheException("Too many redirects: " + redirectCount);
      }*/
    //} while (redirected);

    return requestCall.execute();
  }

  public synchronized String getMime() throws ProxyCacheException {
    if (TextUtils.isEmpty(mime)) {
      fetchContentInfo();
    }
    return mime;
  }

  public String getUrl() {
    return url;
  }

  @Override
  public String toString() {
    return "HttpUrlSource{url='" + url + "}";
  }
}

下载地址:Android视频音频缓存框架AndroidVideoCache

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android线程中Handle的使用讲解

    Android UI线程是不安全的,子线程中进行UI操作,可能会导致程序的崩溃,解决办法:创建一个Message对象,然后借助Handler发送出去,之后在Handler的handleMessage()方法中获得刚才发送的Message对象,然后在这里进行UI操作就不会再出现崩溃了 定义类继承Handler public class BallHandler extends Handler{ ImageView imageview; Bitmap bitmap; public BallHandle

  • Android音频编辑之音频转换PCM与WAV

    前言 本篇开始讲解在Android平台上进行的音频编辑开发,首先需要对音频相关概念有基础的认识.所以本篇要讲解以下内容: 1. 常用音频格式简介 2. WAV和PCM的区别和联系 3. WAV文件头信息 4. 采样率简介 5. 声道数和采样位数下的PCM编码 6. 音频文件解码 7. PCM文件转WAV文件 现在先给出音频编辑的效果图,看看能不能提高大家的积极性~,哈哈 常用音频格式简介 在Android平台上进行音频开发,首先需要对常用的音频格式有个大致的了解.在Android平台上,常用的音

  • Android音频系统AudioTrack使用方法详解

    今天,简单讲讲AudioTrack的使用方法. 1.Android AudioTrack简介 在android中播放声音可以用MediaPlayer和AudioTrack两种方案的,但是两种方案是有很大区别的,MediaPlayer可以播放多种格式的声音文件,例如MP3,AAC,WAV,OGG,MIDI等.而AudioTrack只能播放PCM数据流. 事实上,两种本质上是没啥区别的,MediaPlayer在播放音频时,在framework层还是会创建AudioTrack,把解码后的PCM数流传递

  • Android亮屏速度分析总结

    前面聊的 最近在调试项目的亮屏速度,我们希望在按下power键后到亮屏这个时间能达到500MS以内,在Rockchip 3399和3288上面的时间都不能达到要求,因此引发了一系列的调试之路. 计算按下power键到亮屏的时间 Android 唤醒时间统计 刚开始的时候,我只在android阶段统计时间,也能看到时间的差异,但是不是最准确的,我统计的时间日志如下 01-18 09:13:40.992 683 772 D SurfaceControl: Excessive delay in set

  • Android传感器SensorEventListener之加速度传感器

    这个类(我的是Activity中)继承SensorEventListener接口 先获取传感器对象,再获取传感器对象的类型 //获取传感器管理对象 SensorManager mSensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE); // 获取传感器的类型(TYPE_ACCELEROMETER:加速度传感器) Sensor mSensor = mSensorManager.getDefaultSensor(

  • Android使用MediaRecorder类实现视频和音频录制功能

    一.前期基础知识储备 Android提供了MediaRecorder这一个类来实现视频和音频的录制. 由官方配图可知,MediaRecorder用于录制视频时需要调用一系列的API来设置和录制相关的配置,而且调用方法的顺序是固定的,必须按照这个顺序进行API调用才能正确利用手机摄像头实现录像功能. 调用MediaRecorder的录像API顺序如下: 1)Open Camera - Use the Camera.open() to get an instance of the camera ob

  • Android音频编辑之音频合成功能

    前言 音频编辑系列: - android音频编辑之音频转换PCM与WAV -android音频编辑之音频裁剪 - android音频编辑之音频合成 本篇主要讲解音频PCM数据的合成,这里合成包括音频之间的拼接,混合. - 音频拼接:一段音频连接着另一段音频,两段音频不会同时播放,有先后顺序. - 音频混合:一段音频和另一段音频存在相同的区间,两者会有同时播放的区间. 下面是音频拼接,音频混合的效果图: 音频拼接 如果大家理解了android音频编辑之音频转换PCM与WAV和android音频编辑

  • Android添加音频的几种方法

    在res文件夹中新建一个文件夹,命名为raw.在里面放入我们需要的音频文件. 第一种: // 根据资源创建播放器对象 player = MediaPlayer.create(this, R.raw.xiaoxiaole); try { player.prepare();// 同步 } catch (IllegalStateException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOExcept

  • Android 判断网络状态对音频静音的实现方法

    在实际应用中,我们不希望在教室网络,打开游戏就显示较大的声音,进而影响上课质量.因此,就需要让app变得智能,让app可以根据使用者当前网络状态,自动进行静音等操作. 本次内容分为两部分:1. 识别网络环境 2. 实现app自动静音. 自动静音 /** * 实现静音功能 */ private void silentSwitchOn() { AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVI

  • android采用FFmpeg实现音频混合与拼接剪切

    接触FFmpeg有一段时间了,它是音视频开发的开源库,几乎其他所有播放器.直播平台都基于FFmpeg进行二次开发.本篇文章来总结下采用FFmpeg进行音频处理:音频混合.音频剪切.音频拼接与音频转码. 采用android studio进行开发,配置build.gradle文件: defaultConfig { ...... externalNativeBuild { cmake { cppFlags "" } } ndk { abiFilters "armeabi-v7a&q

随机推荐