Android视频缓存的应用
一、AndroidVideoCache
github:https://github.com/danikula/AndroidVideoCache
AndroidVideoCache是国外大神Alexey Danilov写的一个android开源库。一个视频/音频缓存库,利用本地代理实现了边下边播,支VideoView/MediaPlayer, ExoPlayer ,IJK等播放器的边下载边播放。集成简单,与业务无关。
引入依赖gradle配置
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.kuaige.kgplayer"
minSdkVersion 24
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
implementation 'com.danikula:videocache:2.7.0'
// required, enough for most devices.
implementation 'tv.danmaku.ijk.media:ijkplayer-java:0.8.8'
implementation 'tv.danmaku.ijk.media:ijkplayer-armv7a:0.8.8'
// Other ABIs: optional
implementation 'tv.danmaku.ijk.media:ijkplayer-armv5:0.8.8'
implementation 'tv.danmaku.ijk.media:ijkplayer-arm64:0.8.8'
implementation 'tv.danmaku.ijk.media:ijkplayer-x86:0.8.8'
implementation 'tv.danmaku.ijk.media:ijkplayer-x86_64:0.8.8'
// ExoPlayer as IMediaPlayer: optional, experimental
implementation 'tv.danmaku.ijk.media:ijkplayer-exo:0.8.8'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
使用基于ijkplayer封装的播放控件测试代码如下
1.App中创建HttpProxyCacheServer的单例并build参数
package com.kuaige.kgplayer; import android.app.Application; import android.content.Context; import com.danikula.videocache.HttpProxyCacheServer; public class App extends Application { @Override public void onCreate() { super.onCreate(); } private HttpProxyCacheServer proxy;//App中创建单例proxy public static HttpProxyCacheServer getProxy(Context context) { App app = (App) context.getApplicationContext(); return app.proxy == null ? (app.proxy = app.newProxy()) : app.proxy; } private HttpProxyCacheServer newProxy() { return new HttpProxyCacheServer.Builder(this) .maxCacheSize(1024 * 1024 * 1024)//设置最大缓存大小 .maxCacheFilesCount(20)//限制缓存中的文件总数 .cacheDirectory(Utils.getAudioCacheDir(this))//缓存目录 .fileNameGenerator(new MyFileNameGenerator())//缓存文件命名格式 .diskUsage(new MyCoolDiskUsageStrategy()) .build(); } }
MyFileNameGenerator
package com.kuaige.kgplayer; import android.net.Uri; import com.danikula.videocache.file.FileNameGenerator; public class MyFileNameGenerator implements FileNameGenerator { // Urls contain mutable parts (parameter 'sessionToken') and stable video's id (parameter 'videoId'). // e. g. http://example.com?videoId=abcqaz&sessionToken=xyz987 public String generate(String url) { Uri uri = Uri.parse(url); String videoId = uri.getQueryParameter("videoId"); return videoId + ".mp4";//videoId唯一则生成缓存文件不重复 } }
MyCoolDiskUsageStrategy
package com.kuaige.kgplayer; import android.util.Log; import com.danikula.videocache.file.DiskUsage; import java.io.File; import java.io.IOException; public class MyCoolDiskUsageStrategy implements DiskUsage { @Override public void touch(File file) throws IOException { Log.d("aaaa", "Name"+file.getName()); Log.d("aaaa", "Path"+file.getAbsolutePath()); Log.d("aaaa", "Space"+file.getTotalSpace()); } }
Utils
package com.kuaige.kgplayer; import android.content.Context; import java.io.File; public class Utils { public static File getAudioCacheDir(Context context) { return new File(context.getExternalFilesDir("mp4"), "audio-cache"); } }
重新封装的VideoPlayer
VideoListener
package com.kuaige.kgplayer; import tv.danmaku.ijk.media.player.IMediaPlayer; public interface VideoListener extends IMediaPlayer.OnBufferingUpdateListener, IMediaPlayer.OnCompletionListener, IMediaPlayer.OnPreparedListener, IMediaPlayer.OnInfoListener, IMediaPlayer.OnVideoSizeChangedListener, IMediaPlayer.OnErrorListener, IMediaPlayer.OnSeekCompleteListener{ }
VideoPlayer
package com.kuaige.kgplayer;
import android.content.Context;
import android.graphics.Color;
import android.media.AudioManager;
import android.net.Uri;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.MediaController;
import android.widget.RelativeLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.IOException;
import java.util.Map;
import tv.danmaku.ijk.media.player.IMediaPlayer;
import tv.danmaku.ijk.media.player.IjkMediaPlayer;
public class VideoPlayer extends FrameLayout implements MediaController.MediaPlayerControl{
/**
* 由ijkplayer提供,用于播放视频,需要给他传入一个surfaceView
*/
private IMediaPlayer mMediaPlayer = null;
/**
* 视频文件地址
*/
private String mPath ;
/**
* 视频请求header
*/
private Map<String,String> mHeader;
private SurfaceView mSurfaceView;
private Context mContext;
private boolean mEnableMediaCodec;
private VideoListener mListener;
private AudioManager mAudioManager;
private AudioFocusHelper mAudioFocusHelper;
public VideoPlayer(@NonNull Context context) {
this(context, null);
}
public VideoPlayer(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public VideoPlayer(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
//初始化
private void init(Context context) {
mContext = context;
setBackgroundColor(Color.BLACK);
createSurfaceView();
mAudioManager = (AudioManager)mContext.getApplicationContext().getSystemService(Context.AUDIO_SERVICE);
mAudioFocusHelper = new AudioFocusHelper();
}
//创建surfaceView
private void createSurfaceView() {
mSurfaceView = new SurfaceView(mContext);
mSurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
}
@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
if (mMediaPlayer != null) {
mMediaPlayer.setDisplay(surfaceHolder);
}
}
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
}
});
LayoutParams layoutParams = new LayoutParams(LayoutParams.MATCH_PARENT
, LayoutParams.MATCH_PARENT,Gravity.CENTER);
// mSurfaceView.setLayoutParams(layoutParams);
addView(mSurfaceView,0,layoutParams);
}
//创建一个新的player
private IMediaPlayer createPlayer() {
IjkMediaPlayer ijkMediaPlayer = new IjkMediaPlayer();
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "opensles", 1);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "overlay-format", IjkMediaPlayer.SDL_FCC_RV32);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "framedrop", 1);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "start-on-prepared", 0);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "http-detect-range-support", 1);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_CODEC, "skip_loop_filter", 48);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_CODEC, "min-frames", 100);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "enable-accurate-seek", 1);
ijkMediaPlayer.setVolume(1.0f, 1.0f);
setEnableMediaCodec(ijkMediaPlayer,mEnableMediaCodec);
return ijkMediaPlayer;
}
//设置是否开启硬解码
private void setEnableMediaCodec(IjkMediaPlayer ijkMediaPlayer, boolean isEnable) {
int value = isEnable ? 1 : 0;
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", value);//开启硬解码
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-auto-rotate", value);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-handle-resolution-change", value);
}
public void setEnableMediaCodec(boolean isEnable){
mEnableMediaCodec = isEnable;
}
//设置ijkplayer的监听
private void setListener(IMediaPlayer player){
player.setOnPreparedListener(mPreparedListener);
player.setOnVideoSizeChangedListener(mVideoSizeChangedListener);
}
/**
* 设置自己的player回调
*/
public void setVideoListener(VideoListener listener){
mListener = listener;
}
//设置播放地址
public void setPath(String path) {
setPath(path,null);
}
public void setPath(String path,Map<String,String> header){
mPath = path;
mHeader = header;
}
//开始加载视频
public void load() throws IOException {
if(mMediaPlayer != null){
mMediaPlayer.stop();
mMediaPlayer.release();
}
mMediaPlayer = createPlayer();
setListener(mMediaPlayer);
mMediaPlayer.setDisplay(mSurfaceView.getHolder());
mMediaPlayer.setDataSource(mContext, Uri.parse(mPath),mHeader);
mMediaPlayer.prepareAsync();
}
public void start() {
if (mMediaPlayer != null) {
mMediaPlayer.start();
mAudioFocusHelper.requestFocus();
}
}
public void release() {
if (mMediaPlayer != null) {
mMediaPlayer.reset();
mMediaPlayer.release();
mMediaPlayer = null;
mAudioFocusHelper.abandonFocus();
}
}
public void pause() {
if (mMediaPlayer != null) {
mMediaPlayer.pause();
mAudioFocusHelper.abandonFocus();
}
}
public void stop() {
if (mMediaPlayer != null) {
mMediaPlayer.stop();
mAudioFocusHelper.abandonFocus();
}
}
public void reset() {
if (mMediaPlayer != null) {
mMediaPlayer.reset();
mAudioFocusHelper.abandonFocus();
}
}
public void restart() {
if (mMediaPlayer != null) {
mMediaPlayer.seekTo(0);
}
}
@Override
public int getDuration() {
if (mMediaPlayer != null) {
return (int)mMediaPlayer.getDuration();
} else {
return 0;
}
}
@Override
public int getCurrentPosition() {
if (mMediaPlayer != null) {
return (int)mMediaPlayer.getCurrentPosition();
} else {
return 0;
}
}
@Override
public void seekTo(int i) {
if (mMediaPlayer != null) {
mMediaPlayer.seekTo(i);
}
}
public void seekTo(long l) {
if (mMediaPlayer != null) {
mMediaPlayer.seekTo(l);
}
}
public boolean isPlaying(){
if(mMediaPlayer != null) {
return mMediaPlayer.isPlaying();
}
return false;
}
@Override
public int getBufferPercentage() {
return 0;
}
@Override
public boolean canPause() {
return false;
}
@Override
public boolean canSeekBackward() {
return false;
}
@Override
public boolean canSeekForward() {
return false;
}
@Override
public int getAudioSessionId() {
return 0;
}
//------------------ 各种listener 赋值 ---------------------//
private IMediaPlayer.OnPreparedListener mPreparedListener = new IMediaPlayer.OnPreparedListener(){
@Override
public void onPrepared(IMediaPlayer iMediaPlayer) {
if(mListener != null){
mListener.onPrepared(iMediaPlayer);
}
}
};
private IMediaPlayer.OnVideoSizeChangedListener mVideoSizeChangedListener = new IMediaPlayer.OnVideoSizeChangedListener() {
@Override
public void onVideoSizeChanged(IMediaPlayer iMediaPlayer, int i, int i1, int i2, int i3) {
int videoWidth = iMediaPlayer.getVideoWidth();
int videoHeight = iMediaPlayer.getVideoHeight();
if (videoWidth != 0 && videoHeight != 0) {
mSurfaceView.getHolder().setFixedSize(videoWidth, videoHeight);
}
}
};
//------------------ 音频监听 ---------------------//
/**
* 音频焦点改变监听
*/
private class AudioFocusHelper implements AudioManager.OnAudioFocusChangeListener {
boolean startRequested = false;
boolean pausedForLoss = false;
int currentFocus = 0;
@Override
public void onAudioFocusChange(int focusChange) {
if (currentFocus == focusChange) {
return;
}
currentFocus = focusChange;
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN://获得焦点
case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT://暂时获得焦点
if (startRequested || pausedForLoss) {
start();
startRequested = false;
pausedForLoss = false;
}
if (mMediaPlayer != null)//恢复音量
mMediaPlayer.setVolume(1.0f, 1.0f);
break;
case AudioManager.AUDIOFOCUS_LOSS://焦点丢失
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT://焦点暂时丢失
if (isPlaying()) {
pausedForLoss = true;
pause();
}
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK://此时需降低音量
if (mMediaPlayer != null && isPlaying()) {
mMediaPlayer.setVolume(0.1f, 0.1f);
}
break;
}
}
boolean requestFocus() {
if (currentFocus == AudioManager.AUDIOFOCUS_GAIN) {
return true;
}
if (mAudioManager == null) {
return false;
}
int status = mAudioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
if (AudioManager.AUDIOFOCUS_REQUEST_GRANTED == status) {
currentFocus = AudioManager.AUDIOFOCUS_GAIN;
return true;
}
startRequested = true;
return false;
}
boolean abandonFocus() {
if (mAudioManager == null) {
return false;
}
startRequested = false;
int status = mAudioManager.abandonAudioFocus(this);
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED == status;
}
}
}
MainActivity
package com.kuaige.kgplayer; import android.Manifest; import android.content.pm.PackageManager; import android.os.Build; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import android.os.Handler; import android.os.Message; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.LinearLayout; import android.widget.SeekBar; import android.widget.TextView; import android.widget.Toast; import android.widget.VideoView; import com.danikula.videocache.HttpProxyCacheServer; import java.io.IOException; import tv.danmaku.ijk.media.player.IMediaPlayer; public class MainActivity extends AppCompatActivity implements VideoListener { private String TAG = "MainActivity"; private VideoPlayer videoview; private static final int MY_PERMISSIONS_WRITE_EXTERNAL_STORAGE = 1; String VIDEO_URL ="http://vfx.mtime.cn/Video/2019/03/09/mp4/190309153658147087.mp4"; private Button start; private Button pause; private Button restart; private SeekBar seekBar; private TextView duration; private long seekto; private int MSG_REFRESH = 0001; private LinearLayout top; private LinearLayout bottom; private Handler handler = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message message) { switch (message.what) { case 0001: if (videoview.isPlaying()) { refresh(); Message message1 = new Message(); message1.what = MSG_REFRESH; handler.sendMessage(message1); } break; case 0002: if (videoview!=null){ videoview.seekTo(0); videoview.stop(); seekBar.setProgress(0); } } return true; } private void refreshHandler(){ //handler.removeCallbacks(); handler = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message message) { /** * */ return true; } }); } }); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); videoview = findViewById(R.id.videoview); seekBar = findViewById(R.id.seekbar); videoview.setVideoListener(this); start = findViewById(R.id.start); pause = findViewById(R.id.pause); restart = findViewById(R.id.resume); duration = findViewById(R.id.duration); top = findViewById(R.id.include_play_top); bottom = findViewById(R.id.include_play_bottom); start.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Message message =new Message(); message.what = MSG_REFRESH; handler.sendMessage(message); videoview.start();//已缓存则从本地文件加载 未缓存则缓存 } }); pause.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { videoview.pause(); } }); restart.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { videoview.restart(); } }); //检查版本是否大于M if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, MY_PERMISSIONS_WRITE_EXTERNAL_STORAGE); } else { Log.d("aaaa", "权限已申请"); initVideo(); } } try { videoview.load(); duration.setText(formatTime(videoview.getDuration())); } catch (IOException e) { Toast.makeText(this,"播放失败",Toast.LENGTH_SHORT); e.printStackTrace(); } seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int i, boolean b) { seekto = i; } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onStopTrackingTouch(SeekBar seekBar) { if(videoview!=null&&videoview.isPlaying()){ videoview.seekTo(seekto); Log.d("onStopTrackingTouch", ""+seekto); } } }); } @Override public void onBufferingUpdate(IMediaPlayer iMediaPlayer, int i) { } @Override public void onCompletion(IMediaPlayer iMediaPlayer) { } @Override public boolean onError(IMediaPlayer iMediaPlayer, int i, int i1) { return false; } @Override public boolean onInfo(IMediaPlayer iMediaPlayer, int i, int i1) { return false; } @Override public void onPrepared(IMediaPlayer iMediaPlayer) { Log.d(TAG, "播放器准备就绪"); Log.d(TAG, "时长:"+formatTime(videoview.getDuration())); duration.setText(formatTime(videoview.getDuration())); videoview.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (top.getVisibility()==View.VISIBLE){ top.setVisibility(View.INVISIBLE); bottom.setVisibility(View.INVISIBLE); }else if(top.getVisibility()==View.INVISIBLE||top.getVisibility()==View.GONE){ top.setVisibility(View.VISIBLE); bottom.setVisibility(View.VISIBLE); } } }); } @Override public void onSeekComplete(IMediaPlayer iMediaPlayer) { } @Override public void onVideoSizeChanged(IMediaPlayer iMediaPlayer, int i, int i1, int i2, int i3) { if (videoview!=null&&videoview.isPlaying()){ videoview.seekTo(0); duration.setText(formatTime(videoview.getDuration())); } } private void initVideo() { HttpProxyCacheServer proxy = App.getProxy(this);//获得单例 String proxyUrl = proxy.getProxyUrl(VIDEO_URL);//根据VIDEO_URL获得代理服务器访问路径 if (proxy.isCached(VIDEO_URL)) { Log.d("aaaa", "已缓存"); } else { Log.d("aaaa", "未缓存"); } Log.d("aaaa", proxyUrl); videoview.setPath(proxyUrl); } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { if (requestCode == MY_PERMISSIONS_WRITE_EXTERNAL_STORAGE) { if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { initVideo(); } else { //"权限已拒绝"; } } super.onRequestPermissionsResult(requestCode, permissions, grantResults); } private String formatTime(long time) { time = time/ 1000; String strHour = "" + (time/3600); String strMinute = "" + time%3600/60; String strSecond = "" + time%3600%60; strHour = strHour.length() < 2? "0" + strHour: strHour; strMinute = strMinute.length() < 2? "0" + strMinute: strMinute; strSecond = strSecond.length() < 2? "0" + strSecond: strSecond; String strRsult = ""; if (!strHour.equals("00")) { strRsult += strHour + ":"; } if (!strMinute.equals("00")) { strRsult += strMinute + ":"; } strRsult += strSecond; return strRsult; } private void refresh(){ if (videoview!=null){ seekBar.setProgress((int)(videoview.getCurrentPosition()*100/videoview.getDuration())); Log.d(TAG, "refresh: "+seekBar.getProgress()); } } @Override protected void onPause() { super.onPause(); if (videoview!=null){ videoview.pause(); } } @Override protected void onRestart() { super.onRestart(); if (videoview!=null){ Message message =new Message(); message.what = MSG_REFRESH; handler.sendMessage(message); videoview.start(); } } @Override protected void onResume() { super.onResume(); if (videoview!=null){ Message message =new Message(); message.what = MSG_REFRESH; handler.sendMessage(message); videoview.start(); } } @Override protected void onStop() { super.onStop(); if (videoview!=null){ videoview.pause(); } } @Override protected void onDestroy() { super.onDestroy(); if (videoview!=null){ videoview.stop(); videoview.release(); } } }
目录结构


浙公网安备 33010602011771号