diff --git a/library/src/main/java/com/google/android/exoplayer/upstream/DefaultHttpDataSource.java b/library/src/main/java/com/google/android/exoplayer/upstream/DefaultHttpDataSource.java index 8e0a4b75ec..259ffb6214 100644 --- a/library/src/main/java/com/google/android/exoplayer/upstream/DefaultHttpDataSource.java +++ b/library/src/main/java/com/google/android/exoplayer/upstream/DefaultHttpDataSource.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer.upstream; import com.google.android.exoplayer.C; import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.Predicate; +import com.google.android.exoplayer.util.Util; import android.text.TextUtils; import android.util.Log; @@ -226,6 +227,7 @@ public class DefaultHttpDataSource implements HttpDataSource { public void close() throws HttpDataSourceException { try { if (inputStream != null) { + Util.maybeTerminateInputStream(connection, bytesRemaining()); try { inputStream.close(); } catch (IOException e) { diff --git a/library/src/main/java/com/google/android/exoplayer/util/Util.java b/library/src/main/java/com/google/android/exoplayer/util/Util.java index 5081532f7a..8319a7ad88 100644 --- a/library/src/main/java/com/google/android/exoplayer/util/Util.java +++ b/library/src/main/java/com/google/android/exoplayer/util/Util.java @@ -15,12 +15,16 @@ */ package com.google.android.exoplayer.util; +import com.google.android.exoplayer.C; import com.google.android.exoplayer.upstream.DataSource; import android.text.TextUtils; import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Method; import java.math.BigDecimal; +import java.net.HttpURLConnection; import java.net.URL; import java.text.ParseException; import java.util.Arrays; @@ -57,6 +61,8 @@ public final class Util { Pattern.compile("^(-)?P(([0-9]*)Y)?(([0-9]*)M)?(([0-9]*)D)?" + "(T(([0-9]*)H)?(([0-9]*)M)?(([0-9.]*)S)?)?$"); + private static final long MAX_BYTES_TO_DRAIN = 2048; + private Util() {} /** @@ -396,4 +402,48 @@ public final class Util { return intArray; } + /** + * On platform API levels 19 and 20, okhttp's implementation of {@link InputStream#close} can + * block for a long time if the stream has a lot of data remaining. Call this method before + * closing the input stream to make a best effort to cause the input stream to encounter an + * unexpected end of input, working around this issue. On other platform API levels, the method + * does nothing. + * + * @param connection The connection whose {@link InputStream} should be terminated. + * @param bytesRemaining The number of bytes remaining to be read from the input stream if its + * length is known. {@link C#LENGTH_UNBOUNDED} otherwise. + */ + public static void maybeTerminateInputStream(HttpURLConnection connection, long bytesRemaining) { + if (SDK_INT != 19 && SDK_INT != 20) { + return; + } + + try { + InputStream inputStream = connection.getInputStream(); + if (bytesRemaining == C.LENGTH_UNBOUNDED) { + // If the input stream has already ended, do nothing. The socket may be re-used. + if (inputStream.read() == -1) { + return; + } + } else if (bytesRemaining <= MAX_BYTES_TO_DRAIN) { + // There isn't much data left. Prefer to allow it to drain, which may allow the socket to be + // re-used. + return; + } + String className = inputStream.getClass().getName(); + if (className.equals("com.android.okhttp.internal.http.HttpTransport$ChunkedInputStream") + || className.equals( + "com.android.okhttp.internal.http.HttpTransport$FixedLengthInputStream")) { + Class superclass = inputStream.getClass().getSuperclass(); + Method unexpectedEndOfInput = superclass.getDeclaredMethod("unexpectedEndOfInput"); + unexpectedEndOfInput.setAccessible(true); + unexpectedEndOfInput.invoke(inputStream); + } + } catch (IOException e) { + // The connection didn't ever have an input stream, or it was closed already. + } catch (Exception e) { + // Something went wrong. The device probably isn't using okhttp. + } + } + }