The project than the from danikula/AndroidVideoCache, version 2.7.1
preface
Because the project need, on the basis of the original ijkplayer player to join the cache function, the research has found the better solutions is the local agent, the most famous danikula/AndroidVideoCache. However, there are 2k+ issues on AndroidVideoCache, and the last update is half a year ago. So, in order to combined the actual project and the known problems, for danikula/AndroidVideoCache did some customized optimization.
The original danikula/AndroidVideoCache README look here
To the chase
Below will be a few points about their own customization optimization.
1. If the video drag exceeds the cached part, stop the cache thread download
AndroidVideoCache will always connect to the network to download data, until the data download is complete, and drag to exceed the current partial cache is greater than the current video cache size plus 20% of the video file, will not go cache branch, and the original cache download will not stop immediately. This causes a problem. If the current user’s network environment is not good enough or the current video file itself is large, it takes a long time to drag to a place without cache to play. In view of this point so do their own optimization. The sourceLength * NO_CACHE_BARRIER is replaced with a smaller constant value and the cache download thread is stopped if the user drags past the cached portion, allowing bandwidth to be used to play from the drag point and load out the portion the user needs more quickly. Main changes to the ProxyCache and HttpProxyCache files
//HttpProxyCache.java
public void processRequest(GetRequest request, Socket socket) throws IOException, ProxyCacheException {
OutputStream out = new BufferedOutputStream(socket.getOutputStream());
String responseHeaders = newResponseHeaders(request);
out.write(responseHeaders.getBytes("UTF-8"));
long offset = request.rangeOffset;
if(! isForceCancel && isUseCache(request)) { Log.i(TAG,"processRequest: responseWithCache");
pauseCache(false);
responseWithCache(out, offset);
} else {
Log.i(TAG, "processRequest: responseWithoutCache");
pauseCache(true); responseWithoutCache(out, offset); }}/** * Whether to forcibly cancel the cache */
public void cancelCache(a) {
isForceCancel = true;
}
private boolean isUseCache(GetRequest request) throws ProxyCacheException {
long sourceLength = source.length();
boolean sourceLengthKnown = sourceLength > 0;
long cacheAvailable = cache.available();
// do not use cache for partial requests which too far from available cache. It seems user seek video.
long offset = request.rangeOffset;
// If seek exceeds only a little (here set to 2M), it will still cache
return! sourceLengthKnown || ! request.partial || offset <= cacheAvailable + MINI_OFFSET_CACHE; }...private void responseWithCache(OutputStream out, long offset) throws ProxyCacheException {
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
int readBytes;
try {
while((readBytes = read(buffer, offset, buffer.length)) ! = -1 && !stopped) {
out.write(buffer, 0, readBytes);
offset += readBytes;
}
out.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch(IOException e) { e.printStackTrace(); }}}Copy the code
The ==isUseCache #800023== method is modified to stop the cache when it exceeds the cache by a little bit (set to 2M in this case). This prevents the online playback and cache download threads from occupying the bandwidth at the same time, resulting in a long time to load and play successfully after the jump. PauseCache (true) == pauseCache(true); pauseCache(true) == pauseCache(true); pauseCache(true); Method to mark the parent class as true to stop returning data from the proxy cache to the player. For details, see HttpProxyCache and ProxyCache.
2. Implement cache without player (offline cache)
AndroidVideoCache is player dependent, so this limitation has been modified. Offline caching basically means downloading in advance. Whether the video is downloaded or not, the downloaded part can be used as the video cache. Here for the download is not specific, how to achieve the download function to find the appropriate library. The following describes how to add partially downloaded videos to the local proxy (all downloaded videos do not need to go through the local proxy). Assume that partially downloaded videos have the suffix.download.
2.1 modify FileCache. Java
Add a FileCache constructor that can be passed to a local concrete path
//FileCache.java
public FileCache(String downloadFilePath) throws ProxyCacheException{
try {
this.diskUsage = new UnlimitedDiskUsage();
this.file = new File(downloadFilePath);
this.dataFile = new RandomAccessFile(this.file, "rw");
} catch (IOException e) {
throw new ProxyCacheException("Error using file " + file + " as disc cache", e); }}Copy the code
If a cache file format is added, it needs to be modified to determine whether the cache is complete
@Override
public synchronized void complete(a) throws ProxyCacheException {
if (isCompleted()) {
return;
}
close();
String fileName;
if (file.getName().endsWith(DOWNLOAD_TEMP_POSTFIX)) {
// Temporarily download files
fileName = file.getName().substring(0, file.getName().length() - DOWNLOAD_TEMP_POSTFIX.length());
} else {
fileName = file.getName().substring(0, file.getName().length() - TEMP_POSTFIX.length());
}
File completedFile = new File(file.getParentFile(), fileName);
boolean renamed = file.renameTo(completedFile);
if(! renamed) {throw new ProxyCacheException("Error renaming file " + file + " to " + completedFile + " for completion!");
}
file = completedFile;
try {
dataFile = new RandomAccessFile(file, "r");
diskUsage.touch(file);
} catch (IOException e) {
throw new ProxyCacheException("Error opening " + file + " as disc cache", e); }}...private boolean isTempFile(File file) {
return file.getName().endsWith(TEMP_POSTFIX)
|| file.getName().endsWith(DOWNLOAD_TEMP_POSTFIX);
}
Copy the code
2.2 modify HttpProxyCacheServerClients
Add a HttpProxyCacheServerClients constructor can be introduced into local video files, most changes have a comment, so I will no longer be extra explanation.
private FileCache mCache;
private String downloadPath=null;
public HttpProxyCacheServerClients(String url, Config config) {
this.url = checkNotNull(url);
this.config = checkNotNull(config);
this.uiCacheListener = new UiListenerHandler(url, listeners);
}
public void processRequest(GetRequest request, Socket socket) {
try {
startProcessRequest();
clientsCount.incrementAndGet();
proxyCache.processRequest(request, socket);
} catch (Exception e) {
e.printStackTrace();
if (e instanceofProxyCacheException){ uiCacheListener.onCacheError(e); }}finally{ finishProcessRequest(); }}...private synchronized void startProcessRequest(a) throws ProxyCacheException {
if (proxyCache == null) {if (downloadPath==null) {/ / the original proxyCache
proxyCache=newHttpProxyCache();
}else{
// A partially downloaded video file is used as a cachenewHttpProxyCacheForDownloadFile(downloadPath); }}if(isCancelCache){ proxyCache.cancelCache(); }}...public void shutdown(a) {
listeners.clear();
if(proxyCache ! =null) {
proxyCache.registerCacheListener(null);
proxyCache.shutdown();
proxyCache = null;
}
clientsCount.set(0);
// Clear unnecessary caches
if(mCache ! =null && isCancelCache && downloadPath == null) { mCache.file.delete(); }}/** * Generates a cached file based on the partially downloaded video *@param downloadFilePath
* @return
* @throws ProxyCacheException
*/
private void newHttpProxyCacheForDownloadFile(String downloadFilePath) throws ProxyCacheException {
HttpUrlSource source = new HttpUrlSource(url, config.sourceInfoStorage, config.headerInjector);
mCache = new FileCache(downloadFilePath);
HttpProxyCache httpProxyCache = new HttpProxyCache(source, mCache);
httpProxyCache.registerCacheListener(uiCacheListener);
proxyCache = httpProxyCache;
}
Copy the code
Yes, it’s as simple as that. The downloaded video file in the local part can be used as the video cache, and the video can continue to be cached while the video is playing, and the data can be written to the downloaded video file in the local part.
3. High bit rate cache, low bit rate no cache
This is required by our project. Only high bit rate videos above HD are cached, while low bit rate videos are directly played online. Part of that is the power of the player itself. IjkPlayer cancelCache(mVideoUrl) : cancelCache(mVideoUrl) : isForceCancel: true; Reissuing the proxy request after seekTo, isForceCancel=true, will not branch the cache, but play online. Specific process to see the source code.
public void onPrepared(IMediaPlayer mp) {
...
if ( !isLocalVideo && bitrate < MINI_BITRATE_USE_CACHE
&& mCacheManager.getDownloadTempPath(mVideoUrl)==null)
{
bufferPoint = - 1;
mOnBufferUpdateListener.update(this.- 1);
mCacheManager.cancelCache(mVideoUrl);
// Note: seekTo reinitiates the request to the local proxy, cancelCache will not branch the cache
if (lastWatchPosition==- 1){
seekTo(1);
}else{ seekTo(lastWatchPosition); }}if(mPreparedListener ! =null) {
mPreparedListener.onPrepared(this); }... }Copy the code
4. Other minor modifications
The rest of the changes are few and unimportant, so I won’t go into details. It is worth mentioning that slF4J dependencies are removed and all logging parts are entered using the native logs of Andrdoid.