diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/CachedMusicService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/CachedMusicService.java index 8fe09e59..38c27e44 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/CachedMusicService.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/CachedMusicService.java @@ -21,7 +21,6 @@ package org.moire.ultrasonic.service; import android.content.Context; import android.graphics.Bitmap; -import org.apache.http.HttpResponse; import org.moire.ultrasonic.domain.Bookmark; import org.moire.ultrasonic.domain.ChatMessage; import org.moire.ultrasonic.domain.Genre; @@ -43,11 +42,14 @@ import org.moire.ultrasonic.util.ProgressListener; import org.moire.ultrasonic.util.TimeLimitedCache; import org.moire.ultrasonic.util.Util; +import java.io.InputStream; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.concurrent.TimeUnit; +import kotlin.Pair; + /** * @author Sindre Mehus */ @@ -314,7 +316,7 @@ public class CachedMusicService implements MusicService } @Override - public HttpResponse getDownloadInputStream(Context context, MusicDirectory.Entry song, long offset, int maxBitrate, CancellableTask task) throws Exception + public Pair getDownloadInputStream(Context context, MusicDirectory.Entry song, long offset, int maxBitrate, CancellableTask task) throws Exception { return musicService.getDownloadInputStream(context, song, offset, maxBitrate, task); } diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadFile.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadFile.java index 69a138d8..9364fb2f 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadFile.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/DownloadFile.java @@ -29,10 +29,6 @@ import org.moire.ultrasonic.util.CancellableTask; import org.moire.ultrasonic.util.FileUtil; import org.moire.ultrasonic.util.Util; -import org.apache.http.Header; -import org.apache.http.HttpResponse; -import org.apache.http.HttpStatus; - import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -40,6 +36,8 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.RandomAccessFile; +import kotlin.Pair; + import static android.content.Context.POWER_SERVICE; import static android.os.PowerManager.ON_AFTER_RELEASE; import static android.os.PowerManager.SCREEN_DIM_WAKE_LOCK; @@ -66,7 +64,6 @@ public class DownloadFile private volatile boolean isPlaying; private volatile boolean saveWhenDone; private volatile boolean completeWhenDone; - private Integer contentLength; public DownloadFile(Context context, MusicDirectory.Entry song, boolean save) { @@ -105,11 +102,6 @@ public class DownloadFile return song.getBitRate() == null ? 160 : song.getBitRate(); } - public Integer getContentLength() - { - return contentLength; - } - public synchronized void download() { FileUtil.createDirectoryForParent(saveFile); @@ -369,30 +361,17 @@ public class DownloadFile if (compare) { // Attempt partial HTTP GET, appending to the file if it exists. - HttpResponse response = musicService.getDownloadInputStream(context, song, partialFile.length(), bitRate, DownloadTask.this); - Header contentLengthHeader = response.getFirstHeader("Content-Length"); + Pair response = musicService + .getDownloadInputStream(context, song, partialFile.length(), bitRate, + DownloadTask.this); - if (contentLengthHeader != null) - { - String contentLengthString = contentLengthHeader.getValue(); - - if (contentLengthString != null) - { - Log.i(TAG, "Content Length: " + contentLengthString); - contentLength = Integer.parseInt(contentLengthString); - } - } - - in = response.getEntity().getContent(); - boolean partial = response.getStatusLine().getStatusCode() == HttpStatus.SC_PARTIAL_CONTENT; - - if (partial) + if (response.getSecond()) { Log.i(TAG, String.format("Executed partial HTTP GET, skipping %d bytes", partialFile.length())); } - out = new FileOutputStream(partialFile, partial); - long n = copy(in, out); + out = new FileOutputStream(partialFile, response.getSecond()); + long n = copy(response.getFirst(), out); Log.i(TAG, String.format("Downloaded %d bytes to %s", n, partialFile)); out.flush(); out.close(); diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicService.java index 31ae5bac..19eb315c 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicService.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/MusicService.java @@ -39,8 +39,11 @@ import org.moire.ultrasonic.domain.UserInfo; import org.moire.ultrasonic.util.CancellableTask; import org.moire.ultrasonic.util.ProgressListener; +import java.io.InputStream; import java.util.List; +import kotlin.Pair; + /** * @author Sindre Mehus */ @@ -101,7 +104,11 @@ public interface MusicService Bitmap getCoverArt(Context context, MusicDirectory.Entry entry, int size, boolean saveToFile, boolean highQuality, ProgressListener progressListener) throws Exception; - HttpResponse getDownloadInputStream(Context context, MusicDirectory.Entry song, long offset, int maxBitrate, CancellableTask task) throws Exception; + /** + * Return response {@link InputStream} and a {@link Boolean} that indicates if this response is + * partial. + */ + Pair getDownloadInputStream(Context context, MusicDirectory.Entry song, long offset, int maxBitrate, CancellableTask task) throws Exception; String getVideoUrl(Context context, String id, boolean useFlash) throws Exception; diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/RESTMusicService.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/RESTMusicService.java index a24b1cc9..7de681be 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/RESTMusicService.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/service/RESTMusicService.java @@ -46,7 +46,6 @@ import org.apache.http.conn.scheme.SchemeRegistry; import org.apache.http.conn.scheme.SocketFactory; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; -import org.apache.http.message.BasicHeader; import org.apache.http.message.BasicNameValuePair; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.HttpConnectionParams; @@ -133,13 +132,13 @@ import java.io.Reader; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.concurrent.atomic.AtomicReference; +import kotlin.Pair; import retrofit2.Response; import static java.util.Arrays.asList; @@ -154,12 +153,8 @@ public class RESTMusicService implements MusicService private static final int SOCKET_CONNECT_TIMEOUT = 10 * 1000; private static final int SOCKET_READ_TIMEOUT_DEFAULT = 10 * 1000; - private static final int SOCKET_READ_TIMEOUT_DOWNLOAD = 30 * 1000; - private static final int SOCKET_READ_TIMEOUT_GET_RANDOM_SONGS = 60 * 1000; - private static final int SOCKET_READ_TIMEOUT_GET_PLAYLIST = 60 * 1000; - // Allow 20 seconds extra timeout per MB offset. - private static final double TIMEOUT_MILLIS_PER_OFFSET_BYTE = 20000.0 / 1000000.0; + private static final int SOCKET_READ_TIMEOUT_GET_RANDOM_SONGS = 60 * 1000; /** * URL from which to fetch latest versions. @@ -785,14 +780,7 @@ public class RESTMusicService implements MusicService } StreamResponse response = subsonicAPIClient.getCoverArt(id, (long) size); - if (response.hasError() || response.getStream() == null) { - if (response.getApiError() != null) { - throw new SubsonicRESTException(response.getApiError().getCode(), "rest error"); - } else { - throw new IOException("Failed to make endpoint request, code: " + - response.getRequestErrorCode()); - } - } + checkStreamResponseError(response); if (response.getStream() == null) { return null; // Failed to load @@ -826,48 +814,39 @@ public class RESTMusicService implements MusicService } } - @Override - public HttpResponse getDownloadInputStream(Context context, MusicDirectory.Entry song, long offset, int maxBitrate, CancellableTask task) throws Exception - { + private void checkStreamResponseError(StreamResponse response) + throws SubsonicRESTException, IOException { + if (response.hasError() || response.getStream() == null) { + if (response.getApiError() != null) { + throw new SubsonicRESTException(response.getApiError().getCode(), "rest error"); + } else { + throw new IOException("Failed to make endpoint request, code: " + + response.getResponseHttpCode()); + } + } + } - String url = Util.getRestUrl(context, "stream"); + @Override + public Pair getDownloadInputStream(final Context context, + final MusicDirectory.Entry song, + final long offset, + final int maxBitrate, + final CancellableTask task) + throws Exception { + if (song == null) { + throw new IllegalArgumentException("Song for download is null!"); + } + long songOffset = offset < 0 ? 0 : offset; - // Set socket read timeout. Note: The timeout increases as the offset gets larger. This is - // to avoid the thrashing effect seen when offset is combined with transcoding/downsampling on the server. - // In that case, the server uses a long time before sending any data, causing the client to time out. - HttpParams params = new BasicHttpParams(); - int timeout = (int) (SOCKET_READ_TIMEOUT_DOWNLOAD + offset * TIMEOUT_MILLIS_PER_OFFSET_BYTE); - HttpConnectionParams.setSoTimeout(params, timeout); + StreamResponse response = subsonicAPIClient.stream(song.getId(), maxBitrate, songOffset); + checkStreamResponseError(response); + if (response.getStream() == null) { + throw new IOException("Null stream response"); + } + Boolean partial = response.getResponseHttpCode() == 206; - // Add "Range" header if offset is given. - Collection
headers = new ArrayList
(); - - if (offset > 0) - { - headers.add(new BasicHeader("Range", String.format("bytes=%d-", offset))); - } - - List parameterNames = asList("id", "maxBitRate"); - List parameterValues = Arrays.asList(song.getId(), maxBitrate); - HttpResponse response = getResponseForURL(context, url, params, parameterNames, parameterValues, headers, null, task); - - // If content type is XML, an error occurred. Get it. - String contentType = Util.getContentType(response.getEntity()); - if (contentType != null && contentType.startsWith("text/xml")) - { - InputStream in = response.getEntity().getContent(); - try - { - new ErrorParser(context).parse(new InputStreamReader(in, Constants.UTF_8)); - } - finally - { - Util.close(in); - } - } - - return response; - } + return new Pair<>(response.getStream(), partial); + } @Override public String getVideoUrl(Context context, String id, boolean useFlash) throws Exception @@ -980,11 +959,6 @@ public class RESTMusicService implements MusicService return getReader(context, progressListener, method, requestParams, Collections.emptyList(), Collections.emptyList()); } - private Reader getReader(Context context, ProgressListener progressListener, String method, HttpParams requestParams, String parameterName, Object parameterValue) throws Exception - { - return getReader(context, progressListener, method, requestParams, Collections.singletonList(parameterName), Collections.singletonList(parameterValue)); - } - private Reader getReader(Context context, ProgressListener progressListener, String method, HttpParams requestParams, List parameterNames, List parameterValues) throws Exception {