Background,

Recently there is a need to do about the video cache, understand the related open source library AndroidVideoCache, a relatively popular video cache framework on the market, and I want to use the framework for video cache processing, and hope to be able to support pre-loading. However, the author of the framework stopped maintenance in 18 years, so there is unlimited programming space for other programmers. For video preloading, there is only one article “AndroidVideoCache source code detail and retrofit series – source code Article”, but click on the author’s blog list, what about preloading?? AndroidVideoCache preloading is not done for AndroidVideoCache, so in this case… Do it yourself.

First of all, you need to understand how AndroidVideoCache works. It is recommended to check out AndroidVideoCache- Proxy Strategy for Caching video while playing.

In fact, the idea of preloading is very simple, after playing a video, then return the url of the video that needs to be preloaded next, enable the background thread to request to download the data, but the middle involves more detailed logic.

First, the implementation scheme

The main logic is:

1, the background starts a thread to request and preload a part of the data

If you want to preload data that is larger than or equal to 1, you need to load the data in the queue first. If you want to load the data in the queue first, you need to load it first.

We first define the task situation we need to deal with:

Private void preload(String method, Call Call) {switch (method) {case "addPreloadURL": addPreloadURL(Call); // Add url to preloaded queue break; case "cancelPreloadURLIfNeeded": cancelPreloadURLIfNeeded(call); // Cancel the corresponding URL preload (because the video may need to be played immediately, so there is no need to preload) break; case "cancelAnyPreloads": cancelAnyPreLoads(); // Cancel all preloading, mainly to facilitate the management task break; default: } }Copy the code

So the preload logic for each time is basically this method execution order:

cancelPreloadURLIfNeeded()->addPreloadURL(); // Cancel the task of loading the corresponding URL, because it is possible that the URL does not need to be preloaded any more.

cancelAnyPreLoads()->addPreloadURL(); // Cancel the task of loading the url (you need to play the latest video immediately, so you should give the network speed to this video), and then add another round of preloaded URLS.

Next specific processing logic VideoPreLoader class, I directly put all the code logic, to facilitate the observation of the deleted part of the less important logic, in fact, the overall process is relatively simple.

public class VideoPreLoader { private Handler handler; private HandlerThread handlerThread; private List<String> cancelList = new ArrayList<>(); private VideoPreLoader() { handlerThread = new HandlerThread("VideoPreLoaderThread"); handlerThread.start(); handler = new Handler(handlerThread.getLooper()) { @Override public void handleMessage(Message msg) { super.handleMessage(msg); }}; } void addPreloadURL(final VideoPreLoadModel data) { handler.post(new Runnable() { @Override public void run() { realPreload(data); }}); } void cancelPreloadURLIfNeeded(String url) { cancelList.add(url); } void cancelAnyPreLoads() { handler.removeCallbacksAndMessages(null); cancelList.clear(); } private void realPreload(VideoPreLoadModel data) { if (data == null || isCancel(data.originalUrl)) { return; } HttpURLConnection conn = null; try { URL myURL = new URL(data.proxyUrl); conn = (HttpURLConnection) myURL.openConnection(); conn.connect(); InputStream is = conn.getInputStream(); byte[] buf = new byte[1024]; int downLoadedSize = 0; do { int numRead = is.read(buf); downLoadedSize += numRead; if (downLoadedSize >= data.preLoadBytes || numRead == -1) { //Reached preload range or end of Input stream. break; } } while (true); is.close(); }... } private boolean isCancel(String url) { if (TextUtils.isEmpty(url)) { return true; } for (String cancelUrl : cancelList) { if (cancelUrl.equals(url)) { return true; } } return false; }}Copy the code

In fact, there are “two” queues in this code, one is the queue in HandlerThread. Students familiar with message mechanism should understand that there is a LOoper in the internal loop to continuously obtain messages, and the next message will be processed after one message is processed. I also defined unqueued. Since we don’t have much control over the unqueued tasks in HandlerThread, we set an unqueued one so that when a subsequent message needs to be executed, it will first determine whether it is in the unqueued queue. This way we can control the preloaded queue logic.

Second, about some details

In this way, when we play a video, all we need to do is pass the URL of the next video to be played, and we can preload and cache it, but there are other conditions:

The length of the preload?

For video load length, it’s easy to think of adding Range to the video URL request on top of the header, such as

conn.addRequestProperty("Range", "0-102400");
Copy the code

We only get the first 102,400 bytes, so we don’t have to preload the whole video, and I’ve tried to do that, but it turns out to be crappy. I did a lot of testing, but I found that no matter how I requested the responseCode, I got 206 responseCode, but I still downloaded all the data.

Finally to the source code to find: the source code to do regular matching range

private static final Pattern RANGE_HEADER_PATTERN = Pattern.compile("[R,r]ange:[ ]?bytes=(\\d*)-");

private long findRangeOffset(String request) {
        Matcher matcher = RANGE_HEADER_PATTERN.matcher(request);
        if (matcher.find()) {
            String rangeValue = matcher.group(1);
            return Long.parseLong(rangeValue);
        }
        return -1;
}
Copy the code

Look at the

“[R,r]ange:[ ]? bytes=(\d

)-“* It only matches the previous one, that is, I passed in 0-102400 and it ended up being treated as: Range: 0-, resulting in the Range implementation of the addRequestProperty setting. The pit! But you can see why he did it, and we’ll talk about that in the epilogue. There is no way to use the most primitive method of judgment: every time the inputStream is obtained to determine whether the preload size, although there is a certain performance overhead, but not to change the source code there is no way.

  do {
        int numRead = is.read(buf);
        downLoadedSize += numRead;
        if (downLoadedSize >= data.preLoadBytes || numRead == -1) { //Reached  preload range or end of Input stream.
          break;
        }
      } while (true);
      is.close();
Copy the code

Third, summary

This article mainly talks about the preloading principle based on AndroidVideoCache, and the pit encountered

1, preloading mainly through HandlerThread to achieve the background network access and cache processing logic

2. Join the cancellation queue to control the tasks that need to be cancelled

3, for the preloaded size can only be judged by reading, there is no way to use range to judge. In fact, it is easy to understand why the author wrote the regex like this, because it is just a video cache framework, mainly used to “play while saving”, so every time to make a request should be on the original cache to cache data processing, and the cache finally needs to process the content size. You don’t need to worry about the end Range in Range anymore.