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();
        }
    }
}

目录结构

 

posted @ 2020-03-24 17:19  DreamCatt  阅读(990)  评论(0)    收藏  举报