Fixed StreamProxy filename generation to take url params into account.

Also converted StreamProxy to kotlin.
This commit is contained in:
casey langen 2017-06-13 10:08:35 -07:00
parent 8a04959d6b
commit 1d8cfd8c7c
6 changed files with 144 additions and 134 deletions

View File

@ -44,7 +44,7 @@ public abstract class OfflineDb extends RoomDatabase {
List<String> toDelete = new ArrayList<>();
for (final String uri : uris) {
if (!StreamProxy.isCached(uri)) {
if (!StreamProxy.Companion.isCached(uri)) {
toDelete.add(uri);
}
}

View File

@ -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!!)
}
}
}

View File

@ -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)

View File

@ -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<Integer, Long> 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<String, String> 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/<id> */
final List<String> 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;
}
}
}

View File

@ -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<String, String>()
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/<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<Int, Long>
private val DEFAULT_FILENAME_GENERATOR = Md5FileNameGenerator()
init {
CACHE_SETTING_TO_BYTES = HashMap<Int, Long>()
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
}
}
}
}

View File

@ -211,7 +211,7 @@ public class SettingsActivity extends AppCompatActivity {
PlaybackServiceFactory.streaming(this).stop();
}
StreamProxy.reload();
StreamProxy.Companion.reload();
WebSocketService.getInstance(this).disconnect();
finish();