Use StreamProxy for local playback (no stutter!)
Use SeekBar instead of custom HorizonalSlider for seeking
This commit is contained in:
parent
32024ed1af
commit
1404616b35
|
@ -1,8 +1,8 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:a="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:a="http://schemas.android.com/apk/res/android"
|
||||||
package="net.sourceforge.subsonic.androidapp"
|
package="net.sourceforge.subsonic.androidapp"
|
||||||
a:versionCode="57"
|
a:versionCode="58"
|
||||||
a:versionName="3.9.9.16" a:installLocation="auto">
|
a:versionName="3.9.9.17" a:installLocation="auto">
|
||||||
|
|
||||||
<uses-permission a:name="android.permission.INTERNET"/>
|
<uses-permission a:name="android.permission.INTERNET"/>
|
||||||
<uses-permission a:name="android.permission.READ_PHONE_STATE"/>
|
<uses-permission a:name="android.permission.READ_PHONE_STATE"/>
|
||||||
|
|
|
@ -4,10 +4,9 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical" >
|
android:orientation="vertical" >
|
||||||
|
|
||||||
<net.sourceforge.subsonic.androidapp.util.HorizontalSlider
|
<SeekBar
|
||||||
xmlns:a="http://schemas.android.com/apk/res/android"
|
xmlns:a="http://schemas.android.com/apk/res/android"
|
||||||
a:id="@+id/download_progress_bar"
|
a:id="@+id/download_progress_bar"
|
||||||
style="?android:attr/progressBarStyleHorizontal"
|
|
||||||
a:layout_width="fill_parent"
|
a:layout_width="fill_parent"
|
||||||
a:layout_height="48dp"
|
a:layout_height="48dp"
|
||||||
a:background="@color/mediaControlBackground"
|
a:background="@color/mediaControlBackground"
|
||||||
|
|
|
@ -53,6 +53,7 @@ import android.widget.ImageButton;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
|
import android.widget.SeekBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.ViewFlipper;
|
import android.widget.ViewFlipper;
|
||||||
import net.sourceforge.subsonic.androidapp.R;
|
import net.sourceforge.subsonic.androidapp.R;
|
||||||
|
@ -64,7 +65,6 @@ import net.sourceforge.subsonic.androidapp.service.DownloadService;
|
||||||
import net.sourceforge.subsonic.androidapp.service.MusicService;
|
import net.sourceforge.subsonic.androidapp.service.MusicService;
|
||||||
import net.sourceforge.subsonic.androidapp.service.MusicServiceFactory;
|
import net.sourceforge.subsonic.androidapp.service.MusicServiceFactory;
|
||||||
import net.sourceforge.subsonic.androidapp.util.Constants;
|
import net.sourceforge.subsonic.androidapp.util.Constants;
|
||||||
import net.sourceforge.subsonic.androidapp.util.HorizontalSlider;
|
|
||||||
import net.sourceforge.subsonic.androidapp.util.SilentBackgroundTask;
|
import net.sourceforge.subsonic.androidapp.util.SilentBackgroundTask;
|
||||||
import net.sourceforge.subsonic.androidapp.util.SongView;
|
import net.sourceforge.subsonic.androidapp.util.SongView;
|
||||||
import net.sourceforge.subsonic.androidapp.util.Util;
|
import net.sourceforge.subsonic.androidapp.util.Util;
|
||||||
|
@ -88,7 +88,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
||||||
private TextView positionTextView;
|
private TextView positionTextView;
|
||||||
private TextView durationTextView;
|
private TextView durationTextView;
|
||||||
private TextView statusTextView;
|
private TextView statusTextView;
|
||||||
private HorizontalSlider progressBar;
|
private static SeekBar progressBar;
|
||||||
private View previousButton;
|
private View previousButton;
|
||||||
private View nextButton;
|
private View nextButton;
|
||||||
private View pauseButton;
|
private View pauseButton;
|
||||||
|
@ -134,7 +134,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
||||||
positionTextView = (TextView) findViewById(R.id.download_position);
|
positionTextView = (TextView) findViewById(R.id.download_position);
|
||||||
durationTextView = (TextView) findViewById(R.id.download_duration);
|
durationTextView = (TextView) findViewById(R.id.download_duration);
|
||||||
statusTextView = (TextView) findViewById(R.id.download_status);
|
statusTextView = (TextView) findViewById(R.id.download_status);
|
||||||
progressBar = (HorizontalSlider) findViewById(R.id.download_progress_bar);
|
progressBar = (SeekBar) findViewById(R.id.download_progress_bar);
|
||||||
playlistView = (ListView) findViewById(R.id.download_list);
|
playlistView = (ListView) findViewById(R.id.download_list);
|
||||||
previousButton = findViewById(R.id.download_previous);
|
previousButton = findViewById(R.id.download_previous);
|
||||||
nextButton = findViewById(R.id.download_next);
|
nextButton = findViewById(R.id.download_next);
|
||||||
|
@ -175,7 +175,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
||||||
warnIfNetworkOrStorageUnavailable();
|
warnIfNetworkOrStorageUnavailable();
|
||||||
getDownloadService().previous();
|
getDownloadService().previous();
|
||||||
onCurrentChanged();
|
onCurrentChanged();
|
||||||
onProgressChanged();
|
onSliderProgressChanged();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -186,7 +186,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
||||||
if (getDownloadService().getCurrentPlayingIndex() < getDownloadService().size() - 1) {
|
if (getDownloadService().getCurrentPlayingIndex() < getDownloadService().size() - 1) {
|
||||||
getDownloadService().next();
|
getDownloadService().next();
|
||||||
onCurrentChanged();
|
onCurrentChanged();
|
||||||
onProgressChanged();
|
onSliderProgressChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -196,7 +196,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
getDownloadService().pause();
|
getDownloadService().pause();
|
||||||
onCurrentChanged();
|
onCurrentChanged();
|
||||||
onProgressChanged();
|
onSliderProgressChanged();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -205,7 +205,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
getDownloadService().reset();
|
getDownloadService().reset();
|
||||||
onCurrentChanged();
|
onCurrentChanged();
|
||||||
onProgressChanged();
|
onSliderProgressChanged();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -215,7 +215,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
||||||
warnIfNetworkOrStorageUnavailable();
|
warnIfNetworkOrStorageUnavailable();
|
||||||
start();
|
start();
|
||||||
onCurrentChanged();
|
onCurrentChanged();
|
||||||
onProgressChanged();
|
onSliderProgressChanged();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -256,23 +256,32 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
progressBar.setOnSliderChangeListener(new HorizontalSlider.OnSliderChangeListener() {
|
progressBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||||
@Override
|
|
||||||
public void onSliderChanged(View view, int position, boolean inProgress) {
|
@Override
|
||||||
Util.toast(DownloadActivity.this, Util.formatDuration(position / 1000), true);
|
public void onStopTrackingTouch(SeekBar seekBar) {
|
||||||
if (!inProgress) {
|
}
|
||||||
getDownloadService().seekTo(position);
|
|
||||||
onProgressChanged();
|
@Override
|
||||||
|
public void onStartTrackingTouch(SeekBar seekBar) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||||
|
if (fromUser) {
|
||||||
|
getDownloadService().seekTo(progress);
|
||||||
|
onSliderProgressChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
playlistView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
playlistView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||||
warnIfNetworkOrStorageUnavailable();
|
warnIfNetworkOrStorageUnavailable();
|
||||||
getDownloadService().play(position);
|
getDownloadService().play(position);
|
||||||
onCurrentChanged();
|
onCurrentChanged();
|
||||||
onProgressChanged();
|
onSliderProgressChanged();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -333,7 +342,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
||||||
|
|
||||||
onDownloadListChanged();
|
onDownloadListChanged();
|
||||||
onCurrentChanged();
|
onCurrentChanged();
|
||||||
onProgressChanged();
|
onSliderProgressChanged();
|
||||||
scrollToCurrent();
|
scrollToCurrent();
|
||||||
if (downloadService != null && downloadService.getKeepScreenOn()) {
|
if (downloadService != null && downloadService.getKeepScreenOn()) {
|
||||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||||
|
@ -560,7 +569,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
||||||
onCurrentChanged();
|
onCurrentChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
onProgressChanged();
|
onSliderProgressChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void savePlaylistInBackground(final String playlistName) {
|
private void savePlaylistInBackground(final String playlistName) {
|
||||||
|
@ -676,13 +685,12 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onProgressChanged() {
|
private void onSliderProgressChanged() {
|
||||||
if (getDownloadService() == null) {
|
if (getDownloadService() == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentPlaying != null) {
|
if (currentPlaying != null) {
|
||||||
|
|
||||||
int millisPlayed = Math.max(0, getDownloadService().getPlayerPosition());
|
int millisPlayed = Math.max(0, getDownloadService().getPlayerPosition());
|
||||||
Integer duration = getDownloadService().getPlayerDuration();
|
Integer duration = getDownloadService().getPlayerDuration();
|
||||||
int millisTotal = duration == null ? 0 : duration;
|
int millisTotal = duration == null ? 0 : duration;
|
||||||
|
@ -691,12 +699,11 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
||||||
durationTextView.setText(Util.formatDuration(millisTotal / 1000));
|
durationTextView.setText(Util.formatDuration(millisTotal / 1000));
|
||||||
progressBar.setMax(millisTotal == 0 ? 100 : millisTotal); // Work-around for apparent bug.
|
progressBar.setMax(millisTotal == 0 ? 100 : millisTotal); // Work-around for apparent bug.
|
||||||
progressBar.setProgress(millisPlayed);
|
progressBar.setProgress(millisPlayed);
|
||||||
progressBar.setSlidingEnabled(currentPlaying.isCompleteFileAvailable() || getDownloadService().isJukeboxEnabled());
|
|
||||||
} else {
|
} else {
|
||||||
positionTextView.setText("0:00");
|
positionTextView.setText("0:00");
|
||||||
durationTextView.setText("-:--");
|
durationTextView.setText("-:--");
|
||||||
progressBar.setProgress(0);
|
progressBar.setProgress(0);
|
||||||
progressBar.setSlidingEnabled(false);
|
progressBar.setMax(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerState playerState = getDownloadService().getPlayerState();
|
PlayerState playerState = getDownloadService().getPlayerState();
|
||||||
|
@ -784,7 +791,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
||||||
if (downloadService.getCurrentPlayingIndex() < downloadService.size() - 1) {
|
if (downloadService.getCurrentPlayingIndex() < downloadService.size() - 1) {
|
||||||
downloadService.next();
|
downloadService.next();
|
||||||
onCurrentChanged();
|
onCurrentChanged();
|
||||||
onProgressChanged();
|
onSliderProgressChanged();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -794,7 +801,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
||||||
warnIfNetworkOrStorageUnavailable();
|
warnIfNetworkOrStorageUnavailable();
|
||||||
downloadService.previous();
|
downloadService.previous();
|
||||||
onCurrentChanged();
|
onCurrentChanged();
|
||||||
onProgressChanged();
|
onSliderProgressChanged();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -802,7 +809,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
||||||
if (e2.getY() - e1.getY() > swipeDistance && Math.abs(velocityY) > swipeVelocity) {
|
if (e2.getY() - e1.getY() > swipeDistance && Math.abs(velocityY) > swipeVelocity) {
|
||||||
warnIfNetworkOrStorageUnavailable();
|
warnIfNetworkOrStorageUnavailable();
|
||||||
downloadService.seekTo(downloadService.getPlayerPosition() + 30000);
|
downloadService.seekTo(downloadService.getPlayerPosition() + 30000);
|
||||||
onProgressChanged();
|
onSliderProgressChanged();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -810,7 +817,7 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
||||||
if (e1.getY() - e2.getY() > swipeDistance && Math.abs(velocityY) > swipeVelocity) {
|
if (e1.getY() - e2.getY() > swipeDistance && Math.abs(velocityY) > swipeVelocity) {
|
||||||
warnIfNetworkOrStorageUnavailable();
|
warnIfNetworkOrStorageUnavailable();
|
||||||
downloadService.seekTo(downloadService.getPlayerPosition() - 8000);
|
downloadService.seekTo(downloadService.getPlayerPosition() - 8000);
|
||||||
onProgressChanged();
|
onSliderProgressChanged();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -834,4 +841,8 @@ public class DownloadActivity extends SubsonicTabActivity implements OnGestureLi
|
||||||
public boolean onSingleTapUp(MotionEvent e) {
|
public boolean onSingleTapUp(MotionEvent e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static SeekBar getProgressBar() {
|
||||||
|
return progressBar;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@ import android.os.PowerManager;
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.widget.RemoteViews;
|
import android.widget.RemoteViews;
|
||||||
|
import android.widget.SeekBar;
|
||||||
import net.sourceforge.subsonic.androidapp.R;
|
import net.sourceforge.subsonic.androidapp.R;
|
||||||
import net.sourceforge.subsonic.androidapp.activity.DownloadActivity;
|
import net.sourceforge.subsonic.androidapp.activity.DownloadActivity;
|
||||||
import net.sourceforge.subsonic.androidapp.audiofx.EqualizerController;
|
import net.sourceforge.subsonic.androidapp.audiofx.EqualizerController;
|
||||||
|
@ -48,6 +49,7 @@ import net.sourceforge.subsonic.androidapp.util.CancellableTask;
|
||||||
import net.sourceforge.subsonic.androidapp.util.LRUCache;
|
import net.sourceforge.subsonic.androidapp.util.LRUCache;
|
||||||
import net.sourceforge.subsonic.androidapp.util.ShufflePlayBuffer;
|
import net.sourceforge.subsonic.androidapp.util.ShufflePlayBuffer;
|
||||||
import net.sourceforge.subsonic.androidapp.util.SimpleServiceBinder;
|
import net.sourceforge.subsonic.androidapp.util.SimpleServiceBinder;
|
||||||
|
import net.sourceforge.subsonic.androidapp.util.StreamProxy;
|
||||||
import net.sourceforge.subsonic.androidapp.util.Util;
|
import net.sourceforge.subsonic.androidapp.util.Util;
|
||||||
import net.sourceforge.subsonic.androidapp.util.RemoteControlHelper;
|
import net.sourceforge.subsonic.androidapp.util.RemoteControlHelper;
|
||||||
import net.sourceforge.subsonic.androidapp.util.RemoteControlClientCompat;
|
import net.sourceforge.subsonic.androidapp.util.RemoteControlClientCompat;
|
||||||
|
@ -57,7 +59,6 @@ import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static net.sourceforge.subsonic.androidapp.domain.PlayerState.*;
|
import static net.sourceforge.subsonic.androidapp.domain.PlayerState.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -105,6 +106,7 @@ public class DownloadServiceImpl extends Service implements DownloadService {
|
||||||
private VisualizerController visualizerController;
|
private VisualizerController visualizerController;
|
||||||
private boolean showVisualization;
|
private boolean showVisualization;
|
||||||
private boolean jukeboxEnabled;
|
private boolean jukeboxEnabled;
|
||||||
|
private StreamProxy proxy;
|
||||||
|
|
||||||
private static MusicDirectory.Entry currentSong;
|
private static MusicDirectory.Entry currentSong;
|
||||||
|
|
||||||
|
@ -827,11 +829,34 @@ public class DownloadServiceImpl extends Service implements DownloadService {
|
||||||
mediaPlayer.reset();
|
mediaPlayer.reset();
|
||||||
setPlayerState(IDLE);
|
setPlayerState(IDLE);
|
||||||
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
|
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
|
||||||
mediaPlayer.setDataSource(file.getPath());
|
|
||||||
|
String url = file.getPath();
|
||||||
|
String playUrl = url;
|
||||||
|
|
||||||
|
if (proxy == null) {
|
||||||
|
proxy = new StreamProxy(this);
|
||||||
|
proxy.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
playUrl = String.format("http://127.0.0.1:%d/%s", proxy.getPort(), url);
|
||||||
|
|
||||||
|
mediaPlayer.setDataSource(playUrl);
|
||||||
setPlayerState(PREPARING);
|
setPlayerState(PREPARING);
|
||||||
mediaPlayer.prepare();
|
mediaPlayer.prepare();
|
||||||
setPlayerState(PREPARED);
|
setPlayerState(PREPARED);
|
||||||
|
|
||||||
|
mediaPlayer.setOnBufferingUpdateListener(new MediaPlayer.OnBufferingUpdateListener() {
|
||||||
|
@Override
|
||||||
|
public void onBufferingUpdate(MediaPlayer mp, int percent) {
|
||||||
|
SeekBar progressBar = DownloadActivity.getProgressBar();
|
||||||
|
if (progressBar != null) {
|
||||||
|
int max = progressBar.getMax();
|
||||||
|
int secondaryProgress = (int) (((double)percent / (double)100) * max);
|
||||||
|
DownloadActivity.getProgressBar().setSecondaryProgress(secondaryProgress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
|
mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onCompletion(MediaPlayer mediaPlayer) {
|
public void onCompletion(MediaPlayer mediaPlayer) {
|
||||||
|
|
|
@ -1,141 +0,0 @@
|
||||||
/*
|
|
||||||
This file is part of Subsonic.
|
|
||||||
|
|
||||||
Subsonic is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
Subsonic is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with Subsonic. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
Copyright 2009 (C) Sindre Mehus
|
|
||||||
*/
|
|
||||||
package net.sourceforge.subsonic.androidapp.util;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.graphics.BitmapFactory;
|
|
||||||
import android.graphics.Canvas;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.ProgressBar;
|
|
||||||
import net.sourceforge.subsonic.androidapp.R;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Sindre Mehus
|
|
||||||
* @version $Id$
|
|
||||||
*/
|
|
||||||
public class HorizontalSlider extends ProgressBar {
|
|
||||||
|
|
||||||
private final Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.slider_knob);
|
|
||||||
private boolean slidingEnabled;
|
|
||||||
private OnSliderChangeListener listener;
|
|
||||||
private static final int PADDING = 2;
|
|
||||||
private boolean sliding;
|
|
||||||
private int sliderPosition;
|
|
||||||
private int startPosition;
|
|
||||||
|
|
||||||
public interface OnSliderChangeListener {
|
|
||||||
void onSliderChanged(View view, int position, boolean inProgress);
|
|
||||||
}
|
|
||||||
|
|
||||||
public HorizontalSlider(Context context, AttributeSet attrs, int defStyle) {
|
|
||||||
super(context, attrs, defStyle);
|
|
||||||
}
|
|
||||||
|
|
||||||
public HorizontalSlider(Context context, AttributeSet attrs) {
|
|
||||||
super(context, attrs, android.R.attr.progressBarStyleHorizontal);
|
|
||||||
}
|
|
||||||
|
|
||||||
public HorizontalSlider(Context context) {
|
|
||||||
super(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSlidingEnabled(boolean slidingEnabled) {
|
|
||||||
if (this.slidingEnabled != slidingEnabled) {
|
|
||||||
this.slidingEnabled = slidingEnabled;
|
|
||||||
invalidate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSlidingEnabled() {
|
|
||||||
return slidingEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOnSliderChangeListener(OnSliderChangeListener listener) {
|
|
||||||
this.listener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDraw(Canvas canvas) {
|
|
||||||
super.onDraw(canvas);
|
|
||||||
|
|
||||||
int max = getMax();
|
|
||||||
if (!slidingEnabled || max == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int paddingLeft = getPaddingLeft();
|
|
||||||
int paddingRight = getPaddingRight();
|
|
||||||
int paddingTop = getPaddingTop();
|
|
||||||
int paddingBottom = getPaddingBottom();
|
|
||||||
|
|
||||||
int w = getWidth() - paddingLeft - paddingRight;
|
|
||||||
int h = getHeight() - paddingTop - paddingBottom;
|
|
||||||
int position = sliding ? sliderPosition : getProgress();
|
|
||||||
|
|
||||||
int bitmapWidth = bitmap.getWidth();
|
|
||||||
int bitmapHeight = bitmap.getWidth();
|
|
||||||
float x = paddingLeft + w * ((float) position / max) - bitmapWidth / 2.0F;
|
|
||||||
x = Math.max(x, paddingLeft);
|
|
||||||
x = Math.min(x, paddingLeft + w - bitmapWidth);
|
|
||||||
float y = paddingTop + h / 2.0F - bitmapHeight / 2.0F;
|
|
||||||
|
|
||||||
canvas.drawBitmap(bitmap, x, y, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onTouchEvent(MotionEvent event) {
|
|
||||||
if (!slidingEnabled) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int action = event.getAction();
|
|
||||||
|
|
||||||
if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_MOVE) {
|
|
||||||
|
|
||||||
if (action == MotionEvent.ACTION_DOWN) {
|
|
||||||
sliding = true;
|
|
||||||
startPosition = getProgress();
|
|
||||||
}
|
|
||||||
|
|
||||||
float x = event.getX() - PADDING;
|
|
||||||
float width = getWidth() - 2 * PADDING;
|
|
||||||
sliderPosition = Math.round((float) getMax() * (x / width));
|
|
||||||
sliderPosition = Math.max(sliderPosition, 0);
|
|
||||||
|
|
||||||
setProgress(Math.min(startPosition, sliderPosition));
|
|
||||||
setSecondaryProgress(Math.max(startPosition, sliderPosition));
|
|
||||||
if (listener != null) {
|
|
||||||
listener.onSliderChanged(this, sliderPosition, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (action == MotionEvent.ACTION_UP) {
|
|
||||||
sliding = false;
|
|
||||||
setProgress(sliderPosition);
|
|
||||||
setSecondaryProgress(0);
|
|
||||||
if (listener != null) {
|
|
||||||
listener.onSliderChanged(this, sliderPosition, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,243 @@
|
||||||
|
package net.sourceforge.subsonic.androidapp.util;
|
||||||
|
|
||||||
|
import java.io.BufferedOutputStream;
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
|
import java.net.URLDecoder;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.StringTokenizer;
|
||||||
|
|
||||||
|
import org.apache.http.Header;
|
||||||
|
import org.apache.http.HttpRequest;
|
||||||
|
import org.apache.http.message.BasicHttpRequest;
|
||||||
|
|
||||||
|
import net.sourceforge.subsonic.androidapp.service.DownloadService;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
public class StreamProxy implements Runnable {
|
||||||
|
private static final String TAG = StreamProxy.class.getSimpleName();
|
||||||
|
|
||||||
|
private Thread thread;
|
||||||
|
private boolean isRunning;
|
||||||
|
private ServerSocket socket;
|
||||||
|
private int port;
|
||||||
|
private DownloadService downloadService;
|
||||||
|
|
||||||
|
public StreamProxy(DownloadService downloadService) {
|
||||||
|
|
||||||
|
// Create listening socket
|
||||||
|
try {
|
||||||
|
socket = new ServerSocket(0, 0, InetAddress.getByAddress(new byte[] { 127, 0, 0, 1 }));
|
||||||
|
socket.setSoTimeout(5000);
|
||||||
|
port = socket.getLocalPort();
|
||||||
|
this.downloadService = downloadService;
|
||||||
|
} catch (UnknownHostException e) { // impossible
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "IOException initializing server", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPort() {
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() {
|
||||||
|
thread = new Thread(this);
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
isRunning = false;
|
||||||
|
thread.interrupt();
|
||||||
|
try {
|
||||||
|
thread.join(5000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Looper.prepare();
|
||||||
|
isRunning = true;
|
||||||
|
while (isRunning) {
|
||||||
|
try {
|
||||||
|
Socket client = socket.accept();
|
||||||
|
if (client == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Log.d(TAG, "client connected");
|
||||||
|
|
||||||
|
StreamToMediaPlayerTask task = new StreamToMediaPlayerTask(client);
|
||||||
|
if (task.processRequest()) {
|
||||||
|
task.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (SocketTimeoutException e) {
|
||||||
|
// Do nothing
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Error connecting to client", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.d(TAG, "Proxy interrupted. Shutting down.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private class StreamToMediaPlayerTask extends AsyncTask<String, Void, Integer> {
|
||||||
|
|
||||||
|
String localPath;
|
||||||
|
Socket client;
|
||||||
|
int cbSkip;
|
||||||
|
|
||||||
|
public StreamToMediaPlayerTask(Socket client) {
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpRequest readRequest() {
|
||||||
|
HttpRequest request = null;
|
||||||
|
InputStream is;
|
||||||
|
String firstLine;
|
||||||
|
try {
|
||||||
|
is = client.getInputStream();
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(is), 8192);
|
||||||
|
firstLine = reader.readLine();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Error parsing request", e);
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstLine == null) {
|
||||||
|
Log.i(TAG, "Proxy client closed connection without a request.");
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringTokenizer st = new StringTokenizer(firstLine);
|
||||||
|
String method = st.nextToken();
|
||||||
|
String uri = st.nextToken();
|
||||||
|
Log.d(TAG, uri);
|
||||||
|
String realUri = uri.substring(1);
|
||||||
|
Log.d(TAG, realUri);
|
||||||
|
request = new BasicHttpRequest(method, realUri);
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean processRequest() {
|
||||||
|
HttpRequest request = readRequest();
|
||||||
|
if (request == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read HTTP headers
|
||||||
|
Log.d(TAG, "Processing request");
|
||||||
|
|
||||||
|
try {
|
||||||
|
localPath = URLDecoder.decode(request.getRequestLine().getUri(), "UTF-8");
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
Log.e(TAG, "Unsupported encoding", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
File file = new File(localPath);
|
||||||
|
if (!file.exists()) {
|
||||||
|
Log.e(TAG, "File " + localPath + " does not exist");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Header rangeHeader = request.getLastHeader("Range");
|
||||||
|
|
||||||
|
if (rangeHeader != null) {
|
||||||
|
cbSkip = Integer.parseInt(rangeHeader.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Integer doInBackground(String... params) {
|
||||||
|
long fileSize = downloadService.getCurrentPlaying().getSong().getSize();
|
||||||
|
|
||||||
|
// Create HTTP header
|
||||||
|
String headers = "HTTP/1.0 200 OK\r\n";
|
||||||
|
headers += "Content-Type: " + "application/octet-stream" + "\r\n";
|
||||||
|
headers += "Content-Length: " + fileSize + "\r\n";
|
||||||
|
headers += "Connection: close\r\n";
|
||||||
|
headers += "\r\n";
|
||||||
|
|
||||||
|
long cbToSend = fileSize - cbSkip;
|
||||||
|
OutputStream output = null;
|
||||||
|
byte[] buff = new byte[64 * 1024];
|
||||||
|
try {
|
||||||
|
output = new BufferedOutputStream(client.getOutputStream(), 32*1024);
|
||||||
|
output.write(headers.getBytes());
|
||||||
|
|
||||||
|
// Loop as long as there's stuff to send
|
||||||
|
while (isRunning && cbToSend>0 && !client.isClosed()) {
|
||||||
|
|
||||||
|
// See if there's more to send
|
||||||
|
File file = new File(localPath);
|
||||||
|
int cbSentThisBatch = 0;
|
||||||
|
if (file.exists()) {
|
||||||
|
FileInputStream input = new FileInputStream(file);
|
||||||
|
input.skip(cbSkip);
|
||||||
|
int cbToSendThisBatch = input.available();
|
||||||
|
while (cbToSendThisBatch > 0) {
|
||||||
|
int cbToRead = Math.min(cbToSendThisBatch, buff.length);
|
||||||
|
int cbRead = input.read(buff, 0, cbToRead);
|
||||||
|
if (cbRead == -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
cbToSendThisBatch -= cbRead;
|
||||||
|
cbToSend -= cbRead;
|
||||||
|
output.write(buff, 0, cbRead);
|
||||||
|
output.flush();
|
||||||
|
cbSkip += cbRead;
|
||||||
|
cbSentThisBatch += cbRead;
|
||||||
|
}
|
||||||
|
input.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we did nothing this batch, block for a second
|
||||||
|
if (cbSentThisBatch == 0) {
|
||||||
|
Log.d(TAG, "Blocking until more data appears");
|
||||||
|
Thread.sleep(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (SocketException socketException) {
|
||||||
|
Log.e(TAG, "SocketException() thrown, proxy client has probably closed. This can exit harmlessly");
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
Log.e(TAG, "Exception thrown from streaming task:");
|
||||||
|
Log.e(TAG, e.getClass().getName() + " : " + e.getLocalizedMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
try {
|
||||||
|
if (output != null) {
|
||||||
|
output.close();
|
||||||
|
}
|
||||||
|
client.close();
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
Log.e(TAG, "IOException while cleaning up streaming task:");
|
||||||
|
Log.e(TAG, e.getClass().getName() + " : " + e.getLocalizedMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue