mirror of
https://github.com/clangen/musikcube.git
synced 2025-01-29 21:32:41 +00:00
More musikdroid
client work:
1. fixed bugs around abusing the StreamingPlaybackService::next() 2. added album art to MediaSessionCompat implementation 3. minor refactors to the way AlbumArtModel works 4. added constants for preferences keys and defaults
This commit is contained in:
parent
f57881c88e
commit
a67e05dcd2
@ -21,6 +21,7 @@ import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.bumptech.glide.load.resource.drawable.GlideDrawable;
|
||||
import com.bumptech.glide.request.RequestListener;
|
||||
import com.bumptech.glide.request.target.Target;
|
||||
@ -47,6 +48,7 @@ import io.casey.musikcube.remote.ui.util.Views;
|
||||
import io.casey.musikcube.remote.ui.view.LongPressTextView;
|
||||
import io.casey.musikcube.remote.util.Strings;
|
||||
import io.casey.musikcube.remote.websocket.Messages;
|
||||
import io.casey.musikcube.remote.websocket.Prefs;
|
||||
import io.casey.musikcube.remote.websocket.SocketMessage;
|
||||
import io.casey.musikcube.remote.websocket.WebSocketService;
|
||||
|
||||
@ -70,7 +72,7 @@ public class MainActivity extends WebSocketActivityBase {
|
||||
private enum DisplayMode { Artwork, NoArtwork, Stopped }
|
||||
private View mainTrackMetadataWithAlbumArt, mainTrackMetadataNoAlbumArt;
|
||||
private ViewPropertyAnimator metadataAnim1, metadataAnim2;
|
||||
private AlbumArtModel albumArtModel = new AlbumArtModel();
|
||||
private AlbumArtModel albumArtModel = AlbumArtModel.empty();
|
||||
private ImageView albumArtImageView;
|
||||
|
||||
static {
|
||||
@ -89,7 +91,7 @@ public class MainActivity extends WebSocketActivityBase {
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
this.prefs = this.getSharedPreferences("prefs", Context.MODE_PRIVATE);
|
||||
this.prefs = this.getSharedPreferences(Prefs.NAME, Context.MODE_PRIVATE);
|
||||
this.wss = getWebSocketService();
|
||||
this.playback = getPlaybackService();
|
||||
|
||||
@ -325,7 +327,9 @@ public class MainActivity extends WebSocketActivityBase {
|
||||
/* state management for UI stuff is starting to get out of hand. we should
|
||||
refactor things pretty soon before they're completely out of control */
|
||||
|
||||
final boolean streaming = prefs.getBoolean("streaming_playback", false);
|
||||
final boolean streaming = prefs.getBoolean(
|
||||
Prefs.Key.STREAMING_PLAYBACK, Prefs.Default.STREAMING_PLAYBACK);
|
||||
|
||||
final WebSocketService.State state = wss.getState();
|
||||
|
||||
final boolean connected = state == WebSocketService.State.Connected;
|
||||
@ -382,17 +386,20 @@ public class MainActivity extends WebSocketActivityBase {
|
||||
Views.setCheckWithoutEvent(this.shuffleCb, playback.isShuffled(), this.shuffleListener);
|
||||
Views.setCheckWithoutEvent(this.muteCb, playback.isMuted(), this.muteListener);
|
||||
|
||||
boolean albumArtEnabledInSettings = this.prefs.getBoolean("album_art_enabled", true);
|
||||
boolean albumArtEnabledInSettings = this.prefs.getBoolean(
|
||||
Prefs.Key.ALBUM_ART_ENABLED, Prefs.Default.ALBUM_ART_ENABLED);
|
||||
|
||||
if (stateIsValidForArtwork) {
|
||||
if (!albumArtEnabledInSettings || Strings.empty(artist) || Strings.empty(album)) {
|
||||
this.albumArtModel = new AlbumArtModel();
|
||||
this.albumArtModel = AlbumArtModel.empty();
|
||||
setMetadataDisplayMode(DisplayMode.NoArtwork);
|
||||
}
|
||||
else {
|
||||
if (!this.albumArtModel.is(artist, album)) {
|
||||
this.albumArtModel.destroy();
|
||||
this.albumArtModel = new AlbumArtModel(title, artist, album, albumArtRetrieved);
|
||||
|
||||
this.albumArtModel = new AlbumArtModel(
|
||||
title, artist, album, AlbumArtModel.Size.Mega, albumArtRetrieved);
|
||||
}
|
||||
updateAlbumArt();
|
||||
}
|
||||
@ -400,7 +407,7 @@ public class MainActivity extends WebSocketActivityBase {
|
||||
}
|
||||
|
||||
private void clearUi() {
|
||||
albumArtModel = new AlbumArtModel();
|
||||
albumArtModel = AlbumArtModel.empty();
|
||||
updateAlbumArt();
|
||||
rebindUi();
|
||||
}
|
||||
@ -442,7 +449,7 @@ public class MainActivity extends WebSocketActivityBase {
|
||||
final String album = track.optString(Metadata.Track.ALBUM, "");
|
||||
|
||||
if (!albumArtModel.is(artist, album)) {
|
||||
new AlbumArtModel("", artist, album, (info, url) -> {
|
||||
new AlbumArtModel("", artist, album, AlbumArtModel.Size.Mega, (info, url) -> {
|
||||
int width = albumArtImageView.getWidth();
|
||||
int height = albumArtImageView.getHeight();
|
||||
Glide.with(MainActivity.this).load(url).downloadOnly(width, height);
|
||||
@ -468,6 +475,7 @@ public class MainActivity extends WebSocketActivityBase {
|
||||
|
||||
Glide.with(this)
|
||||
.load(url)
|
||||
.diskCacheStrategy(DiskCacheStrategy.ALL)
|
||||
.listener(new RequestListener<String, GlideDrawable>() {
|
||||
@Override
|
||||
public boolean onException(Exception e,
|
||||
@ -576,12 +584,7 @@ public class MainActivity extends WebSocketActivityBase {
|
||||
}
|
||||
};
|
||||
|
||||
private PlaybackService.EventListener playbackEvents = new PlaybackService.EventListener() {
|
||||
@Override
|
||||
public void onStateUpdated() {
|
||||
rebindUi();
|
||||
}
|
||||
};
|
||||
private PlaybackService.EventListener playbackEvents = () -> rebindUi();
|
||||
|
||||
private WebSocketService.Client serviceClient = new WebSocketService.Client() {
|
||||
@Override
|
||||
|
@ -3,6 +3,7 @@ package io.casey.musikcube.remote.playback;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||
import com.google.android.exoplayer2.ExoPlayer;
|
||||
@ -33,6 +34,8 @@ import java.util.Map;
|
||||
|
||||
import io.casey.musikcube.remote.Application;
|
||||
import io.casey.musikcube.remote.util.NetworkUtil;
|
||||
import io.casey.musikcube.remote.util.Preconditions;
|
||||
import io.casey.musikcube.remote.websocket.Prefs;
|
||||
import okhttp3.Cache;
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
@ -70,7 +73,7 @@ public class ExoPlayerWrapper extends PlayerWrapper {
|
||||
|
||||
public ExoPlayerWrapper() {
|
||||
this.context = Application.getInstance();
|
||||
this.prefs = context.getSharedPreferences("prefs", Context.MODE_PRIVATE);
|
||||
this.prefs = context.getSharedPreferences(Prefs.NAME, Context.MODE_PRIVATE);
|
||||
this.bandwidth = new DefaultBandwidthMeter();
|
||||
final TrackSelection.Factory trackFactory = new AdaptiveTrackSelection.Factory(bandwidth);
|
||||
final TrackSelector trackSelector = new DefaultTrackSelector(trackFactory);
|
||||
@ -86,7 +89,9 @@ public class ExoPlayerWrapper extends PlayerWrapper {
|
||||
if (audioStreamHttpClient == null) {
|
||||
final File path = new File(context.getExternalCacheDir(), "audio");
|
||||
|
||||
int diskCacheIndex = this.prefs.getInt("disk_cache_size_index", 0);
|
||||
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;
|
||||
}
|
||||
@ -94,7 +99,7 @@ public class ExoPlayerWrapper extends PlayerWrapper {
|
||||
OkHttpClient.Builder builder = new OkHttpClient.Builder()
|
||||
.cache(new Cache(path, CACHE_SETTING_TO_BYTES.get(diskCacheIndex)));
|
||||
|
||||
if (this.prefs.getBoolean("cert_validation_disabled", false)) {
|
||||
if (this.prefs.getBoolean(Prefs.Key.CERT_VALIDATION_DISABLED, Prefs.Default.CERT_VALIDATION_DISABLED)) {
|
||||
NetworkUtil.disableCertificateValidation(builder);
|
||||
}
|
||||
|
||||
@ -116,27 +121,37 @@ public class ExoPlayerWrapper extends PlayerWrapper {
|
||||
|
||||
@Override
|
||||
public void play(String uri) {
|
||||
initHttpClient(uri);
|
||||
this.source = new ExtractorMediaSource(Uri.parse(uri), datasources, extractors, null, null);
|
||||
this.player.setPlayWhenReady(true);
|
||||
this.player.prepare(this.source);
|
||||
addActivePlayer(this);
|
||||
setState(State.Preparing);
|
||||
Preconditions.throwIfNotOnMainThread();
|
||||
|
||||
if (!dead()) {
|
||||
initHttpClient(uri);
|
||||
this.source = new ExtractorMediaSource(Uri.parse(uri), datasources, extractors, null, null);
|
||||
this.player.setPlayWhenReady(true);
|
||||
this.player.prepare(this.source);
|
||||
addActivePlayer(this);
|
||||
setState(State.Preparing);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prefetch(String uri) {
|
||||
initHttpClient(uri);
|
||||
this.prefetch = true;
|
||||
this.source = new ExtractorMediaSource(Uri.parse(uri), datasources, extractors, null, null);
|
||||
this.player.setPlayWhenReady(false);
|
||||
this.player.prepare(this.source);
|
||||
addActivePlayer(this);
|
||||
setState(State.Preparing);
|
||||
Preconditions.throwIfNotOnMainThread();
|
||||
|
||||
if (!dead()) {
|
||||
initHttpClient(uri);
|
||||
this.prefetch = true;
|
||||
this.source = new ExtractorMediaSource(Uri.parse(uri), datasources, extractors, null, null);
|
||||
this.player.setPlayWhenReady(false);
|
||||
this.player.prepare(this.source);
|
||||
addActivePlayer(this);
|
||||
setState(State.Preparing);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pause() {
|
||||
Preconditions.throwIfNotOnMainThread();
|
||||
|
||||
this.prefetch = true;
|
||||
|
||||
if (this.getState() == State.Playing) {
|
||||
@ -147,6 +162,8 @@ public class ExoPlayerWrapper extends PlayerWrapper {
|
||||
|
||||
@Override
|
||||
public void resume() {
|
||||
Preconditions.throwIfNotOnMainThread();
|
||||
|
||||
if (this.getState() == State.Paused || this.getState() == State.Prepared) {
|
||||
this.player.setPlayWhenReady(true);
|
||||
setState(State.Playing);
|
||||
@ -157,6 +174,8 @@ public class ExoPlayerWrapper extends PlayerWrapper {
|
||||
|
||||
@Override
|
||||
public void setPosition(int millis) {
|
||||
Preconditions.throwIfNotOnMainThread();
|
||||
|
||||
if (this.player.getPlaybackState() != ExoPlayer.STATE_IDLE) {
|
||||
this.player.seekTo(millis);
|
||||
}
|
||||
@ -164,33 +183,43 @@ public class ExoPlayerWrapper extends PlayerWrapper {
|
||||
|
||||
@Override
|
||||
public int getPosition() {
|
||||
Preconditions.throwIfNotOnMainThread();
|
||||
|
||||
return (int) this.player.getCurrentPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDuration() {
|
||||
Preconditions.throwIfNotOnMainThread();
|
||||
|
||||
return (int) this.player.getDuration();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateVolume() {
|
||||
Preconditions.throwIfNotOnMainThread();
|
||||
|
||||
this.player.setVolume(getGlobalVolume());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNextMediaPlayer(PlayerWrapper wrapper) {
|
||||
|
||||
Preconditions.throwIfNotOnMainThread();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
if (getState() != State.Disposed) {
|
||||
removeActivePlayer(this);
|
||||
setState(State.Killing);
|
||||
Preconditions.throwIfNotOnMainThread();
|
||||
|
||||
setState(State.Killing);
|
||||
removeActivePlayer(this);
|
||||
if (this.player != null) {
|
||||
this.player.setPlayWhenReady(false);
|
||||
this.player.removeListener(eventListener);
|
||||
this.player.stop();
|
||||
this.player.release();
|
||||
setState(State.Disposed);
|
||||
}
|
||||
setState(State.Disposed);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -198,6 +227,11 @@ public class ExoPlayerWrapper extends PlayerWrapper {
|
||||
super.setOnStateChangedListener(listener);
|
||||
}
|
||||
|
||||
private boolean dead() {
|
||||
final State state = getState();
|
||||
return (state == State.Killing || state == State.Disposed);
|
||||
}
|
||||
|
||||
private ExoPlayer.EventListener eventListener = new ExoPlayer.EventListener() {
|
||||
@Override
|
||||
public void onTimelineChanged(Timeline timeline, Object manifest) {
|
||||
@ -216,17 +250,24 @@ public class ExoPlayerWrapper extends PlayerWrapper {
|
||||
|
||||
@Override
|
||||
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
|
||||
Preconditions.throwIfNotOnMainThread();
|
||||
|
||||
if (playbackState == ExoPlayer.STATE_READY) {
|
||||
setState(State.Prepared);
|
||||
|
||||
player.setVolume(getGlobalVolume());
|
||||
|
||||
if (!prefetch) {
|
||||
player.setPlayWhenReady(true);
|
||||
setState(State.Playing);
|
||||
if (dead()) {
|
||||
dispose();
|
||||
}
|
||||
else {
|
||||
setState(State.Paused);
|
||||
setState(State.Prepared);
|
||||
|
||||
player.setVolume(getGlobalVolume());
|
||||
|
||||
if (!prefetch) {
|
||||
player.setPlayWhenReady(true);
|
||||
setState(State.Playing);
|
||||
}
|
||||
else {
|
||||
setState(State.Paused);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (playbackState == ExoPlayer.STATE_ENDED) {
|
||||
@ -237,6 +278,8 @@ public class ExoPlayerWrapper extends PlayerWrapper {
|
||||
|
||||
@Override
|
||||
public void onPlayerError(ExoPlaybackException error) {
|
||||
Preconditions.throwIfNotOnMainThread();
|
||||
|
||||
/* if we're transcoding the size of the response will be inexact, so the player
|
||||
will try to pick up the last few bytes and be left with an HTTP 416. if that happens,
|
||||
and we're towards the end of the track, just move to the next one */
|
||||
|
@ -3,6 +3,8 @@ package io.casey.musikcube.remote.playback;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import io.casey.musikcube.remote.websocket.Prefs;
|
||||
|
||||
public class PlaybackServiceFactory {
|
||||
private static StreamingPlaybackService streaming;
|
||||
private static RemotePlaybackService remote;
|
||||
@ -11,7 +13,7 @@ public class PlaybackServiceFactory {
|
||||
public static synchronized PlaybackService instance(final Context context) {
|
||||
init(context);
|
||||
|
||||
if (prefs.getBoolean("streaming_playback", true)) {
|
||||
if (prefs.getBoolean(Prefs.Key.STREAMING_PLAYBACK, Prefs.Default.STREAMING_PLAYBACK)) {
|
||||
return streaming;
|
||||
}
|
||||
|
||||
@ -30,7 +32,7 @@ public class PlaybackServiceFactory {
|
||||
|
||||
private static void init(final Context context) {
|
||||
if (streaming == null || remote == null || prefs == null) {
|
||||
prefs = context.getSharedPreferences("prefs", Context.MODE_PRIVATE);
|
||||
prefs = context.getSharedPreferences(Prefs.NAME, Context.MODE_PRIVATE);
|
||||
streaming = new StreamingPlaybackService(context);
|
||||
remote = new RemotePlaybackService(context);
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
package io.casey.musikcube.remote.playback;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
|
@ -28,6 +28,7 @@ import io.casey.musikcube.remote.R;
|
||||
import io.casey.musikcube.remote.ui.model.TrackListSlidingWindow;
|
||||
import io.casey.musikcube.remote.util.Strings;
|
||||
import io.casey.musikcube.remote.websocket.Messages;
|
||||
import io.casey.musikcube.remote.websocket.Prefs;
|
||||
import io.casey.musikcube.remote.websocket.SocketMessage;
|
||||
import io.casey.musikcube.remote.websocket.WebSocketService;
|
||||
import io.reactivex.Observable;
|
||||
@ -67,15 +68,10 @@ public class StreamingPlaybackService implements PlaybackService {
|
||||
int currentIndex = -1, nextIndex = -1;
|
||||
boolean nextPlayerScheduled;
|
||||
|
||||
public void stopPlayback() {
|
||||
public void stopPlaybackAndReset() {
|
||||
reset(currentPlayer);
|
||||
reset(nextPlayer);
|
||||
nextPlayerScheduled = false;
|
||||
}
|
||||
|
||||
public void stopPlaybackAndReset() {
|
||||
stopPlayback();
|
||||
this.currentPlayer = this.nextPlayer = null;
|
||||
nextPlayerScheduled = false; this.currentPlayer = this.nextPlayer = null;
|
||||
this.currentMetadata = this.nextMetadata = null;
|
||||
this.currentIndex = this.nextIndex = -1;
|
||||
}
|
||||
@ -156,7 +152,7 @@ public class StreamingPlaybackService implements PlaybackService {
|
||||
|
||||
public StreamingPlaybackService(final Context context) {
|
||||
this.wss = WebSocketService.getInstance(context.getApplicationContext());
|
||||
this.prefs = context.getSharedPreferences("prefs", Context.MODE_PRIVATE);
|
||||
this.prefs = context.getSharedPreferences(Prefs.NAME, Context.MODE_PRIVATE);
|
||||
this.audioManager = (AudioManager) Application.getInstance().getSystemService(Context.AUDIO_SERVICE);
|
||||
this.lastSystemVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
|
||||
this.repeatMode = RepeatMode.from(this.prefs.getString(REPEAT_MODE_PREF, RepeatMode.None.toString()));
|
||||
@ -208,7 +204,7 @@ public class StreamingPlaybackService implements PlaybackService {
|
||||
@Override
|
||||
public void playAt(int index) {
|
||||
if (requestAudioFocus()) {
|
||||
this.context.stopPlayback();
|
||||
this.context.stopPlaybackAndReset();
|
||||
loadQueueAndPlay(this.params, index);
|
||||
}
|
||||
}
|
||||
@ -316,7 +312,7 @@ public class StreamingPlaybackService implements PlaybackService {
|
||||
|
||||
@Override
|
||||
public double getVolume() {
|
||||
if (prefs.getBoolean("software_volume", false)) {
|
||||
if (prefs.getBoolean(Prefs.Key.SOFTWARE_VOLUME, Prefs.Default.SOFTWARE_VOLUME)) {
|
||||
return PlayerWrapper.getGlobalVolume();
|
||||
}
|
||||
|
||||
@ -409,7 +405,7 @@ public class StreamingPlaybackService implements PlaybackService {
|
||||
}
|
||||
|
||||
private float getVolumeStep() {
|
||||
if (prefs.getBoolean("software_volume", false)) {
|
||||
if (prefs.getBoolean(Prefs.Key.SOFTWARE_VOLUME, Prefs.Default.SOFTWARE_VOLUME)) {
|
||||
return 0.1f;
|
||||
}
|
||||
return 1.0f / getMaxSystemVolume();
|
||||
@ -420,7 +416,7 @@ public class StreamingPlaybackService implements PlaybackService {
|
||||
toggleMute();
|
||||
}
|
||||
|
||||
final boolean softwareVolume = prefs.getBoolean("software_volume", false);
|
||||
final boolean softwareVolume = prefs.getBoolean(Prefs.Key.SOFTWARE_VOLUME, Prefs.Default.SOFTWARE_VOLUME);
|
||||
float current = softwareVolume ? PlayerWrapper.getGlobalVolume() : getSystemVolume();
|
||||
|
||||
current += delta;
|
||||
@ -538,11 +534,15 @@ public class StreamingPlaybackService implements PlaybackService {
|
||||
if (track != null) {
|
||||
final String externalId = track.optString("external_id", "");
|
||||
if (Strings.notEmpty(externalId)) {
|
||||
final String protocol = prefs.getBoolean("ssl_enabled", false) ? "https" : "http";
|
||||
final String protocol = prefs.getBoolean(
|
||||
Prefs.Key.SSL_ENABLED, Prefs.Default.SSL_ENABLED) ? "https" : "http";
|
||||
|
||||
/* transcoding bitrate, if selected by the user */
|
||||
String bitrateQueryParam = "";
|
||||
final int bitrateIndex = prefs.getInt("transcoder_bitrate_index", 0);
|
||||
final int bitrateIndex = prefs.getInt(
|
||||
Prefs.Key.TRANSCODER_BITRATE_INDEX,
|
||||
Prefs.Default.TRANSCODER_BITRATE_INDEX);
|
||||
|
||||
if (bitrateIndex > 0) {
|
||||
final Resources r = Application.getInstance().getResources();
|
||||
|
||||
@ -556,8 +556,8 @@ public class StreamingPlaybackService implements PlaybackService {
|
||||
Locale.ENGLISH,
|
||||
"%s://%s:%d/audio/external_id/%s%s",
|
||||
protocol,
|
||||
prefs.getString("address", "192.168.1.100"),
|
||||
prefs.getInt("http_port", 7906),
|
||||
prefs.getString(Prefs.Key.ADDRESS, Prefs.Default.ADDRESS),
|
||||
prefs.getInt(Prefs.Key.AUDIO_PORT, Prefs.Default.AUDIO_PORT),
|
||||
URLEncoder.encode(externalId),
|
||||
bitrateQueryParam);
|
||||
}
|
||||
@ -565,25 +565,6 @@ public class StreamingPlaybackService implements PlaybackService {
|
||||
return null;
|
||||
}
|
||||
|
||||
private void playCurrentTrack() {
|
||||
this.context.stopPlayback();
|
||||
|
||||
final String uri = getUri(this.context.currentMetadata);
|
||||
|
||||
if (uri != null) {
|
||||
this.context.currentPlayer = PlayerWrapper.newInstance();
|
||||
this.context.currentPlayer.setOnStateChangedListener(onCurrentPlayerStateChanged);
|
||||
this.context.currentPlayer.play(uri);
|
||||
setState(PlaybackState.Buffering);
|
||||
}
|
||||
}
|
||||
|
||||
private void onPlayQueueLoaded() {
|
||||
if (this.state == PlaybackState.Buffering) {
|
||||
playCurrentTrack();
|
||||
}
|
||||
}
|
||||
|
||||
private int resolvePrevIndex(final int currentIndex, final int count) {
|
||||
if (currentIndex - 1 < 0) {
|
||||
if (repeatMode == RepeatMode.List) {
|
||||
@ -741,9 +722,10 @@ public class StreamingPlaybackService implements PlaybackService {
|
||||
cancelScheduledPausedShutdown();
|
||||
SystemService.wakeup();
|
||||
|
||||
this.context.stopPlayback();
|
||||
this.context.stopPlaybackAndReset();
|
||||
final PlaybackContext context = new PlaybackContext();
|
||||
context.currentIndex = startIndex;
|
||||
this.context = context;
|
||||
context.currentIndex = startIndex;
|
||||
|
||||
this.params = params;
|
||||
final SocketMessage countMessage = queryFactory.getRequeryMessage();
|
||||
@ -763,10 +745,15 @@ public class StreamingPlaybackService implements PlaybackService {
|
||||
}
|
||||
})
|
||||
.doOnComplete(() -> {
|
||||
if (StreamingPlaybackService.this.params == params) {
|
||||
StreamingPlaybackService.this.context = context;
|
||||
if (this.params == params && this.context == context) {
|
||||
notifyEventListeners();
|
||||
onPlayQueueLoaded();
|
||||
|
||||
final String uri = getUri(this.context.currentMetadata);
|
||||
if (uri != null) {
|
||||
this.context.currentPlayer = PlayerWrapper.newInstance();
|
||||
this.context.currentPlayer.setOnStateChangedListener(onCurrentPlayerStateChanged);
|
||||
this.context.currentPlayer.play(uri);
|
||||
}
|
||||
}
|
||||
})
|
||||
.subscribe();
|
||||
|
@ -7,7 +7,10 @@ import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Bitmap;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.PowerManager;
|
||||
import android.support.annotation.Nullable;
|
||||
@ -18,11 +21,19 @@ import android.support.v7.app.NotificationCompat;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.bumptech.glide.request.animation.GlideAnimation;
|
||||
import com.bumptech.glide.request.target.SimpleTarget;
|
||||
import com.bumptech.glide.request.target.Target;
|
||||
|
||||
import io.casey.musikcube.remote.Application;
|
||||
import io.casey.musikcube.remote.MainActivity;
|
||||
import io.casey.musikcube.remote.R;
|
||||
import io.casey.musikcube.remote.ui.model.AlbumArtModel;
|
||||
import io.casey.musikcube.remote.util.Debouncer;
|
||||
import io.casey.musikcube.remote.util.Strings;
|
||||
import io.casey.musikcube.remote.websocket.Prefs;
|
||||
|
||||
/* basically a stub service that exists to keep a connection active to the
|
||||
StreamingPlaybackService, which keeps music playing. TODO: should also hold
|
||||
@ -46,12 +57,18 @@ public class SystemService extends Service {
|
||||
PlaybackStateCompat.ACTION_FAST_FORWARD |
|
||||
PlaybackStateCompat.ACTION_REWIND;
|
||||
|
||||
private Handler handler = new Handler();
|
||||
private SharedPreferences prefs;
|
||||
private StreamingPlaybackService playback;
|
||||
private PowerManager.WakeLock wakeLock;
|
||||
private PowerManager powerManager;
|
||||
private MediaSessionCompat mediaSession;
|
||||
private int headsetHookPressCount = 0;
|
||||
|
||||
private AlbumArtModel albumArtModel = AlbumArtModel.empty();
|
||||
private Bitmap albumArt = null;
|
||||
private SimpleTarget<Bitmap> albumArtRequest;
|
||||
|
||||
public static void wakeup() {
|
||||
final Context c = Application.getInstance();
|
||||
c.startService(new Intent(c, SystemService.class).setAction(ACTION_WAKE_UP));
|
||||
@ -69,6 +86,7 @@ public class SystemService extends Service {
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
prefs = this.getSharedPreferences(Prefs.NAME, Context.MODE_PRIVATE);
|
||||
powerManager = (PowerManager) getSystemService(POWER_SERVICE);
|
||||
registerReceivers();
|
||||
}
|
||||
@ -76,6 +94,7 @@ public class SystemService extends Service {
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
recycleAlbumArt();
|
||||
unregisterReceivers();
|
||||
}
|
||||
|
||||
@ -189,7 +208,7 @@ public class SystemService extends Service {
|
||||
duration = (int) (playback.getDuration() * 1000);
|
||||
}
|
||||
|
||||
updateMetadata(title, artist, album, duration);
|
||||
updateMetadata(title, artist, album, null, duration);
|
||||
updateNotification(title, artist, album, mediaSessionState);
|
||||
|
||||
mediaSession.setPlaybackState(new PlaybackStateCompat.Builder()
|
||||
@ -198,14 +217,73 @@ public class SystemService extends Service {
|
||||
.build());
|
||||
}
|
||||
|
||||
private void updateMetadata(final String title, final String artist, final String album, int duration) {
|
||||
private synchronized void recycleAlbumArt() {
|
||||
if (albumArt != null) {
|
||||
//albumArt.recycle();
|
||||
albumArt = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void downloadAlbumArt(final String title, final String artist, final String album, final int duration) {
|
||||
recycleAlbumArt();
|
||||
|
||||
albumArtModel = new AlbumArtModel(title, artist, album, AlbumArtModel.Size.Mega, (info, url) -> {
|
||||
if (albumArtModel.is(artist, album)) {
|
||||
handler.post(() -> {
|
||||
if (albumArtRequest != null && albumArtRequest.getRequest() != null) {
|
||||
albumArtRequest.getRequest().clear();
|
||||
}
|
||||
|
||||
albumArtRequest = Glide
|
||||
.with(getApplicationContext())
|
||||
.load(url)
|
||||
.asBitmap()
|
||||
.diskCacheStrategy(DiskCacheStrategy.ALL)
|
||||
.into(new SimpleTarget<Bitmap>(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) {
|
||||
@Override
|
||||
public void onResourceReady(final Bitmap bitmap, GlideAnimation glideAnimation) {
|
||||
albumArtRequest = null;
|
||||
if (albumArtModel.is(artist, album)) {
|
||||
albumArt = bitmap;
|
||||
updateMetadata(title, artist, album, bitmap, duration);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
albumArtModel.fetch();
|
||||
}
|
||||
|
||||
private void updateMetadata(final String title, final String artist, final String album, Bitmap image, final int duration) {
|
||||
boolean albumArtEnabledInSettings = this.prefs.getBoolean(
|
||||
Prefs.Key.ALBUM_ART_ENABLED, Prefs.Default.ALBUM_ART_ENABLED);
|
||||
|
||||
if (albumArtEnabledInSettings) {
|
||||
if (!"-".equals(artist) && !"-".equals(album) && !albumArtModel.is(artist, album)) {
|
||||
downloadAlbumArt(title, artist, album, duration);
|
||||
}
|
||||
else if (albumArtModel.is(artist, album)) {
|
||||
if (image == null && Strings.notEmpty(albumArtModel.getUrl())) {
|
||||
/* lookup may have failed -- try again. if the fetch is already in
|
||||
progress this will be a no-op */
|
||||
albumArtModel.fetch();
|
||||
}
|
||||
|
||||
image = albumArt;
|
||||
}
|
||||
else {
|
||||
recycleAlbumArt();
|
||||
}
|
||||
}
|
||||
|
||||
mediaSession.setMetadata(new MediaMetadataCompat.Builder()
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, artist)
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, album)
|
||||
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, title)
|
||||
.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, duration)
|
||||
// .putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART,
|
||||
// BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
|
||||
.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, image)
|
||||
.build());
|
||||
}
|
||||
|
||||
|
@ -26,6 +26,7 @@ import io.casey.musikcube.remote.playback.ExoPlayerWrapper;
|
||||
import io.casey.musikcube.remote.playback.MediaPlayerWrapper;
|
||||
import io.casey.musikcube.remote.playback.PlaybackServiceFactory;
|
||||
import io.casey.musikcube.remote.ui.util.Views;
|
||||
import io.casey.musikcube.remote.websocket.Prefs;
|
||||
import io.casey.musikcube.remote.websocket.WebSocketService;
|
||||
|
||||
public class SettingsActivity extends AppCompatActivity {
|
||||
@ -43,7 +44,7 @@ public class SettingsActivity extends AppCompatActivity {
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
prefs = this.getSharedPreferences("prefs", MODE_PRIVATE);
|
||||
prefs = this.getSharedPreferences(Prefs.NAME, MODE_PRIVATE);
|
||||
setContentView(R.layout.activity_settings);
|
||||
setTitle(R.string.settings_title);
|
||||
wasStreaming = isStreamingEnabled();
|
||||
@ -72,10 +73,10 @@ public class SettingsActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
private void rebindUi() {
|
||||
Views.setTextAndMoveCursorToEnd(this.addressText, prefs.getString("address", "192.168.1.100"));
|
||||
Views.setTextAndMoveCursorToEnd(this.portText, String.format(Locale.ENGLISH, "%d", prefs.getInt("port", 7905)));
|
||||
Views.setTextAndMoveCursorToEnd(this.httpPortText, String.format(Locale.ENGLISH, "%d", prefs.getInt("http_port", 7906)));
|
||||
Views.setTextAndMoveCursorToEnd(this.passwordText, prefs.getString("password", ""));
|
||||
Views.setTextAndMoveCursorToEnd(this.addressText, prefs.getString(Prefs.Key.ADDRESS, Prefs.Default.ADDRESS));
|
||||
Views.setTextAndMoveCursorToEnd(this.portText, String.format(Locale.ENGLISH, "%d", prefs.getInt(Prefs.Key.MAIN_PORT, Prefs.Default.MAIN_PORT)));
|
||||
Views.setTextAndMoveCursorToEnd(this.httpPortText, String.format(Locale.ENGLISH, "%d", prefs.getInt(Prefs.Key.AUDIO_PORT, Prefs.Default.AUDIO_PORT)));
|
||||
Views.setTextAndMoveCursorToEnd(this.passwordText, prefs.getString(Prefs.Key.PASSWORD, Prefs.Default.PASSWORD));
|
||||
|
||||
final ArrayAdapter<CharSequence> playbackModes = ArrayAdapter.createFromResource(
|
||||
this, R.array.streaming_mode_array, android.R.layout.simple_spinner_item);
|
||||
@ -89,35 +90,38 @@ public class SettingsActivity extends AppCompatActivity {
|
||||
|
||||
bitrates.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
bitrateSpinner.setAdapter(bitrates);
|
||||
bitrateSpinner.setSelection(prefs.getInt("transcoder_bitrate_index", 0));
|
||||
bitrateSpinner.setSelection(prefs.getInt(Prefs.Key.TRANSCODER_BITRATE_INDEX, Prefs.Default.TRANSCODER_BITRATE_INDEX));
|
||||
|
||||
final ArrayAdapter<CharSequence> cacheSizes = ArrayAdapter.createFromResource(
|
||||
this, R.array.disk_cache_array, android.R.layout.simple_spinner_item);
|
||||
|
||||
cacheSizes.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
cacheSpinner.setAdapter(cacheSizes);
|
||||
cacheSpinner.setSelection(prefs.getInt("disk_cache_size_index", 0));
|
||||
cacheSpinner.setSelection(prefs.getInt(Prefs.Key.DISK_CACHE_SIZE_INDEX, Prefs.Default.DISK_CACHE_SIZE_INDEX));
|
||||
|
||||
this.albumArtCheckbox.setChecked(this.prefs.getBoolean("album_art_enabled", true));
|
||||
this.messageCompressionCheckbox.setChecked(this.prefs.getBoolean("message_compression_enabled", true));
|
||||
this.softwareVolume.setChecked(this.prefs.getBoolean("software_volume", false));
|
||||
this.certCheckbox.setChecked(this.prefs.getBoolean("cert_validation_disabled", false));
|
||||
this.albumArtCheckbox.setChecked(this.prefs.getBoolean(Prefs.Key.ALBUM_ART_ENABLED, Prefs.Default.ALBUM_ART_ENABLED));
|
||||
this.messageCompressionCheckbox.setChecked(this.prefs.getBoolean(Prefs.Key.MESSAGE_COMPRESSION_ENABLED, Prefs.Default.MESSAGE_COMPRESSION_ENABLED));
|
||||
this.softwareVolume.setChecked(this.prefs.getBoolean(Prefs.Key.SOFTWARE_VOLUME, Prefs.Default.SOFTWARE_VOLUME));
|
||||
|
||||
Views.setCheckWithoutEvent(
|
||||
this.sslCheckbox,
|
||||
this.prefs.getBoolean("ssl_enabled", false),
|
||||
this.prefs.getBoolean(
|
||||
Prefs.Key.SSL_ENABLED,
|
||||
Prefs.Default.SSL_ENABLED),
|
||||
sslCheckChanged);
|
||||
|
||||
Views.setCheckWithoutEvent(
|
||||
this.certCheckbox,
|
||||
this.prefs.getBoolean("cert_validation_disabled", false),
|
||||
this.prefs.getBoolean(
|
||||
Prefs.Key.CERT_VALIDATION_DISABLED,
|
||||
Prefs.Default.CERT_VALIDATION_DISABLED),
|
||||
certValidationChanged);
|
||||
|
||||
Views.enableUpNavigation(this);
|
||||
}
|
||||
|
||||
private boolean isStreamingEnabled() {
|
||||
return this.prefs.getBoolean("streaming_playback", false);
|
||||
return this.prefs.getBoolean(Prefs.Key.STREAMING_PLAYBACK, Prefs.Default.STREAMING_PLAYBACK);
|
||||
}
|
||||
|
||||
private boolean isStreamingSelected() {
|
||||
@ -185,18 +189,18 @@ public class SettingsActivity extends AppCompatActivity {
|
||||
final String password = passwordText.getText().toString();
|
||||
|
||||
prefs.edit()
|
||||
.putString("address", addr)
|
||||
.putInt("port", (port.length() > 0) ? Integer.valueOf(port) : 0)
|
||||
.putInt("http_port", (httpPort.length() > 0) ? Integer.valueOf(httpPort) : 0)
|
||||
.putString("password", password)
|
||||
.putBoolean("album_art_enabled", albumArtCheckbox.isChecked())
|
||||
.putBoolean("message_compression_enabled", messageCompressionCheckbox.isChecked())
|
||||
.putBoolean("streaming_playback", isStreamingSelected())
|
||||
.putBoolean("software_volume", softwareVolume.isChecked())
|
||||
.putBoolean("ssl_enabled", sslCheckbox.isChecked())
|
||||
.putBoolean("cert_validation_disabled", certCheckbox.isChecked())
|
||||
.putInt("transcoder_bitrate_index", bitrateSpinner.getSelectedItemPosition())
|
||||
.putInt("disk_cache_size_index", cacheSpinner.getSelectedItemPosition())
|
||||
.putString(Prefs.Key.ADDRESS, addr)
|
||||
.putInt(Prefs.Key.MAIN_PORT, (port.length() > 0) ? Integer.valueOf(port) : 0)
|
||||
.putInt(Prefs.Key.AUDIO_PORT, (httpPort.length() > 0) ? Integer.valueOf(httpPort) : 0)
|
||||
.putString(Prefs.Key.PASSWORD, password)
|
||||
.putBoolean(Prefs.Key.ALBUM_ART_ENABLED, albumArtCheckbox.isChecked())
|
||||
.putBoolean(Prefs.Key.MESSAGE_COMPRESSION_ENABLED, messageCompressionCheckbox.isChecked())
|
||||
.putBoolean(Prefs.Key.STREAMING_PLAYBACK, isStreamingSelected())
|
||||
.putBoolean(Prefs.Key.SOFTWARE_VOLUME, softwareVolume.isChecked())
|
||||
.putBoolean(Prefs.Key.SSL_ENABLED, sslCheckbox.isChecked())
|
||||
.putBoolean(Prefs.Key.CERT_VALIDATION_DISABLED, certCheckbox.isChecked())
|
||||
.putInt(Prefs.Key.TRANSCODER_BITRATE_INDEX, bitrateSpinner.getSelectedItemPosition())
|
||||
.putInt(Prefs.Key.DISK_CACHE_SIZE_INDEX, cacheSpinner.getSelectedItemPosition())
|
||||
.apply();
|
||||
|
||||
if (!softwareVolume.isChecked()) {
|
||||
|
@ -15,6 +15,7 @@ import com.uacf.taskrunner.Task;
|
||||
|
||||
import io.casey.musikcube.remote.playback.PlaybackService;
|
||||
import io.casey.musikcube.remote.playback.PlaybackServiceFactory;
|
||||
import io.casey.musikcube.remote.websocket.Prefs;
|
||||
import io.casey.musikcube.remote.websocket.WebSocketService;
|
||||
|
||||
public abstract class WebSocketActivityBase extends AppCompatActivity implements Runner.TaskCallbacks {
|
||||
@ -32,7 +33,7 @@ public abstract class WebSocketActivityBase extends AppCompatActivity implements
|
||||
this.runnerDelegate.onCreate(savedInstanceState);
|
||||
this.wss = WebSocketService.getInstance(this);
|
||||
this.playback = PlaybackServiceFactory.instance(this);
|
||||
this.prefs = getSharedPreferences("prefs", Context.MODE_PRIVATE);
|
||||
this.prefs = getSharedPreferences(Prefs.NAME, Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -68,7 +69,8 @@ public abstract class WebSocketActivityBase extends AppCompatActivity implements
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
boolean streaming = prefs.getBoolean("streaming_playback", false);
|
||||
boolean streaming = prefs.getBoolean(
|
||||
Prefs.Key.STREAMING_PLAYBACK, Prefs.Default.STREAMING_PLAYBACK);
|
||||
|
||||
/* if we're not streaming we want the hardware buttons to go out to the system */
|
||||
if (!streaming) {
|
||||
|
@ -9,6 +9,10 @@ import org.json.JSONObject;
|
||||
import java.io.IOException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@ -59,19 +63,55 @@ public final class AlbumArtModel {
|
||||
private AlbumArtCallback callback;
|
||||
private boolean fetching;
|
||||
private boolean noart;
|
||||
private long loadTime = 0;
|
||||
private int id;
|
||||
private Size desiredSize;
|
||||
|
||||
public AlbumArtModel() {
|
||||
this("", "", "", null);
|
||||
public enum Size {
|
||||
Small("small", 0),
|
||||
Medium("medium", 1),
|
||||
Large("large", 2),
|
||||
ExtraLarge("extralarge", 3),
|
||||
Mega("mega", 4);
|
||||
|
||||
final String name;
|
||||
final int order;
|
||||
|
||||
static Size from(final String value) {
|
||||
for (Size size : values()) {
|
||||
if (size.name.equals(value)) {
|
||||
return size;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Size(String name, int order) {
|
||||
this.name = name;
|
||||
this.order = order;
|
||||
}
|
||||
}
|
||||
|
||||
public AlbumArtModel(String track, String artist, String album, AlbumArtCallback callback) {
|
||||
public static class Image {
|
||||
final String url;
|
||||
final Size size;
|
||||
|
||||
public Image(final Size size, final String url) {
|
||||
this.url = url;
|
||||
this.size = size;
|
||||
}
|
||||
}
|
||||
|
||||
public static AlbumArtModel empty() {
|
||||
return new AlbumArtModel("", "", "", Size.Small, null);
|
||||
}
|
||||
|
||||
public AlbumArtModel(String track, String artist, String album, Size size, AlbumArtCallback callback) {
|
||||
this.track = track;
|
||||
this.artist = artist;
|
||||
this.album = album;
|
||||
this.callback = callback != null ? callback : (info, url) -> { };
|
||||
this.id = (artist + album).hashCode();
|
||||
this.desiredSize = size;
|
||||
this.id = (artist + album + size.name).hashCode();
|
||||
|
||||
synchronized (this) {
|
||||
this.url = URL_CACHE.get(id);
|
||||
@ -99,18 +139,15 @@ public final class AlbumArtModel {
|
||||
return this.url;
|
||||
}
|
||||
|
||||
public synchronized long getLoadTimeMillis() {
|
||||
return this.loadTime;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public synchronized void fetch() {
|
||||
public synchronized AlbumArtModel fetch() {
|
||||
if (this.fetching || this.noart) {
|
||||
return;
|
||||
return this;
|
||||
}
|
||||
|
||||
if (!Strings.empty(this.url)) {
|
||||
callback.onFinished(this, this.url);
|
||||
}
|
||||
@ -130,7 +167,6 @@ public final class AlbumArtModel {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
|
||||
final long start = System.currentTimeMillis();
|
||||
this.fetching = true;
|
||||
final Request request = new Request.Builder().url(requestUrl).build();
|
||||
|
||||
@ -144,27 +180,53 @@ public final class AlbumArtModel {
|
||||
@Override
|
||||
public void onResponse(Call call, Response response) throws IOException {
|
||||
synchronized (AlbumArtModel.this) {
|
||||
List<Image> imageList = new ArrayList<>();
|
||||
|
||||
try {
|
||||
final JSONObject json = new JSONObject(response.body().string());
|
||||
final JSONArray images = json.getJSONObject("album").getJSONArray("image");
|
||||
for (int i = images.length() - 1; i >= 0; i--) {
|
||||
final JSONObject image = images.getJSONObject(i);
|
||||
final String size = image.optString("size", "");
|
||||
if (size != null && size.length() > 0) {
|
||||
final String resolvedUrl = image.optString("#text", "");
|
||||
if (resolvedUrl != null && resolvedUrl.length() > 0) {
|
||||
synchronized (AlbumArtModel.this) {
|
||||
URL_CACHE.put(id, resolvedUrl);
|
||||
}
|
||||
|
||||
AlbumArtModel.this.url = resolvedUrl;
|
||||
loadTime = System.currentTimeMillis() - start;
|
||||
callback.onFinished(AlbumArtModel.this, resolvedUrl);
|
||||
return;
|
||||
final JSONArray imagesJson = json.getJSONObject("album").getJSONArray("image");
|
||||
for (int i = 0; i < imagesJson.length(); i++) {
|
||||
final JSONObject imageJson = imagesJson.getJSONObject(i);
|
||||
final Size size = Size.from(imageJson.optString("size", ""));
|
||||
if (size != null) {
|
||||
final String resolvedUrl = imageJson.optString("#text", "");
|
||||
if (Strings.notEmpty(resolvedUrl)) {
|
||||
imageList.add(new Image(size, resolvedUrl));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (JSONException ex) {
|
||||
|
||||
if (imageList.size() > 0) {
|
||||
/* find the image with the closest to the requested size.
|
||||
exact match preferred. */
|
||||
Size desiredSize = Size.Mega;
|
||||
Image closest = imageList.get(0);
|
||||
int lastDelta = Integer.MAX_VALUE;
|
||||
for (final Image check : imageList) {
|
||||
if (check.size == desiredSize) {
|
||||
closest = check;
|
||||
break;
|
||||
}
|
||||
else {
|
||||
int delta = Math.abs(desiredSize.order - check.size.order);
|
||||
if (lastDelta > delta) {
|
||||
closest = check;
|
||||
lastDelta = delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
synchronized (AlbumArtModel.this) {
|
||||
URL_CACHE.put(id, closest.url);
|
||||
}
|
||||
|
||||
fetching = false;
|
||||
AlbumArtModel.this.url = closest.url;
|
||||
callback.onFinished(AlbumArtModel.this, closest.url);
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (JSONException ex) {
|
||||
}
|
||||
|
||||
noart = true; /* got a response, but it was invalid. we won't try again */
|
||||
@ -178,6 +240,8 @@ public final class AlbumArtModel {
|
||||
else {
|
||||
callback.onFinished(this, null);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private static final Pattern[] BAD_PATTERNS = {
|
||||
|
@ -0,0 +1,35 @@
|
||||
package io.casey.musikcube.remote.websocket;
|
||||
|
||||
public interface Prefs {
|
||||
String NAME = "prefs";
|
||||
|
||||
interface Key {
|
||||
String ADDRESS = "address";
|
||||
String MAIN_PORT = "port";
|
||||
String AUDIO_PORT = "http_port";
|
||||
String PASSWORD = "password";
|
||||
String ALBUM_ART_ENABLED = "album_art_enabled";
|
||||
String MESSAGE_COMPRESSION_ENABLED = "message_compression_enabled";
|
||||
String STREAMING_PLAYBACK = "streaming_playback";
|
||||
String SOFTWARE_VOLUME = "software_volume";
|
||||
String SSL_ENABLED = "ssl_enabled";
|
||||
String CERT_VALIDATION_DISABLED = "cert_validation_disabled";
|
||||
String TRANSCODER_BITRATE_INDEX = "transcoder_bitrate_index";
|
||||
String DISK_CACHE_SIZE_INDEX = "disk_cache_size_index";
|
||||
}
|
||||
|
||||
interface Default {
|
||||
String ADDRESS = "192.168.1.100";
|
||||
int MAIN_PORT = 7905;
|
||||
int AUDIO_PORT = 7906;
|
||||
String PASSWORD = "";
|
||||
boolean ALBUM_ART_ENABLED = true;
|
||||
boolean MESSAGE_COMPRESSION_ENABLED = true;
|
||||
boolean STREAMING_PLAYBACK = false;
|
||||
boolean SOFTWARE_VOLUME = false;
|
||||
boolean SSL_ENABLED = false;
|
||||
boolean CERT_VALIDATION_DISABLED = false;
|
||||
int TRANSCODER_BITRATE_INDEX = 0;
|
||||
int DISK_CACHE_SIZE_INDEX = 0;
|
||||
}
|
||||
}
|
@ -180,7 +180,7 @@ public class WebSocketService {
|
||||
|
||||
private WebSocketService(final Context context) {
|
||||
this.context = context.getApplicationContext();
|
||||
this.prefs = this.context.getSharedPreferences("prefs", Context.MODE_PRIVATE);
|
||||
this.prefs = this.context.getSharedPreferences(Prefs.NAME, Context.MODE_PRIVATE);
|
||||
handler.sendEmptyMessageDelayed(MESSAGE_REMOVE_OLD_CALLBACKS, CALLBACK_TIMEOUT_MILLIS);
|
||||
}
|
||||
|
||||
@ -342,8 +342,8 @@ public class WebSocketService {
|
||||
}
|
||||
|
||||
public boolean hasValidConnection() {
|
||||
final String addr = prefs.getString("address", "");
|
||||
final int port = prefs.getInt("port", -1);
|
||||
final String addr = prefs.getString(Prefs.Key.ADDRESS, "");
|
||||
final int port = prefs.getInt(Prefs.Key.MAIN_PORT, -1);
|
||||
return (addr.length() > 0 && port >= 0);
|
||||
}
|
||||
|
||||
@ -518,23 +518,24 @@ public class WebSocketService {
|
||||
try {
|
||||
final WebSocketFactory factory = new WebSocketFactory();
|
||||
|
||||
if (prefs.getBoolean("cert_validation_disabled", false)) {
|
||||
if (prefs.getBoolean(Prefs.Key.CERT_VALIDATION_DISABLED, Prefs.Default.CERT_VALIDATION_DISABLED)) {
|
||||
NetworkUtil.disableCertificateValidation(factory);
|
||||
}
|
||||
|
||||
final String protocol = prefs.getBoolean("ssl_enabled", false) ? "wss" : "ws";
|
||||
final String protocol = prefs.getBoolean(
|
||||
Prefs.Key.SSL_ENABLED, Prefs.Default.SSL_ENABLED) ? "wss" : "ws";
|
||||
|
||||
final String host = String.format(
|
||||
Locale.ENGLISH,
|
||||
"%s://%s:%d",
|
||||
protocol,
|
||||
prefs.getString("address", "192.168.1.100"),
|
||||
prefs.getInt("port", 7905));
|
||||
prefs.getString(Prefs.Key.ADDRESS, Prefs.Default.ADDRESS),
|
||||
prefs.getInt(Prefs.Key.MAIN_PORT, Prefs.Default.MAIN_PORT));
|
||||
|
||||
socket = factory.createSocket(host, CONNECTION_TIMEOUT_MILLIS);
|
||||
socket.addListener(webSocketAdapter);
|
||||
|
||||
if (prefs.getBoolean("message_compression_enabled", true)) {
|
||||
if (prefs.getBoolean(Prefs.Key.MESSAGE_COMPRESSION_ENABLED, Prefs.Default.MESSAGE_COMPRESSION_ENABLED)) {
|
||||
socket.addExtension(WebSocketExtension.PERMESSAGE_DEFLATE);
|
||||
}
|
||||
|
||||
@ -544,7 +545,7 @@ public class WebSocketService {
|
||||
/* authenticate */
|
||||
final String auth = SocketMessage.Builder
|
||||
.request(Messages.Request.Authenticate)
|
||||
.addOption("password", prefs.getString("password", ""))
|
||||
.addOption("password", prefs.getString(Prefs.Key.PASSWORD, Prefs.Default.PASSWORD))
|
||||
.build()
|
||||
.toString();
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user