[Android] Store covers in a cache on the sdcard for faster loading

Sébastien Toque git at videolan.org
Mon Oct 8 23:18:21 CEST 2012


vlc-ports/android | branch: master | Sébastien Toque <xilasz at gmail.com> | Sat Oct  6 15:54:33 2012 +0200| [42d6c16caa8ec4e79ec12e5fc173cc007be3ad8b] | committer: Sébastien Toque

Store covers in a cache on the sdcard for faster loading

> http://git.videolan.org/gitweb.cgi/vlc-ports/android.git/?a=commit;h=42d6c16caa8ec4e79ec12e5fc173cc007be3ad8b
---

 vlc-android/src/org/videolan/vlc/MurmurHash.java   |  183 ++++++++++++++++++++
 .../src/org/videolan/vlc/gui/audio/AudioUtil.java  |   49 +++++-
 2 files changed, 231 insertions(+), 1 deletion(-)

diff --git a/vlc-android/src/org/videolan/vlc/MurmurHash.java b/vlc-android/src/org/videolan/vlc/MurmurHash.java
new file mode 100644
index 0000000..bae3723
--- /dev/null
+++ b/vlc-android/src/org/videolan/vlc/MurmurHash.java
@@ -0,0 +1,183 @@
+package org.videolan.vlc;
+
+/** Murmur hash 2.0.
+ *
+ * The murmur hash is a relative fast hash function from
+ * http://murmurhash.googlepages.com/ for platforms with efficient
+ * multiplication.
+ *
+ * This is a re-implementation of the original C code plus some
+ * additional features.
+ *
+ * Public domain.
+ *
+ * @author Viliam Holub
+ * @version 1.0.2
+ *
+ */
+public final class MurmurHash {
+
+    /** Generates 32 bit hash from byte array of the given length and
+     * seed.
+     *
+     * @param data byte array to hash
+     * @param length length of the array to hash
+     * @param seed initial seed value
+     * @return 32 bit hash of the given array
+     */
+    public static int hash32(final byte[] data, int length, int seed) {
+        // 'm' and 'r' are mixing constants generated offline.
+        // They're not really 'magic', they just happen to work well.
+        final int m = 0x5bd1e995;
+        final int r = 24;
+        // Initialize the hash to a random value
+        int h = seed ^ length;
+        int length4 = length / 4;
+
+        for (int i = 0; i < length4; i++) {
+            final int i4 = i * 4;
+            int k = (data[i4 + 0] & 0xff) + ((data[i4 + 1] & 0xff) << 8)
+                    + ((data[i4 + 2] & 0xff) << 16) + ((data[i4 + 3] & 0xff) << 24);
+            k *= m;
+            k ^= k >>> r;
+            k *= m;
+            h *= m;
+            h ^= k;
+        }
+
+        // Handle the last few bytes of the input array
+        switch (length % 4) {
+            case 3:
+                h ^= (data[(length & ~3) + 2] & 0xff) << 16;
+            case 2:
+                h ^= (data[(length & ~3) + 1] & 0xff) << 8;
+            case 1:
+                h ^= (data[length & ~3] & 0xff);
+                h *= m;
+        }
+
+        h ^= h >>> 13;
+        h *= m;
+        h ^= h >>> 15;
+
+        return h;
+    }
+
+    /** Generates 32 bit hash from byte array with default seed value.
+     *
+     * @param data byte array to hash
+     * @param length length of the array to hash
+     * @return 32 bit hash of the given array
+     */
+    public static int hash32(final byte[] data, int length) {
+        return hash32(data, length, 0x9747b28c);
+    }
+
+    /** Generates 32 bit hash from a string.
+     *
+     * @param text string to hash
+     * @return 32 bit hash of the given string
+     */
+    public static int hash32(final String text) {
+        final byte[] bytes = text.getBytes();
+        return hash32(bytes, bytes.length);
+    }
+
+    /** Generates 32 bit hash from a substring.
+     *
+     * @param text string to hash
+     * @param from starting index
+     * @param length length of the substring to hash
+     * @return 32 bit hash of the given string
+     */
+    public static int hash32(final String text, int from, int length) {
+        return hash32(text.substring(from, from + length));
+    }
+
+    /** Generates 64 bit hash from byte array of the given length and seed.
+     *
+     * @param data byte array to hash
+     * @param length length of the array to hash
+     * @param seed initial seed value
+     * @return 64 bit hash of the given array
+     */
+    public static long hash64(final byte[] data, int length, int seed) {
+        final long m = 0xc6a4a7935bd1e995L;
+        final int r = 47;
+
+        long h = (seed & 0xffffffffl) ^ (length * m);
+
+        int length8 = length / 8;
+
+        for (int i = 0; i < length8; i++) {
+            final int i8 = i * 8;
+            long k = ((long) data[i8 + 0] & 0xff) + (((long) data[i8 + 1] & 0xff) << 8)
+                    + (((long) data[i8 + 2] & 0xff) << 16) + (((long) data[i8 + 3] & 0xff) << 24)
+                    + (((long) data[i8 + 4] & 0xff) << 32) + (((long) data[i8 + 5] & 0xff) << 40)
+                    + (((long) data[i8 + 6] & 0xff) << 48) + (((long) data[i8 + 7] & 0xff) << 56);
+
+            k *= m;
+            k ^= k >>> r;
+            k *= m;
+
+            h ^= k;
+            h *= m;
+        }
+
+        switch (length % 8) {
+            case 7:
+                h ^= (long) (data[(length & ~7) + 6] & 0xff) << 48;
+            case 6:
+                h ^= (long) (data[(length & ~7) + 5] & 0xff) << 40;
+            case 5:
+                h ^= (long) (data[(length & ~7) + 4] & 0xff) << 32;
+            case 4:
+                h ^= (long) (data[(length & ~7) + 3] & 0xff) << 24;
+            case 3:
+                h ^= (long) (data[(length & ~7) + 2] & 0xff) << 16;
+            case 2:
+                h ^= (long) (data[(length & ~7) + 1] & 0xff) << 8;
+            case 1:
+                h ^= (long) (data[length & ~7] & 0xff);
+                h *= m;
+        }
+        ;
+
+        h ^= h >>> r;
+        h *= m;
+        h ^= h >>> r;
+
+        return h;
+    }
+
+    /** Generates 64 bit hash from byte array with default seed value.
+     *
+     * @param data byte array to hash
+     * @param length length of the array to hash
+     * @return 64 bit hash of the given string
+     */
+    public static long hash64(final byte[] data, int length) {
+        return hash64(data, length, 0xe17a1465);
+    }
+
+    /** Generates 64 bit hash from a string.
+     *
+     * @param text string to hash
+     * @return 64 bit hash of the given string
+     */
+    public static long hash64(final String text) {
+        final byte[] bytes = text.getBytes();
+        return hash64(bytes, bytes.length);
+    }
+
+    /** Generates 64 bit hash from a substring.
+     *
+     * @param text string to hash
+     * @param from starting index
+     * @param length length of the substring to hash
+     * @return 64 bit hash of the given array
+     */
+    public static long hash64(final String text, int from, int length) {
+        return hash64(text.substring(from, from + length));
+    }
+}
diff --git a/vlc-android/src/org/videolan/vlc/gui/audio/AudioUtil.java b/vlc-android/src/org/videolan/vlc/gui/audio/AudioUtil.java
index 00807eb..b21f0b9 100644
--- a/vlc-android/src/org/videolan/vlc/gui/audio/AudioUtil.java
+++ b/vlc-android/src/org/videolan/vlc/gui/audio/AudioUtil.java
@@ -19,13 +19,18 @@
  *****************************************************************************/
 package org.videolan.vlc.gui.audio;
 
+import java.io.BufferedOutputStream;
 import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
 import java.io.UnsupportedEncodingException;
 import java.math.BigInteger;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 
 import org.videolan.vlc.Media;
+import org.videolan.vlc.MurmurHash;
 import org.videolan.vlc.R;
 import org.videolan.vlc.Util;
 import org.videolan.vlc.VLCApplication;
@@ -37,13 +42,17 @@ import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
 import android.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
 import android.graphics.BitmapFactory;
 import android.media.RingtoneManager;
 import android.net.Uri;
 import android.provider.MediaStore;
+import android.util.Log;
 
 public class AudioUtil {
 
+    public final static String TAG = "VLC/AudioUtil";
+
     public static void setRingtone( Media song, Activity activity){
         File newringtone = Util.URItoFile(song.getLocation());
         ContentValues values = new ContentValues();
@@ -138,9 +147,20 @@ public class AudioUtil {
         return null;
     }
 
-    public static Bitmap getCover(Context context, Media media, int width) {
+    @SuppressLint("SdCardPath")
+    public synchronized static Bitmap getCover(Context context, Media media, int width) {
         Bitmap cover = null;
+        String cachePath = null;
+
         try {
+            // try to load from cache
+            int hash = MurmurHash.hash32(media.getArtist()+media.getAlbum());
+            cachePath = "/sdcard/Android/data/org.videolan.vlc/cache/covers/" +
+                        (hash >= 0 ? "" + hash : "m" + (-hash)) + "_" + width;
+            cover = readBitmap(cachePath);
+            if (cover != null)
+                return cover;
+
             // try to get the cover from android MediaStore
             cover = getCoverFromMediaStore(context, media);
 
@@ -155,8 +175,35 @@ public class AudioUtil {
             //scale down if requested
             if (cover != null && width > 0)
                 cover = Util.scaleDownBitmap(context, cover, width);
+
+            //store cover into cache
+            if (cover != null)
+                writeBitmap(cover, cachePath);
+
         } catch (Exception e) {
+            e.printStackTrace();
         }
         return cover;
     }
+
+    private static void writeBitmap(Bitmap bitmap, String path) throws IOException {
+        OutputStream out = null;
+        try {
+            File file = new File(path);
+            out = new BufferedOutputStream(new FileOutputStream(file), 4096);
+            bitmap.compress(CompressFormat.JPEG, 90, out);
+        } catch (Exception e) {
+            Log.e(TAG, "writeBitmap failed : "+ e.getMessage());
+        } finally {
+            if (out != null) {
+                out.close();
+            }
+        }
+    }
+
+    private static Bitmap readBitmap(String path) {
+        File file = new File(path);
+        if (file == null || !file.exists()) return null;
+        return BitmapFactory.decodeFile(path);
+    }
 }



More information about the Android mailing list