diff --git a/src/musikdroid/app/src/main/java/io/casey/musikcube/remote/offline/OfflineDb.java b/src/musikdroid/app/src/main/java/io/casey/musikcube/remote/offline/OfflineDb.java index 8ff8008ed..1ae0a9153 100644 --- a/src/musikdroid/app/src/main/java/io/casey/musikcube/remote/offline/OfflineDb.java +++ b/src/musikdroid/app/src/main/java/io/casey/musikcube/remote/offline/OfflineDb.java @@ -44,7 +44,7 @@ public abstract class OfflineDb extends RoomDatabase { List toDelete = new ArrayList<>(); for (final String uri : uris) { - if (!StreamProxy.isCached(uri)) { + if (!StreamProxy.Companion.isCached(uri)) { toDelete.add(uri); } } diff --git a/src/musikdroid/app/src/main/java/io/casey/musikcube/remote/playback/ExoPlayerWrapper.kt b/src/musikdroid/app/src/main/java/io/casey/musikcube/remote/playback/ExoPlayerWrapper.kt index 06619d081..afb3dd1d0 100644 --- a/src/musikdroid/app/src/main/java/io/casey/musikcube/remote/playback/ExoPlayerWrapper.kt +++ b/src/musikdroid/app/src/main/java/io/casey/musikcube/remote/playback/ExoPlayerWrapper.kt @@ -5,6 +5,7 @@ import android.content.SharedPreferences import android.net.Uri import android.util.Base64 import android.util.Log +import com.danikula.videocache.CacheListener import com.google.android.exoplayer2.* import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSourceFactory import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory @@ -61,7 +62,7 @@ class ExoPlayerWrapper : PlayerWrapper() { } val builder = OkHttpClient.Builder() - .cache(Cache(path, CACHE_SETTING_TO_BYTES[diskCacheIndex] ?: MINIMUM_CACHE_SIZE_BYTES)) + .cache(Cache(path, StreamProxy.CACHE_SETTING_TO_BYTES[diskCacheIndex] ?: StreamProxy.MINIMUM_CACHE_SIZE_BYTES)) .addInterceptor { chain -> var request = chain.request() val userPass = "default:" + prefs.getString(Prefs.Key.PASSWORD, Prefs.Default.PASSWORD)!! @@ -110,7 +111,7 @@ class ExoPlayerWrapper : PlayerWrapper() { this.metadata = metadata this.originalUri = uri - this.proxyUri = StreamProxy.getProxyUrl(context, uri) + this.proxyUri = StreamProxy.getProxyUrl(uri) Log.d("ExoPlayerWrapper", "originalUri: ${this.originalUri} proxyUri: ${this.proxyUri}") addCacheListener() @@ -131,7 +132,7 @@ class ExoPlayerWrapper : PlayerWrapper() { this.metadata = metadata this.originalUri = uri - this.proxyUri = StreamProxy.getProxyUrl(context, uri) + this.proxyUri = StreamProxy.getProxyUrl(uri) Log.d("ExoPlayerWrapper", "originalUri: ${this.originalUri} proxyUri: ${this.proxyUri}") this.prefetch = true @@ -251,7 +252,7 @@ class ExoPlayerWrapper : PlayerWrapper() { private fun addCacheListener() { if (StreamProxy.ENABLED) { - if (StreamProxy.isCached(this.originalUri)) { + if (StreamProxy.isCached(this.originalUri!!)) { percentAvailable = 100 if (originalUri != null && metadata != null) { @@ -259,7 +260,7 @@ class ExoPlayerWrapper : PlayerWrapper() { } } else { - StreamProxy.registerCacheListener(this.cacheListener, this.originalUri) + StreamProxy.registerCacheListener(this.cacheListener, this.originalUri!!) } } else { @@ -273,13 +274,13 @@ class ExoPlayerWrapper : PlayerWrapper() { } } - private val cacheListener = { _: File, _: String, percent: Int -> + private val cacheListener = CacheListener { _: File, _: String, percent: Int -> //Log.e("CLCLCL", String.format("%d", percent)); percentAvailable = percent if (percentAvailable >= 100) { if (originalUri != null && metadata != null) { - PlayerWrapper.storeOffline(originalUri!!, metadata!!) + storeOffline(originalUri!!, metadata!!) } } } diff --git a/src/musikdroid/app/src/main/java/io/casey/musikcube/remote/playback/MediaPlayerWrapper.kt b/src/musikdroid/app/src/main/java/io/casey/musikcube/remote/playback/MediaPlayerWrapper.kt index 6c01b0faf..400a7d816 100644 --- a/src/musikdroid/app/src/main/java/io/casey/musikcube/remote/playback/MediaPlayerWrapper.kt +++ b/src/musikdroid/app/src/main/java/io/casey/musikcube/remote/playback/MediaPlayerWrapper.kt @@ -45,7 +45,7 @@ class MediaPlayerWrapper : PlayerWrapper() { this.metadata = metadata this.originalUri = uri - this.proxyUri = StreamProxy.getProxyUrl(context, uri) + this.proxyUri = StreamProxy.getProxyUrl(uri) player.setDataSource(context, Uri.parse(proxyUri), headers) player.setAudioStreamType(AudioManager.STREAM_MUSIC) diff --git a/src/musikdroid/app/src/main/java/io/casey/musikcube/remote/playback/StreamProxy.java b/src/musikdroid/app/src/main/java/io/casey/musikcube/remote/playback/StreamProxy.java deleted file mode 100644 index 3b1ac3765..000000000 --- a/src/musikdroid/app/src/main/java/io/casey/musikcube/remote/playback/StreamProxy.java +++ /dev/null @@ -1,124 +0,0 @@ -package io.casey.musikcube.remote.playback; - -import android.content.Context; -import android.content.SharedPreferences; -import android.net.Uri; -import android.util.Base64; - -import com.danikula.videocache.CacheListener; -import com.danikula.videocache.HttpProxyCacheServer; -import com.danikula.videocache.file.FileNameGenerator; -import com.danikula.videocache.file.Md5FileNameGenerator; - -import java.io.File; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import io.casey.musikcube.remote.util.NetworkUtil; -import io.casey.musikcube.remote.websocket.Prefs; - -public class StreamProxy { - public static final boolean ENABLED = true; - public static final long BYTES_PER_MEGABYTE = 1048576L; - public static final long BYTES_PER_GIGABYTE = 1073741824L; - public static final long MINIMUM_CACHE_SIZE_BYTES = BYTES_PER_MEGABYTE * 32; - public static final Map CACHE_SETTING_TO_BYTES; - private static final FileNameGenerator DEFAULT_FILENAME_GENERATOR = new Md5FileNameGenerator(); - - static { - CACHE_SETTING_TO_BYTES = new HashMap<>(); - CACHE_SETTING_TO_BYTES.put(0, MINIMUM_CACHE_SIZE_BYTES); - CACHE_SETTING_TO_BYTES.put(1, BYTES_PER_GIGABYTE / 2); - CACHE_SETTING_TO_BYTES.put(2, BYTES_PER_GIGABYTE); - CACHE_SETTING_TO_BYTES.put(3, BYTES_PER_GIGABYTE * 2); - CACHE_SETTING_TO_BYTES.put(4, BYTES_PER_GIGABYTE * 3); - CACHE_SETTING_TO_BYTES.put(5, BYTES_PER_GIGABYTE * 4); - } - - private static StreamProxy INSTANCE; - - private HttpProxyCacheServer proxy; - private SharedPreferences prefs; - - private StreamProxy(final Context context) { - prefs = context.getSharedPreferences(Prefs.NAME, Context.MODE_PRIVATE); - - if (this.prefs.getBoolean(Prefs.Key.CERT_VALIDATION_DISABLED, Prefs.Default.CERT_VALIDATION_DISABLED)) { - NetworkUtil.disableCertificateValidation(); - } - else { - NetworkUtil.enableCertificateValidation(); - } - - int diskCacheIndex = this.prefs.getInt( - Prefs.Key.DISK_CACHE_SIZE_INDEX, Prefs.Default.DISK_CACHE_SIZE_INDEX); - - if (diskCacheIndex < 0 || diskCacheIndex > CACHE_SETTING_TO_BYTES.size()) { - diskCacheIndex = 0; - } - - final File cachePath = new File(context.getExternalCacheDir(), "audio"); - - proxy = new HttpProxyCacheServer.Builder(context.getApplicationContext()) - .cacheDirectory(cachePath) - .maxCacheSize(CACHE_SETTING_TO_BYTES.get(diskCacheIndex)) - .headerInjector((url) -> { - Map headers = new HashMap<>(); - final String userPass = "default:" + prefs.getString(Prefs.Key.PASSWORD, Prefs.Default.PASSWORD); - final String encoded = Base64.encodeToString(userPass.getBytes(), Base64.NO_WRAP); - headers.put("Authorization", "Basic " + encoded); - return headers; - }) - .fileNameGenerator((url) -> { - try { - final Uri uri = Uri.parse(url); - /* format is: audio/external_id/ */ - final List segments = uri.getPathSegments(); - if (segments.size() == 3 && "external_id".equals(segments.get(1))) { - return segments.get(2); /* id, should be globally unique. */ - } - } - catch (Exception ex) { - /* eh... */ - } - return DEFAULT_FILENAME_GENERATOR.generate(url); - }) - .build(); - } - - public static synchronized void init(final Context context) { - if (INSTANCE == null) { - INSTANCE = new StreamProxy(context.getApplicationContext()); - } - } - - public static synchronized void registerCacheListener(final CacheListener cl, final String uri) { - if (INSTANCE != null && cl != null) { - INSTANCE.proxy.registerCacheListener(cl, uri); /* let it throw */ - } - } - - public static synchronized void unregisterCacheListener(final CacheListener cl) { - if (INSTANCE != null && cl != null) { - INSTANCE.proxy.unregisterCacheListener(cl); - } - } - - public static synchronized boolean isCached(final String url) { - return INSTANCE != null && INSTANCE.proxy.isCached(url); - } - - public static synchronized String getProxyUrl(final Context context, final String url) { - init(context); - return ENABLED ? INSTANCE.proxy.getProxyUrl(url) : url; - } - - public static synchronized void reload() { - if (INSTANCE != null) { - INSTANCE.proxy.shutdown(); - INSTANCE = null; - } - } - -} diff --git a/src/musikdroid/app/src/main/java/io/casey/musikcube/remote/playback/StreamProxy.kt b/src/musikdroid/app/src/main/java/io/casey/musikcube/remote/playback/StreamProxy.kt new file mode 100644 index 000000000..0f3b8972c --- /dev/null +++ b/src/musikdroid/app/src/main/java/io/casey/musikcube/remote/playback/StreamProxy.kt @@ -0,0 +1,133 @@ +package io.casey.musikcube.remote.playback + +import android.content.Context +import android.content.SharedPreferences +import android.net.Uri +import android.util.Base64 +import com.danikula.videocache.CacheListener +import com.danikula.videocache.HttpProxyCacheServer +import com.danikula.videocache.file.Md5FileNameGenerator +import io.casey.musikcube.remote.Application +import io.casey.musikcube.remote.util.NetworkUtil +import io.casey.musikcube.remote.util.Strings +import io.casey.musikcube.remote.websocket.Prefs +import java.io.File +import java.util.* + +class StreamProxy private constructor(context: Context) { + private val proxy: HttpProxyCacheServer + private val prefs: SharedPreferences + + init { + prefs = context.getSharedPreferences(Prefs.NAME, Context.MODE_PRIVATE) + + if (this.prefs.getBoolean(Prefs.Key.CERT_VALIDATION_DISABLED, Prefs.Default.CERT_VALIDATION_DISABLED)) { + NetworkUtil.disableCertificateValidation() + } + else { + NetworkUtil.enableCertificateValidation() + } + + var diskCacheIndex = this.prefs.getInt( + Prefs.Key.DISK_CACHE_SIZE_INDEX, Prefs.Default.DISK_CACHE_SIZE_INDEX) + + if (diskCacheIndex < 0 || diskCacheIndex > CACHE_SETTING_TO_BYTES.size) { + diskCacheIndex = 0 + } + + val cachePath = File(context.externalCacheDir, "audio") + + proxy = HttpProxyCacheServer.Builder(context.applicationContext) + .cacheDirectory(cachePath) + .maxCacheSize(CACHE_SETTING_TO_BYTES[diskCacheIndex] ?: MINIMUM_CACHE_SIZE_BYTES) + .headerInjector { url -> + val headers = HashMap() + val userPass = "default:" + prefs.getString(Prefs.Key.PASSWORD, Prefs.Default.PASSWORD)!! + val encoded = Base64.encodeToString(userPass.toByteArray(), Base64.NO_WRAP) + headers.put("Authorization", "Basic " + encoded) + headers + } + .fileNameGenerator { url -> + try { + val uri = Uri.parse(url) + /* format is: audio/external_id/ */ + val segments = uri.pathSegments + if (segments.size == 3 && "external_id" == segments[1]) { + /* url params, hyphen separated */ + var params = uri.query + if (Strings.notEmpty(params)) { + params = "-" + params + .replace("?", "-") + .replace("&", "-") + .replace("=", "-") + } + else { + params = "" + } + + "${segments[2]}-$params" + } + } catch (ex: Exception) { + /* eh... */ + } + + DEFAULT_FILENAME_GENERATOR.generate(url) + } + .build() + } + + companion object { + val ENABLED = true + val BYTES_PER_MEGABYTE = 1048576L + val BYTES_PER_GIGABYTE = 1073741824L + val MINIMUM_CACHE_SIZE_BYTES = BYTES_PER_MEGABYTE * 128 + val CACHE_SETTING_TO_BYTES: MutableMap + private val DEFAULT_FILENAME_GENERATOR = Md5FileNameGenerator() + + init { + CACHE_SETTING_TO_BYTES = HashMap() + CACHE_SETTING_TO_BYTES.put(0, MINIMUM_CACHE_SIZE_BYTES) + CACHE_SETTING_TO_BYTES.put(1, BYTES_PER_GIGABYTE / 2) + CACHE_SETTING_TO_BYTES.put(2, BYTES_PER_GIGABYTE) + CACHE_SETTING_TO_BYTES.put(3, BYTES_PER_GIGABYTE * 2) + CACHE_SETTING_TO_BYTES.put(4, BYTES_PER_GIGABYTE * 3) + CACHE_SETTING_TO_BYTES.put(5, BYTES_PER_GIGABYTE * 4) + } + + private var INSTANCE: StreamProxy? = null + + @Synchronized fun init(context: Context) { + if (INSTANCE == null) { + INSTANCE = StreamProxy(context.applicationContext) + } + } + + @Synchronized fun registerCacheListener(cl: CacheListener, uri: String) { + if (INSTANCE != null) { + INSTANCE!!.proxy.registerCacheListener(cl, uri) /* let it throw */ + } + } + + @Synchronized fun unregisterCacheListener(cl: CacheListener) { + if (INSTANCE != null) { + INSTANCE!!.proxy.unregisterCacheListener(cl) + } + } + + @Synchronized fun isCached(url: String): Boolean { + return INSTANCE != null && INSTANCE!!.proxy.isCached(url) + } + + @Synchronized fun getProxyUrl(url: String): String { + init(Application.instance!!) + return if (ENABLED) INSTANCE!!.proxy.getProxyUrl(url) else url + } + + @Synchronized fun reload() { + if (INSTANCE != null) { + INSTANCE!!.proxy.shutdown() + INSTANCE = null + } + } + } +} diff --git a/src/musikdroid/app/src/main/java/io/casey/musikcube/remote/ui/activity/SettingsActivity.java b/src/musikdroid/app/src/main/java/io/casey/musikcube/remote/ui/activity/SettingsActivity.java index ee391578c..6a36fc92b 100644 --- a/src/musikdroid/app/src/main/java/io/casey/musikcube/remote/ui/activity/SettingsActivity.java +++ b/src/musikdroid/app/src/main/java/io/casey/musikcube/remote/ui/activity/SettingsActivity.java @@ -211,7 +211,7 @@ public class SettingsActivity extends AppCompatActivity { PlaybackServiceFactory.streaming(this).stop(); } - StreamProxy.reload(); + StreamProxy.Companion.reload(); WebSocketService.getInstance(this).disconnect(); finish();