mirror of
https://github.com/clangen/musikcube.git
synced 2025-03-29 19:20:28 +00:00
- Upgraded AndroidVideoCache to a custom build that fixes bugs related
to seeking while streaming when the backend returns an HTTP 200 instead of a 206 (i.e. ignores a range request) - Removed Vol+, Vol-, Seek>, and <Seek buttons in favor of current and total time controls with a seekbar. - Removed LongPressTextView - Added Snackbar notifications when switching streaming modes
This commit is contained in:
parent
a613022209
commit
3051ebf813
Binary file not shown.
@ -5,11 +5,15 @@ import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.HashMap;
|
||||
@ -26,7 +30,6 @@ import io.casey.musikcube.remote.ui.activity.TrackListActivity;
|
||||
import io.casey.musikcube.remote.ui.activity.WebSocketActivityBase;
|
||||
import io.casey.musikcube.remote.ui.fragment.InvalidPasswordDialogFragment;
|
||||
import io.casey.musikcube.remote.ui.util.Views;
|
||||
import io.casey.musikcube.remote.ui.view.LongPressTextView;
|
||||
import io.casey.musikcube.remote.ui.view.MainMetadataView;
|
||||
import io.casey.musikcube.remote.util.Duration;
|
||||
import io.casey.musikcube.remote.websocket.Messages;
|
||||
@ -43,11 +46,14 @@ public class MainActivity extends WebSocketActivityBase {
|
||||
private SharedPreferences prefs;
|
||||
private PlaybackService playback;
|
||||
|
||||
private View mainLayout;
|
||||
private MainMetadataView metadataView;
|
||||
private TextView playPause, currentTime, totalTime;
|
||||
private View connectedNotPlaying, disconnectedButton;
|
||||
private CheckBox shuffleCb, muteCb, repeatCb;
|
||||
private View disconnectedOverlay;
|
||||
private SeekBar seekbar;
|
||||
private int seekbarValue = -1;
|
||||
|
||||
static {
|
||||
REPEAT_TO_STRING_ID = new HashMap<>();
|
||||
@ -169,6 +175,12 @@ public class MainActivity extends WebSocketActivityBase {
|
||||
|
||||
prefs.edit().putBoolean(Prefs.Key.STREAMING_PLAYBACK, !streaming).apply();
|
||||
|
||||
final int messageId = streaming
|
||||
? R.string.snackbar_remote_enabled
|
||||
: R.string.snackbar_streaming_enabled;
|
||||
|
||||
showSnackbar(messageId);
|
||||
|
||||
reloadPlaybackService();
|
||||
this.playback = getPlaybackService();
|
||||
|
||||
@ -176,6 +188,15 @@ public class MainActivity extends WebSocketActivityBase {
|
||||
rebindUi();
|
||||
}
|
||||
|
||||
private void showSnackbar(int stringId) {
|
||||
final Snackbar sb = Snackbar.make(mainLayout, stringId, Snackbar.LENGTH_LONG);
|
||||
final View sbView = sb.getView();
|
||||
sbView.setBackgroundColor(ContextCompat.getColor(this, R.color.color_primary));
|
||||
final TextView tv = (TextView) sbView.findViewById(android.support.design.R.id.snackbar_text);
|
||||
tv.setTextColor(ContextCompat.getColor(this, R.color.theme_foreground));
|
||||
sb.show();
|
||||
}
|
||||
|
||||
private void bindCheckBoxEventListeners() {
|
||||
this.shuffleCb.setOnCheckedChangeListener(shuffleListener);
|
||||
this.muteCb.setOnCheckedChangeListener(muteListener);
|
||||
@ -191,6 +212,7 @@ public class MainActivity extends WebSocketActivityBase {
|
||||
}
|
||||
|
||||
private void bindEventListeners() {
|
||||
this.mainLayout = findViewById(R.id.activity_main);
|
||||
this.metadataView = (MainMetadataView) findViewById(R.id.main_metadata_view);
|
||||
this.shuffleCb = (CheckBox) findViewById(R.id.check_shuffle);
|
||||
this.muteCb = (CheckBox) findViewById(R.id.check_mute);
|
||||
@ -201,12 +223,10 @@ public class MainActivity extends WebSocketActivityBase {
|
||||
this.playPause = (TextView) findViewById(R.id.button_play_pause);
|
||||
this.currentTime = (TextView) findViewById(R.id.current_time);
|
||||
this.totalTime = (TextView) findViewById(R.id.total_time);
|
||||
this.seekbar = (SeekBar) findViewById(R.id.seekbar);
|
||||
|
||||
findViewById(R.id.button_prev).setOnClickListener((View view) -> playback.prev());
|
||||
|
||||
final LongPressTextView seekBack = (LongPressTextView) findViewById(R.id.button_seek_back);
|
||||
seekBack.setOnTickListener((View view) -> playback.seekBackward());
|
||||
|
||||
findViewById(R.id.button_play_pause).setOnClickListener((View view) -> {
|
||||
if (playback.getPlaybackState() == PlaybackState.Stopped) {
|
||||
playback.playAll();
|
||||
@ -218,19 +238,33 @@ public class MainActivity extends WebSocketActivityBase {
|
||||
|
||||
findViewById(R.id.button_next).setOnClickListener((View view) -> playback.next());
|
||||
|
||||
final LongPressTextView seekForward = (LongPressTextView) findViewById(R.id.button_seek_forward);
|
||||
seekForward.setOnTickListener((View view) -> playback.seekForward());
|
||||
|
||||
final LongPressTextView volumeUp = (LongPressTextView) findViewById(R.id.button_vol_up);
|
||||
volumeUp.setOnTickListener((View view) -> playback.volumeUp());
|
||||
|
||||
final LongPressTextView volumeDown = (LongPressTextView) findViewById(R.id.button_vol_down);
|
||||
volumeDown.setOnTickListener((View view) -> playback.volumeDown());
|
||||
|
||||
disconnectedButton.setOnClickListener((view) -> {
|
||||
wss.reconnect();
|
||||
});
|
||||
|
||||
seekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||
if (fromUser) {
|
||||
seekbarValue = progress;
|
||||
currentTime.setText(Duration.format(seekbarValue));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||
if (seekbarValue != -1) {
|
||||
playback.seekTo((double) seekbarValue);
|
||||
seekbarValue = -1;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
findViewById(R.id.button_artists).setOnClickListener((View view) -> {
|
||||
startActivity(CategoryBrowseActivity.getStartIntent(this, Messages.Category.ALBUM_ARTIST));
|
||||
});
|
||||
@ -265,10 +299,11 @@ public class MainActivity extends WebSocketActivityBase {
|
||||
final boolean connected = (wss.getState() == WebSocketService.State.Connected);
|
||||
final boolean stopped = (playback.getPlaybackState() == PlaybackState.Stopped);
|
||||
final boolean playing = (playback.getPlaybackState() == PlaybackState.Playing);
|
||||
final boolean buffering = (playback.getPlaybackState() == PlaybackState.Buffering);
|
||||
final boolean showMetadataView = !stopped && connected && playback.getQueueCount() > 0;
|
||||
|
||||
/* bottom section: transport controls */
|
||||
this.playPause.setText(playing ? R.string.button_pause : R.string.button_play);
|
||||
this.playPause.setText(playing || buffering ? R.string.button_pause : R.string.button_play);
|
||||
|
||||
this.connectedNotPlaying.setVisibility((connected && stopped) ? View.VISIBLE : View.GONE);
|
||||
this.disconnectedOverlay.setVisibility(connected ? View.GONE : View.VISIBLE);
|
||||
@ -317,8 +352,15 @@ public class MainActivity extends WebSocketActivityBase {
|
||||
}
|
||||
|
||||
private Runnable updateTimeRunnable = () -> {
|
||||
currentTime.setText(Duration.format(playback.getCurrentTime()));
|
||||
totalTime.setText(Duration.format(playback.getDuration()));
|
||||
final double duration = playback.getDuration();
|
||||
final double current = (seekbarValue == -1) ? playback.getCurrentTime() : seekbarValue;
|
||||
|
||||
currentTime.setText(Duration.format(current));
|
||||
totalTime.setText(Duration.format(duration));
|
||||
|
||||
seekbar.setMax((int) duration);
|
||||
seekbar.setProgress((int) current);
|
||||
|
||||
scheduleUpdateTime(false);
|
||||
};
|
||||
|
||||
|
@ -1,7 +1,9 @@
|
||||
package io.casey.musikcube.remote.playback;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.util.Base64;
|
||||
|
||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||
import com.google.android.exoplayer2.ExoPlayer;
|
||||
@ -9,6 +11,7 @@ import com.google.android.exoplayer2.ExoPlayerFactory;
|
||||
import com.google.android.exoplayer2.PlaybackParameters;
|
||||
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSourceFactory;
|
||||
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
|
||||
import com.google.android.exoplayer2.extractor.ExtractorsFactory;
|
||||
import com.google.android.exoplayer2.source.ExtractorMediaSource;
|
||||
@ -24,10 +27,19 @@ import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
|
||||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
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;
|
||||
import okhttp3.Request;
|
||||
|
||||
public class ExoPlayerWrapper extends PlayerWrapper {
|
||||
private static OkHttpClient audioStreamHttpClient = null;
|
||||
|
||||
private DataSource.Factory datasources;
|
||||
private ExtractorsFactory extractors;
|
||||
private MediaSource source;
|
||||
@ -35,7 +47,56 @@ public class ExoPlayerWrapper extends PlayerWrapper {
|
||||
private boolean prefetch;
|
||||
private Context context;
|
||||
private long lastPosition = -1;
|
||||
private String uri, proxyUri;
|
||||
private String originalUri, resolvedUri;
|
||||
|
||||
private void initHttpClient(final String uri) {
|
||||
if (StreamProxy.ENABLED) {
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (ExoPlayerWrapper.class) {
|
||||
if (audioStreamHttpClient == null) {
|
||||
final SharedPreferences prefs = Application.getInstance()
|
||||
.getSharedPreferences(Prefs.NAME, Context.MODE_PRIVATE);
|
||||
|
||||
final File path = new File(context.getExternalCacheDir(), "audio");
|
||||
|
||||
int diskCacheIndex = prefs.getInt(
|
||||
Prefs.Key.DISK_CACHE_SIZE_INDEX, Prefs.Default.DISK_CACHE_SIZE_INDEX);
|
||||
|
||||
if (diskCacheIndex < 0 || diskCacheIndex > StreamProxy.CACHE_SETTING_TO_BYTES.size()) {
|
||||
diskCacheIndex = 0;
|
||||
}
|
||||
|
||||
final OkHttpClient.Builder builder = new OkHttpClient.Builder()
|
||||
.cache(new Cache(path, StreamProxy.CACHE_SETTING_TO_BYTES.get(diskCacheIndex)))
|
||||
.addInterceptor((chain) -> {
|
||||
Request request = chain.request();
|
||||
final String userPass = "default:" + prefs.getString(Prefs.Key.PASSWORD, Prefs.Default.PASSWORD);
|
||||
final String encoded = Base64.encodeToString(userPass.getBytes(), Base64.NO_WRAP);
|
||||
request = request.newBuilder().addHeader("Authorization", "Basic " + encoded).build();
|
||||
return chain.proceed(request);
|
||||
});
|
||||
|
||||
if (prefs.getBoolean(Prefs.Key.CERT_VALIDATION_DISABLED, Prefs.Default.CERT_VALIDATION_DISABLED)) {
|
||||
NetworkUtil.disableCertificateValidation(builder);
|
||||
}
|
||||
|
||||
audioStreamHttpClient = builder.build();
|
||||
}
|
||||
}
|
||||
|
||||
if (uri.startsWith("http")) {
|
||||
this.datasources = new OkHttpDataSourceFactory(
|
||||
audioStreamHttpClient,
|
||||
Util.getUserAgent(context, "musikdroid"),
|
||||
new DefaultBandwidthMeter());
|
||||
}
|
||||
else {
|
||||
this.datasources = new DefaultDataSourceFactory(
|
||||
context, Util.getUserAgent(context, "musikdroid"));
|
||||
}
|
||||
}
|
||||
|
||||
public ExoPlayerWrapper() {
|
||||
this.context = Application.getInstance();
|
||||
@ -53,9 +114,10 @@ public class ExoPlayerWrapper extends PlayerWrapper {
|
||||
Preconditions.throwIfNotOnMainThread();
|
||||
|
||||
if (!dead()) {
|
||||
this.uri = uri;
|
||||
this.proxyUri = StreamProxy.getProxyUrl(context, uri);
|
||||
this.source = new ExtractorMediaSource(Uri.parse(proxyUri), datasources, extractors, null, null);
|
||||
initHttpClient(uri);
|
||||
this.originalUri = uri;
|
||||
this.resolvedUri = StreamProxy.getProxyUrl(context, uri);
|
||||
this.source = new ExtractorMediaSource(Uri.parse(resolvedUri), datasources, extractors, null, null);
|
||||
this.player.setPlayWhenReady(true);
|
||||
this.player.prepare(this.source);
|
||||
addActivePlayer(this);
|
||||
@ -68,10 +130,11 @@ public class ExoPlayerWrapper extends PlayerWrapper {
|
||||
Preconditions.throwIfNotOnMainThread();
|
||||
|
||||
if (!dead()) {
|
||||
this.uri = uri;
|
||||
initHttpClient(uri);
|
||||
this.originalUri = uri;
|
||||
this.prefetch = true;
|
||||
this.proxyUri = StreamProxy.getProxyUrl(context, uri);
|
||||
this.source = new ExtractorMediaSource(Uri.parse(proxyUri), datasources, extractors, null, null);
|
||||
this.resolvedUri = StreamProxy.getProxyUrl(context, uri);
|
||||
this.source = new ExtractorMediaSource(Uri.parse(resolvedUri), datasources, extractors, null, null);
|
||||
this.player.setPlayWhenReady(false);
|
||||
this.player.prepare(this.source);
|
||||
addActivePlayer(this);
|
||||
@ -119,6 +182,7 @@ public class ExoPlayerWrapper extends PlayerWrapper {
|
||||
this.lastPosition = -1;
|
||||
if (this.player.getPlaybackState() != ExoPlayer.STATE_IDLE) {
|
||||
if (this.player.isCurrentWindowSeekable()) {
|
||||
this.lastPosition = millis;
|
||||
this.player.seekTo(millis);
|
||||
}
|
||||
}
|
||||
@ -196,7 +260,10 @@ public class ExoPlayerWrapper extends PlayerWrapper {
|
||||
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
|
||||
Preconditions.throwIfNotOnMainThread();
|
||||
|
||||
if (playbackState == ExoPlayer.STATE_READY) {
|
||||
if (playbackState == ExoPlayer.STATE_BUFFERING) {
|
||||
setState(State.Buffering);
|
||||
}
|
||||
else if (playbackState == ExoPlayer.STATE_READY) {
|
||||
if (dead()) {
|
||||
dispose();
|
||||
}
|
||||
|
@ -1,15 +1,21 @@
|
||||
package io.casey.musikcube.remote.playback;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.media.AudioManager;
|
||||
import android.media.MediaPlayer;
|
||||
import android.net.Uri;
|
||||
import android.os.PowerManager;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import io.casey.musikcube.remote.Application;
|
||||
import io.casey.musikcube.remote.util.Preconditions;
|
||||
import io.casey.musikcube.remote.websocket.Prefs;
|
||||
|
||||
public class MediaPlayerWrapper extends PlayerWrapper {
|
||||
private static final String TAG = "MediaPlayerWrapper";
|
||||
@ -17,6 +23,12 @@ public class MediaPlayerWrapper extends PlayerWrapper {
|
||||
private MediaPlayer player = new MediaPlayer();
|
||||
private int seekTo;
|
||||
private boolean prefetching;
|
||||
private Context context = Application.getInstance();
|
||||
private SharedPreferences prefs;
|
||||
|
||||
public MediaPlayerWrapper() {
|
||||
this.prefs = context.getSharedPreferences(Prefs.NAME, Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void play(final String uri) {
|
||||
@ -24,7 +36,17 @@ public class MediaPlayerWrapper extends PlayerWrapper {
|
||||
|
||||
try {
|
||||
setState(State.Preparing);
|
||||
player.setDataSource(Application.getInstance(), Uri.parse(uri));
|
||||
|
||||
final String userPass = "default:" + prefs.getString(Prefs.Key.PASSWORD, Prefs.Default.PASSWORD);
|
||||
final String encoded = Base64.encodeToString(userPass.getBytes(), Base64.NO_WRAP);
|
||||
final Map<String, String> headers = new HashMap<>();
|
||||
headers.put("Authorization", "Basic " + encoded);
|
||||
|
||||
player.setDataSource(
|
||||
context,
|
||||
Uri.parse(StreamProxy.getProxyUrl(context, uri)),
|
||||
headers);
|
||||
|
||||
player.setAudioStreamType(AudioManager.STREAM_MUSIC);
|
||||
player.setOnPreparedListener(onPrepared);
|
||||
player.setOnErrorListener(onError);
|
||||
|
@ -32,8 +32,10 @@ public interface PlaybackService {
|
||||
|
||||
void volumeUp();
|
||||
void volumeDown();
|
||||
|
||||
void seekForward();
|
||||
void seekBackward();
|
||||
void seekTo(double seconds);
|
||||
|
||||
int getQueueCount();
|
||||
int getQueuePosition();
|
||||
|
@ -1,14 +1,14 @@
|
||||
package io.casey.musikcube.remote.playback;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import io.casey.musikcube.remote.util.Preconditions;
|
||||
|
||||
public abstract class PlayerWrapper {
|
||||
private static final String TAG = "MediaPlayerWrapper";
|
||||
private enum Type { MediaPlayer, ExoPlayer }
|
||||
|
||||
private static final Type TYPE = Type.ExoPlayer;
|
||||
private static final float DUCK_COEF = 0.2f; /* volume = 20% when ducked */
|
||||
private static final float DUCK_NONE = -1.0f;
|
||||
|
||||
@ -17,6 +17,7 @@ public abstract class PlayerWrapper {
|
||||
Preparing,
|
||||
Prepared,
|
||||
Playing,
|
||||
Buffering,
|
||||
Paused,
|
||||
Error,
|
||||
Finished,
|
||||
@ -92,8 +93,9 @@ public abstract class PlayerWrapper {
|
||||
}
|
||||
|
||||
public static PlayerWrapper newInstance() {
|
||||
//return new MediaPlayerWrapper();
|
||||
return new ExoPlayerWrapper();
|
||||
return TYPE == Type.ExoPlayer
|
||||
? new ExoPlayerWrapper()
|
||||
: new MediaPlayerWrapper();
|
||||
}
|
||||
|
||||
protected static void addActivePlayer(final PlayerWrapper player) {
|
||||
|
@ -209,6 +209,15 @@ public class RemotePlaybackService implements PlaybackService {
|
||||
.addOption(Messages.Key.DELTA, -5.0f).build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seekTo(double seconds) {
|
||||
wss.send(SocketMessage.Builder
|
||||
.request(Messages.Request.SeekTo)
|
||||
.addOption(Messages.Key.POSITION, seconds).build());
|
||||
|
||||
currentTime.update(seconds, currentTime.trackId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getQueueCount() {
|
||||
return queueCount;
|
||||
|
@ -19,9 +19,10 @@ import io.casey.musikcube.remote.util.NetworkUtil;
|
||||
import io.casey.musikcube.remote.websocket.Prefs;
|
||||
|
||||
public class StreamProxy {
|
||||
private static final long BYTES_PER_MEGABYTE = 1048576L;
|
||||
private static final long BYTES_PER_GIGABYTE = 1073741824L;
|
||||
private static final Map<Integer, Long> CACHE_SETTING_TO_BYTES;
|
||||
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 Map<Integer, Long> CACHE_SETTING_TO_BYTES;
|
||||
private static final FileNameGenerator DEFAULT_FILENAME_GENERATOR = new Md5FileNameGenerator();
|
||||
|
||||
static {
|
||||
@ -105,7 +106,7 @@ public class StreamProxy {
|
||||
|
||||
public static synchronized String getProxyUrl(final Context context, final String url) {
|
||||
init(context);
|
||||
return INSTANCE.proxy.getProxyUrl(url);
|
||||
return ENABLED ? INSTANCE.proxy.getProxyUrl(url) : url;
|
||||
}
|
||||
|
||||
public static synchronized void reload() {
|
||||
|
@ -215,7 +215,7 @@ public class StreamingPlaybackService implements PlaybackService {
|
||||
@Override
|
||||
public void pauseOrResume() {
|
||||
if (context.currentPlayer != null) {
|
||||
if (state == PlaybackState.Playing) {
|
||||
if (state == PlaybackState.Playing || state == PlaybackState.Buffering) {
|
||||
pause();
|
||||
}
|
||||
else {
|
||||
@ -308,6 +308,15 @@ public class StreamingPlaybackService implements PlaybackService {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seekTo(double seconds) {
|
||||
if (requestAudioFocus()) {
|
||||
if (context.currentPlayer != null) {
|
||||
context.currentPlayer.setPosition((int)(seconds * 1000));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getQueueCount() {
|
||||
return context.queueCount;
|
||||
@ -508,6 +517,10 @@ public class StreamingPlaybackService implements PlaybackService {
|
||||
precacheTrackMetadata(context.currentIndex, PRECACHE_METADATA_SIZE);
|
||||
break;
|
||||
|
||||
case Buffering:
|
||||
setState(PlaybackState.Buffering);
|
||||
break;
|
||||
|
||||
case Paused:
|
||||
pause();
|
||||
break;
|
||||
|
@ -1,99 +0,0 @@
|
||||
package io.casey.musikcube.remote.ui.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
public class LongPressTextView extends TextView {
|
||||
private static final int TICK_START_DELAY = 700;
|
||||
private static final int MINIMUM_TICK_DELAY = 100;
|
||||
private static final int TICK_DELTA = 100;
|
||||
private static final int FIRST_TICK_DELAY = 200;
|
||||
|
||||
public interface OnTickListener {
|
||||
void onTick(final View view);
|
||||
}
|
||||
|
||||
private int tickDelay = 0;
|
||||
private int ticksFired = 0;
|
||||
private boolean isDown;
|
||||
private Handler handler = new Handler();
|
||||
private OnTickListener onTickListener;
|
||||
private View.OnClickListener onClickListener;
|
||||
|
||||
public LongPressTextView(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public LongPressTextView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public LongPressTextView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init();
|
||||
}
|
||||
|
||||
public void setOnTickListener(OnTickListener onTickListener) {
|
||||
this.onTickListener = onTickListener;
|
||||
this.setClickable(onTickListener != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnClickListener(OnClickListener l) {
|
||||
this.onClickListener = l;
|
||||
}
|
||||
|
||||
private void init() {
|
||||
super.setOnClickListener(onClickProxy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
isDown = true;
|
||||
ticksFired = 0;
|
||||
tickDelay = TICK_START_DELAY;
|
||||
handler.removeCallbacks(tickRunnable);
|
||||
handler.postDelayed(tickRunnable, FIRST_TICK_DELAY);
|
||||
}
|
||||
else if (event.getAction() == MotionEvent.ACTION_UP) {
|
||||
handler.removeCallbacks(tickRunnable);
|
||||
ticksFired = 0;
|
||||
isDown = false;
|
||||
}
|
||||
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
|
||||
private Runnable tickRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (isDown) {
|
||||
if (onTickListener != null) {
|
||||
onTickListener.onTick(LongPressTextView.this);
|
||||
}
|
||||
|
||||
tickDelay = Math.max(MINIMUM_TICK_DELAY, tickDelay - TICK_DELTA);
|
||||
handler.postDelayed(tickRunnable, tickDelay);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private View.OnClickListener onClickProxy = new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if (onTickListener == null && onClickListener != null) {
|
||||
onClickListener.onClick(view);
|
||||
}
|
||||
else if (onTickListener != null && ticksFired == 0) {
|
||||
onTickListener.onTick(view);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
@ -94,6 +94,7 @@ public class Messages {
|
||||
String LIMIT = "limit";
|
||||
String INDEX = "index";
|
||||
String DELTA = "delta";
|
||||
String POSITION = "position";
|
||||
String VALUE = "value";
|
||||
String FILTER = "filter";
|
||||
String RELATIVE = "relative";
|
||||
|
@ -22,37 +22,6 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<android.support.v4.widget.Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="2dp" />
|
||||
|
||||
<FrameLayout
|
||||
android:background="@drawable/playback_button"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/current_time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="left"
|
||||
android:gravity="left"
|
||||
android:text="0:00"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/total_time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="right"
|
||||
android:gravity="right"
|
||||
android:text="0:00"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<android.support.v4.widget.Space
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="2dp" />
|
||||
@ -155,55 +124,39 @@
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:background="@drawable/playback_button"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp"
|
||||
android:paddingTop="6dp"
|
||||
android:paddingBottom="6dp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:orientation="horizontal">
|
||||
android:clipToPadding="false"
|
||||
android:clipChildren="false"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<io.casey.musikcube.remote.ui.view.LongPressTextView
|
||||
style="@style/PlaybackButton"
|
||||
android:layout_weight="1.0"
|
||||
android:id="@+id/button_vol_down"
|
||||
<TextView
|
||||
android:id="@+id/current_time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:minWidth="48dp"
|
||||
android:gravity="center"
|
||||
android:text="0:00"/>
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/seekbar"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/button_vol_down"/>
|
||||
android:layout_gravity="center"
|
||||
android:layout_weight="1.0"/>
|
||||
|
||||
<android.support.v4.widget.Space
|
||||
android:layout_width="2dp"
|
||||
android:layout_height="0dp" />
|
||||
|
||||
<io.casey.musikcube.remote.ui.view.LongPressTextView
|
||||
style="@style/PlaybackButton"
|
||||
android:layout_weight="1.0"
|
||||
android:id="@+id/button_seek_back"
|
||||
android:layout_width="0dp"
|
||||
<TextView
|
||||
android:id="@+id/total_time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/button_seek_back"/>
|
||||
|
||||
<android.support.v4.widget.Space
|
||||
android:layout_width="2dp"
|
||||
android:layout_height="0dp" />
|
||||
|
||||
<io.casey.musikcube.remote.ui.view.LongPressTextView
|
||||
style="@style/PlaybackButton"
|
||||
android:layout_weight="1.0"
|
||||
android:id="@+id/button_seek_forward"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/button_seek_forward"/>
|
||||
|
||||
<android.support.v4.widget.Space
|
||||
android:layout_width="2dp"
|
||||
android:layout_height="0dp" />
|
||||
|
||||
<io.casey.musikcube.remote.ui.view.LongPressTextView
|
||||
style="@style/PlaybackButton"
|
||||
android:layout_weight="1.0"
|
||||
android:id="@+id/button_vol_up"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/button_vol_up"/>
|
||||
android:minWidth="48dp"
|
||||
android:gravity="center"
|
||||
android:text="0:00"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 30 KiB |
@ -54,6 +54,8 @@
|
||||
<string name="menu_playlists">playlists</string>
|
||||
<string name="menu_remote_toggle">remote playback</string>
|
||||
<string name="unknown_value"><unknown></string>
|
||||
<string name="snackbar_streaming_enabled">switched to streaming mode</string>
|
||||
<string name="snackbar_remote_enabled">switched to remote control mode</string>
|
||||
<string name="settings_playback_mode">playback mode:</string>
|
||||
<string name="settings_transcoder_bitrate">streaming downsampler bitrate:</string>
|
||||
<string name="settings_cache_size">streaming disk cache size:</string>
|
||||
|
Loading…
x
Reference in New Issue
Block a user