mirror of
https://github.com/androidx/media.git
synced 2025-05-05 22:50:57 +08:00
commit
35e030f56b
@ -1,5 +1,17 @@
|
|||||||
# Release notes #
|
# Release notes #
|
||||||
|
|
||||||
|
### 2.10.1 ###
|
||||||
|
|
||||||
|
* Offline: Add option to remove all downloads.
|
||||||
|
* HLS: Fix `NullPointerException` when using HLS chunkless preparation
|
||||||
|
([#5868](https://github.com/google/ExoPlayer/issues/5868)).
|
||||||
|
* Fix handling of empty values and line terminators in SHOUTcast ICY metadata
|
||||||
|
([#5876](https://github.com/google/ExoPlayer/issues/5876)).
|
||||||
|
* Fix DVB subtitles for SDK 28
|
||||||
|
([#5862](https://github.com/google/ExoPlayer/issues/5862)).
|
||||||
|
* Add a workaround for a decoder failure on ZTE Axon7 mini devices when playing
|
||||||
|
48kHz audio ([#5821](https://github.com/google/ExoPlayer/issues/5821)).
|
||||||
|
|
||||||
### 2.10.0 ###
|
### 2.10.0 ###
|
||||||
|
|
||||||
* Core library:
|
* Core library:
|
||||||
|
@ -13,8 +13,8 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
project.ext {
|
project.ext {
|
||||||
// ExoPlayer version and version code.
|
// ExoPlayer version and version code.
|
||||||
releaseVersion = '2.10.0'
|
releaseVersion = '2.10.1'
|
||||||
releaseVersionCode = 2010000
|
releaseVersionCode = 2010001
|
||||||
minSdkVersion = 16
|
minSdkVersion = 16
|
||||||
targetSdkVersion = 28
|
targetSdkVersion = 28
|
||||||
compileSdkVersion = 28
|
compileSdkVersion = 28
|
||||||
|
@ -30,15 +30,12 @@ import com.google.android.exoplayer2.offline.DownloadIndex;
|
|||||||
import com.google.android.exoplayer2.offline.DownloadManager;
|
import com.google.android.exoplayer2.offline.DownloadManager;
|
||||||
import com.google.android.exoplayer2.offline.DownloadRequest;
|
import com.google.android.exoplayer2.offline.DownloadRequest;
|
||||||
import com.google.android.exoplayer2.offline.DownloadService;
|
import com.google.android.exoplayer2.offline.DownloadService;
|
||||||
import com.google.android.exoplayer2.offline.StreamKey;
|
|
||||||
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
|
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
|
||||||
import com.google.android.exoplayer2.upstream.DataSource;
|
import com.google.android.exoplayer2.upstream.DataSource;
|
||||||
import com.google.android.exoplayer2.util.Log;
|
import com.google.android.exoplayer2.util.Log;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.CopyOnWriteArraySet;
|
import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
|
|
||||||
/** Tracks media that has been downloaded. */
|
/** Tracks media that has been downloaded. */
|
||||||
@ -86,11 +83,9 @@ public class DownloadTracker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public List<StreamKey> getOfflineStreamKeys(Uri uri) {
|
public DownloadRequest getDownloadRequest(Uri uri) {
|
||||||
Download download = downloads.get(uri);
|
Download download = downloads.get(uri);
|
||||||
return download != null && download.state != Download.STATE_FAILED
|
return download != null && download.state != Download.STATE_FAILED ? download.request : null;
|
||||||
? download.request.streamKeys
|
|
||||||
: Collections.emptyList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void toggleDownload(
|
public void toggleDownload(
|
||||||
|
@ -45,7 +45,8 @@ import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
|
|||||||
import com.google.android.exoplayer2.drm.UnsupportedDrmException;
|
import com.google.android.exoplayer2.drm.UnsupportedDrmException;
|
||||||
import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer.DecoderInitializationException;
|
import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer.DecoderInitializationException;
|
||||||
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException;
|
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException;
|
||||||
import com.google.android.exoplayer2.offline.StreamKey;
|
import com.google.android.exoplayer2.offline.DownloadHelper;
|
||||||
|
import com.google.android.exoplayer2.offline.DownloadRequest;
|
||||||
import com.google.android.exoplayer2.source.BehindLiveWindowException;
|
import com.google.android.exoplayer2.source.BehindLiveWindowException;
|
||||||
import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
|
import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
|
||||||
import com.google.android.exoplayer2.source.MediaSource;
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
@ -75,7 +76,6 @@ import java.lang.reflect.Constructor;
|
|||||||
import java.net.CookieHandler;
|
import java.net.CookieHandler;
|
||||||
import java.net.CookieManager;
|
import java.net.CookieManager;
|
||||||
import java.net.CookiePolicy;
|
import java.net.CookiePolicy;
|
||||||
import java.util.List;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
/** An activity that plays media using {@link SimpleExoPlayer}. */
|
/** An activity that plays media using {@link SimpleExoPlayer}. */
|
||||||
@ -457,32 +457,25 @@ public class PlayerActivity extends AppCompatActivity
|
|||||||
}
|
}
|
||||||
|
|
||||||
private MediaSource buildMediaSource(Uri uri, @Nullable String overrideExtension) {
|
private MediaSource buildMediaSource(Uri uri, @Nullable String overrideExtension) {
|
||||||
|
DownloadRequest downloadRequest =
|
||||||
|
((DemoApplication) getApplication()).getDownloadTracker().getDownloadRequest(uri);
|
||||||
|
if (downloadRequest != null) {
|
||||||
|
return DownloadHelper.createMediaSource(downloadRequest, dataSourceFactory);
|
||||||
|
}
|
||||||
@ContentType int type = Util.inferContentType(uri, overrideExtension);
|
@ContentType int type = Util.inferContentType(uri, overrideExtension);
|
||||||
List<StreamKey> offlineStreamKeys = getOfflineStreamKeys(uri);
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case C.TYPE_DASH:
|
case C.TYPE_DASH:
|
||||||
return new DashMediaSource.Factory(dataSourceFactory)
|
return new DashMediaSource.Factory(dataSourceFactory).createMediaSource(uri);
|
||||||
.setStreamKeys(offlineStreamKeys)
|
|
||||||
.createMediaSource(uri);
|
|
||||||
case C.TYPE_SS:
|
case C.TYPE_SS:
|
||||||
return new SsMediaSource.Factory(dataSourceFactory)
|
return new SsMediaSource.Factory(dataSourceFactory).createMediaSource(uri);
|
||||||
.setStreamKeys(offlineStreamKeys)
|
|
||||||
.createMediaSource(uri);
|
|
||||||
case C.TYPE_HLS:
|
case C.TYPE_HLS:
|
||||||
return new HlsMediaSource.Factory(dataSourceFactory)
|
return new HlsMediaSource.Factory(dataSourceFactory).createMediaSource(uri);
|
||||||
.setStreamKeys(offlineStreamKeys)
|
|
||||||
.createMediaSource(uri);
|
|
||||||
case C.TYPE_OTHER:
|
case C.TYPE_OTHER:
|
||||||
return new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(uri);
|
return new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(uri);
|
||||||
default: {
|
default:
|
||||||
throw new IllegalStateException("Unsupported type: " + type);
|
throw new IllegalStateException("Unsupported type: " + type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private List<StreamKey> getOfflineStreamKeys(Uri uri) {
|
|
||||||
return ((DemoApplication) getApplication()).getDownloadTracker().getOfflineStreamKeys(uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
private DefaultDrmSessionManager<FrameworkMediaCrypto> buildDrmSessionManagerV18(
|
private DefaultDrmSessionManager<FrameworkMediaCrypto> buildDrmSessionManagerV18(
|
||||||
UUID uuid, String licenseUrl, String[] keyRequestPropertiesArray, boolean multiSession)
|
UUID uuid, String licenseUrl, String[] keyRequestPropertiesArray, boolean multiSession)
|
||||||
|
@ -113,7 +113,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
|
|||||||
|
|
||||||
private final CronetEngine cronetEngine;
|
private final CronetEngine cronetEngine;
|
||||||
private final Executor executor;
|
private final Executor executor;
|
||||||
private final Predicate<String> contentTypePredicate;
|
@Nullable private final Predicate<String> contentTypePredicate;
|
||||||
private final int connectTimeoutMs;
|
private final int connectTimeoutMs;
|
||||||
private final int readTimeoutMs;
|
private final int readTimeoutMs;
|
||||||
private final boolean resetTimeoutOnRedirects;
|
private final boolean resetTimeoutOnRedirects;
|
||||||
@ -146,6 +146,18 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
|
|||||||
|
|
||||||
private volatile long currentConnectTimeoutMs;
|
private volatile long currentConnectTimeoutMs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param cronetEngine A CronetEngine.
|
||||||
|
* @param executor The {@link java.util.concurrent.Executor} that will handle responses. This may
|
||||||
|
* be a direct executor (i.e. executes tasks on the calling thread) in order to avoid a thread
|
||||||
|
* hop from Cronet's internal network thread to the response handling thread. However, to
|
||||||
|
* avoid slowing down overall network performance, care must be taken to make sure response
|
||||||
|
* handling is a fast operation when using a direct executor.
|
||||||
|
*/
|
||||||
|
public CronetDataSource(CronetEngine cronetEngine, Executor executor) {
|
||||||
|
this(cronetEngine, executor, /* contentTypePredicate= */ null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param cronetEngine A CronetEngine.
|
* @param cronetEngine A CronetEngine.
|
||||||
* @param executor The {@link java.util.concurrent.Executor} that will handle responses. This may
|
* @param executor The {@link java.util.concurrent.Executor} that will handle responses. This may
|
||||||
@ -158,7 +170,9 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
|
|||||||
* #open(DataSpec)}.
|
* #open(DataSpec)}.
|
||||||
*/
|
*/
|
||||||
public CronetDataSource(
|
public CronetDataSource(
|
||||||
CronetEngine cronetEngine, Executor executor, Predicate<String> contentTypePredicate) {
|
CronetEngine cronetEngine,
|
||||||
|
Executor executor,
|
||||||
|
@Nullable Predicate<String> contentTypePredicate) {
|
||||||
this(
|
this(
|
||||||
cronetEngine,
|
cronetEngine,
|
||||||
executor,
|
executor,
|
||||||
@ -188,7 +202,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
|
|||||||
public CronetDataSource(
|
public CronetDataSource(
|
||||||
CronetEngine cronetEngine,
|
CronetEngine cronetEngine,
|
||||||
Executor executor,
|
Executor executor,
|
||||||
Predicate<String> contentTypePredicate,
|
@Nullable Predicate<String> contentTypePredicate,
|
||||||
int connectTimeoutMs,
|
int connectTimeoutMs,
|
||||||
int readTimeoutMs,
|
int readTimeoutMs,
|
||||||
boolean resetTimeoutOnRedirects,
|
boolean resetTimeoutOnRedirects,
|
||||||
@ -225,7 +239,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
|
|||||||
public CronetDataSource(
|
public CronetDataSource(
|
||||||
CronetEngine cronetEngine,
|
CronetEngine cronetEngine,
|
||||||
Executor executor,
|
Executor executor,
|
||||||
Predicate<String> contentTypePredicate,
|
@Nullable Predicate<String> contentTypePredicate,
|
||||||
int connectTimeoutMs,
|
int connectTimeoutMs,
|
||||||
int readTimeoutMs,
|
int readTimeoutMs,
|
||||||
boolean resetTimeoutOnRedirects,
|
boolean resetTimeoutOnRedirects,
|
||||||
@ -246,7 +260,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
|
|||||||
/* package */ CronetDataSource(
|
/* package */ CronetDataSource(
|
||||||
CronetEngine cronetEngine,
|
CronetEngine cronetEngine,
|
||||||
Executor executor,
|
Executor executor,
|
||||||
Predicate<String> contentTypePredicate,
|
@Nullable Predicate<String> contentTypePredicate,
|
||||||
int connectTimeoutMs,
|
int connectTimeoutMs,
|
||||||
int readTimeoutMs,
|
int readTimeoutMs,
|
||||||
boolean resetTimeoutOnRedirects,
|
boolean resetTimeoutOnRedirects,
|
||||||
|
@ -948,8 +948,8 @@ public final class ImaAdsLoader
|
|||||||
@Override
|
@Override
|
||||||
public void onTimelineChanged(
|
public void onTimelineChanged(
|
||||||
Timeline timeline, @Nullable Object manifest, @Player.TimelineChangeReason int reason) {
|
Timeline timeline, @Nullable Object manifest, @Player.TimelineChangeReason int reason) {
|
||||||
if (reason == Player.TIMELINE_CHANGE_REASON_RESET) {
|
if (timeline.isEmpty()) {
|
||||||
// The player is being reset and this source will be released.
|
// The player is being reset or contains no media.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Assertions.checkArgument(timeline.getPeriodCount() == 1);
|
Assertions.checkArgument(timeline.getPeriodCount() == 1);
|
||||||
|
@ -73,6 +73,15 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
|
|||||||
private long bytesSkipped;
|
private long bytesSkipped;
|
||||||
private long bytesRead;
|
private long bytesRead;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param callFactory A {@link Call.Factory} (typically an {@link okhttp3.OkHttpClient}) for use
|
||||||
|
* by the source.
|
||||||
|
* @param userAgent An optional User-Agent string.
|
||||||
|
*/
|
||||||
|
public OkHttpDataSource(Call.Factory callFactory, @Nullable String userAgent) {
|
||||||
|
this(callFactory, userAgent, /* contentTypePredicate= */ null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param callFactory A {@link Call.Factory} (typically an {@link okhttp3.OkHttpClient}) for use
|
* @param callFactory A {@link Call.Factory} (typically an {@link okhttp3.OkHttpClient}) for use
|
||||||
* by the source.
|
* by the source.
|
||||||
|
@ -3,3 +3,4 @@ android.useAndroidX=true
|
|||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
android.enableUnitTestBinaryResources=true
|
android.enableUnitTestBinaryResources=true
|
||||||
buildDir=buildout
|
buildDir=buildout
|
||||||
|
org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m
|
||||||
|
@ -18,10 +18,13 @@ android.libraryVariants.all { variant ->
|
|||||||
if (!name.equals("release")) {
|
if (!name.equals("release")) {
|
||||||
return; // Skip non-release builds.
|
return; // Skip non-release builds.
|
||||||
}
|
}
|
||||||
|
def allSourceDirs = variant.sourceSets.inject ([]) {
|
||||||
|
acc, val -> acc << val.javaDirectories
|
||||||
|
}
|
||||||
task("generateJavadoc", type: Javadoc) {
|
task("generateJavadoc", type: Javadoc) {
|
||||||
description = "Generates Javadoc for the ${javadocTitle}."
|
description = "Generates Javadoc for the ${javadocTitle}."
|
||||||
title = "ExoPlayer ${javadocTitle}"
|
title = "ExoPlayer ${javadocTitle}"
|
||||||
source = variant.javaCompileProvider.get().source
|
source = allSourceDirs
|
||||||
options {
|
options {
|
||||||
links "http://docs.oracle.com/javase/7/docs/api/"
|
links "http://docs.oracle.com/javase/7/docs/api/"
|
||||||
linksOffline "https://developer.android.com/reference",
|
linksOffline "https://developer.android.com/reference",
|
||||||
|
@ -46,18 +46,21 @@
|
|||||||
|
|
||||||
# Constructors accessed via reflection in DownloadHelper
|
# Constructors accessed via reflection in DownloadHelper
|
||||||
-dontnote com.google.android.exoplayer2.source.dash.DashMediaSource$Factory
|
-dontnote com.google.android.exoplayer2.source.dash.DashMediaSource$Factory
|
||||||
-keepclassmembers class com.google.android.exoplayer2.source.dash.DashMediaSource$Factory {
|
-keepclasseswithmembers class com.google.android.exoplayer2.source.dash.DashMediaSource$Factory {
|
||||||
<init>(com.google.android.exoplayer2.upstream.DataSource$Factory);
|
<init>(com.google.android.exoplayer2.upstream.DataSource$Factory);
|
||||||
|
** setStreamKeys(java.util.List);
|
||||||
com.google.android.exoplayer2.source.dash.DashMediaSource createMediaSource(android.net.Uri);
|
com.google.android.exoplayer2.source.dash.DashMediaSource createMediaSource(android.net.Uri);
|
||||||
}
|
}
|
||||||
-dontnote com.google.android.exoplayer2.source.hls.HlsMediaSource$Factory
|
-dontnote com.google.android.exoplayer2.source.hls.HlsMediaSource$Factory
|
||||||
-keepclassmembers class com.google.android.exoplayer2.source.hls.HlsMediaSource$Factory {
|
-keepclasseswithmembers class com.google.android.exoplayer2.source.hls.HlsMediaSource$Factory {
|
||||||
<init>(com.google.android.exoplayer2.upstream.DataSource$Factory);
|
<init>(com.google.android.exoplayer2.upstream.DataSource$Factory);
|
||||||
|
** setStreamKeys(java.util.List);
|
||||||
com.google.android.exoplayer2.source.hls.HlsMediaSource createMediaSource(android.net.Uri);
|
com.google.android.exoplayer2.source.hls.HlsMediaSource createMediaSource(android.net.Uri);
|
||||||
}
|
}
|
||||||
-dontnote com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource$Factory
|
-dontnote com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource$Factory
|
||||||
-keepclassmembers class com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource$Factory {
|
-keepclasseswithmembers class com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource$Factory {
|
||||||
<init>(com.google.android.exoplayer2.upstream.DataSource$Factory);
|
<init>(com.google.android.exoplayer2.upstream.DataSource$Factory);
|
||||||
|
** setStreamKeys(java.util.List);
|
||||||
com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource createMediaSource(android.net.Uri);
|
com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource createMediaSource(android.net.Uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1053,12 +1053,15 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
&& nextInfo.resolvedPeriodIndex == currentPeriodIndex
|
&& nextInfo.resolvedPeriodIndex == currentPeriodIndex
|
||||||
&& nextInfo.resolvedPeriodTimeUs > oldPeriodPositionUs
|
&& nextInfo.resolvedPeriodTimeUs > oldPeriodPositionUs
|
||||||
&& nextInfo.resolvedPeriodTimeUs <= newPeriodPositionUs) {
|
&& nextInfo.resolvedPeriodTimeUs <= newPeriodPositionUs) {
|
||||||
|
try {
|
||||||
sendMessageToTarget(nextInfo.message);
|
sendMessageToTarget(nextInfo.message);
|
||||||
|
} finally {
|
||||||
if (nextInfo.message.getDeleteAfterDelivery() || nextInfo.message.isCanceled()) {
|
if (nextInfo.message.getDeleteAfterDelivery() || nextInfo.message.isCanceled()) {
|
||||||
pendingMessages.remove(nextPendingMessageIndex);
|
pendingMessages.remove(nextPendingMessageIndex);
|
||||||
} else {
|
} else {
|
||||||
nextPendingMessageIndex++;
|
nextPendingMessageIndex++;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
nextInfo =
|
nextInfo =
|
||||||
nextPendingMessageIndex < pendingMessages.size()
|
nextPendingMessageIndex < pendingMessages.size()
|
||||||
? pendingMessages.get(nextPendingMessageIndex)
|
? pendingMessages.get(nextPendingMessageIndex)
|
||||||
|
@ -29,11 +29,11 @@ public final class ExoPlayerLibraryInfo {
|
|||||||
|
|
||||||
/** The version of the library expressed as a string, for example "1.2.3". */
|
/** The version of the library expressed as a string, for example "1.2.3". */
|
||||||
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa.
|
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa.
|
||||||
public static final String VERSION = "2.10.0";
|
public static final String VERSION = "2.10.1";
|
||||||
|
|
||||||
/** The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}. */
|
/** The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}. */
|
||||||
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
|
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
|
||||||
public static final String VERSION_SLASHY = "ExoPlayerLib/2.10.0";
|
public static final String VERSION_SLASHY = "ExoPlayerLib/2.10.1";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The version of the library expressed as an integer, for example 1002003.
|
* The version of the library expressed as an integer, for example 1002003.
|
||||||
@ -43,7 +43,7 @@ public final class ExoPlayerLibraryInfo {
|
|||||||
* integer version 123045006 (123-045-006).
|
* integer version 123045006 (123-045-006).
|
||||||
*/
|
*/
|
||||||
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
|
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
|
||||||
public static final int VERSION_INT = 2010000;
|
public static final int VERSION_INT = 2010001;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions}
|
* Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions}
|
||||||
|
@ -1231,7 +1231,7 @@ public class SimpleExoPlayer extends BasePlayer
|
|||||||
Log.w(
|
Log.w(
|
||||||
TAG,
|
TAG,
|
||||||
"Player is accessed on the wrong thread. See "
|
"Player is accessed on the wrong thread. See "
|
||||||
+ "https://exoplayer.dev/faqs.html#"
|
+ "https://exoplayer.dev/troubleshooting.html#"
|
||||||
+ "what-do-player-is-accessed-on-the-wrong-thread-warnings-mean",
|
+ "what-do-player-is-accessed-on-the-wrong-thread-warnings-mean",
|
||||||
hasNotifiedFullWrongThreadWarning ? null : new IllegalStateException());
|
hasNotifiedFullWrongThreadWarning ? null : new IllegalStateException());
|
||||||
hasNotifiedFullWrongThreadWarning = true;
|
hasNotifiedFullWrongThreadWarning = true;
|
||||||
|
@ -59,7 +59,7 @@ public interface AnalyticsListener {
|
|||||||
public final Timeline timeline;
|
public final Timeline timeline;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Window index in the {@code timeline} this event belongs to, or the prospective window index
|
* Window index in the {@link #timeline} this event belongs to, or the prospective window index
|
||||||
* if the timeline is not yet known and empty.
|
* if the timeline is not yet known and empty.
|
||||||
*/
|
*/
|
||||||
public final int windowIndex;
|
public final int windowIndex;
|
||||||
@ -76,7 +76,7 @@ public interface AnalyticsListener {
|
|||||||
public final long eventPlaybackPositionMs;
|
public final long eventPlaybackPositionMs;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Position in the current timeline window ({@code timeline.getCurrentWindowIndex()} or the
|
* Position in the current timeline window ({@link Player#getCurrentWindowIndex()}) or the
|
||||||
* currently playing ad at the time of the event, in milliseconds.
|
* currently playing ad at the time of the event, in milliseconds.
|
||||||
*/
|
*/
|
||||||
public final long currentPlaybackPositionMs;
|
public final long currentPlaybackPositionMs;
|
||||||
@ -91,15 +91,15 @@ public interface AnalyticsListener {
|
|||||||
* @param realtimeMs Elapsed real-time as returned by {@code SystemClock.elapsedRealtime()} at
|
* @param realtimeMs Elapsed real-time as returned by {@code SystemClock.elapsedRealtime()} at
|
||||||
* the time of the event, in milliseconds.
|
* the time of the event, in milliseconds.
|
||||||
* @param timeline Timeline at the time of the event.
|
* @param timeline Timeline at the time of the event.
|
||||||
* @param windowIndex Window index in the {@code timeline} this event belongs to, or the
|
* @param windowIndex Window index in the {@link #timeline} this event belongs to, or the
|
||||||
* prospective window index if the timeline is not yet known and empty.
|
* prospective window index if the timeline is not yet known and empty.
|
||||||
* @param mediaPeriodId Media period identifier for the media period this event belongs to, or
|
* @param mediaPeriodId Media period identifier for the media period this event belongs to, or
|
||||||
* {@code null} if the event is not associated with a specific media period.
|
* {@code null} if the event is not associated with a specific media period.
|
||||||
* @param eventPlaybackPositionMs Position in the window or ad this event belongs to at the time
|
* @param eventPlaybackPositionMs Position in the window or ad this event belongs to at the time
|
||||||
* of the event, in milliseconds.
|
* of the event, in milliseconds.
|
||||||
* @param currentPlaybackPositionMs Position in the current timeline window ({@code
|
* @param currentPlaybackPositionMs Position in the current timeline window ({@link
|
||||||
* timeline.getCurrentWindowIndex()} or the currently playing ad at the time of the event,
|
* Player#getCurrentWindowIndex()}) or the currently playing ad at the time of the event, in
|
||||||
* in milliseconds.
|
* milliseconds.
|
||||||
* @param totalBufferedDurationMs Total buffered duration from {@link
|
* @param totalBufferedDurationMs Total buffered duration from {@link
|
||||||
* #currentPlaybackPositionMs} at the time of the event, in milliseconds. This includes
|
* #currentPlaybackPositionMs} at the time of the event, in milliseconds. This includes
|
||||||
* pre-buffered data for subsequent ads and windows.
|
* pre-buffered data for subsequent ads and windows.
|
||||||
|
@ -786,7 +786,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
|||||||
// Set codec configuration values.
|
// Set codec configuration values.
|
||||||
if (Util.SDK_INT >= 23) {
|
if (Util.SDK_INT >= 23) {
|
||||||
mediaFormat.setInteger(MediaFormat.KEY_PRIORITY, 0 /* realtime priority */);
|
mediaFormat.setInteger(MediaFormat.KEY_PRIORITY, 0 /* realtime priority */);
|
||||||
if (codecOperatingRate != CODEC_OPERATING_RATE_UNSET) {
|
if (codecOperatingRate != CODEC_OPERATING_RATE_UNSET && !deviceDoesntSupportOperatingRate()) {
|
||||||
mediaFormat.setFloat(MediaFormat.KEY_OPERATING_RATE, codecOperatingRate);
|
mediaFormat.setFloat(MediaFormat.KEY_OPERATING_RATE, codecOperatingRate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -809,6 +809,17 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the device's decoders are known to not support setting the codec operating
|
||||||
|
* rate.
|
||||||
|
*
|
||||||
|
* <p>See <a href="https://github.com/google/ExoPlayer/issues/5821">GitHub issue #5821</a>.
|
||||||
|
*/
|
||||||
|
private static boolean deviceDoesntSupportOperatingRate() {
|
||||||
|
return Util.SDK_INT == 23
|
||||||
|
&& ("ZTE B2017G".equals(Util.MODEL) || "AXON 7 mini".equals(Util.MODEL));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the decoder is known to output six audio channels when provided with input with
|
* Returns whether the decoder is known to output six audio channels when provided with input with
|
||||||
* fewer than six channels.
|
* fewer than six channels.
|
||||||
|
@ -60,7 +60,7 @@ import java.util.List;
|
|||||||
* The threshold number of samples to trim from the start/end of an audio track when applying an
|
* The threshold number of samples to trim from the start/end of an audio track when applying an
|
||||||
* edit below which gapless info can be used (rather than removing samples from the sample table).
|
* edit below which gapless info can be used (rather than removing samples from the sample table).
|
||||||
*/
|
*/
|
||||||
private static final int MAX_GAPLESS_TRIM_SIZE_SAMPLES = 3;
|
private static final int MAX_GAPLESS_TRIM_SIZE_SAMPLES = 4;
|
||||||
|
|
||||||
/** The magic signature for an Opus Identification header, as defined in RFC-7845. */
|
/** The magic signature for an Opus Identification header, as defined in RFC-7845. */
|
||||||
private static final byte[] opusMagic = Util.getUtf8Bytes("OpusHead");
|
private static final byte[] opusMagic = Util.getUtf8Bytes("OpusHead");
|
||||||
|
@ -31,7 +31,7 @@ public final class IcyDecoder implements MetadataDecoder {
|
|||||||
|
|
||||||
private static final String TAG = "IcyDecoder";
|
private static final String TAG = "IcyDecoder";
|
||||||
|
|
||||||
private static final Pattern METADATA_ELEMENT = Pattern.compile("(.+?)='(.+?)';");
|
private static final Pattern METADATA_ELEMENT = Pattern.compile("(.+?)='(.*?)';", Pattern.DOTALL);
|
||||||
private static final String STREAM_KEY_NAME = "streamtitle";
|
private static final String STREAM_KEY_NAME = "streamtitle";
|
||||||
private static final String STREAM_KEY_URL = "streamurl";
|
private static final String STREAM_KEY_URL = "streamurl";
|
||||||
|
|
||||||
|
@ -233,6 +233,19 @@ public final class DefaultDownloadIndex implements WritableDownloadIndex {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setStatesToRemoving() throws DatabaseIOException {
|
||||||
|
ensureInitialized();
|
||||||
|
try {
|
||||||
|
ContentValues values = new ContentValues();
|
||||||
|
values.put(COLUMN_STATE, Download.STATE_REMOVING);
|
||||||
|
SQLiteDatabase writableDatabase = databaseProvider.getWritableDatabase();
|
||||||
|
writableDatabase.update(tableName, values, /* whereClause= */ null, /* whereArgs= */ null);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DatabaseIOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setStopReason(int stopReason) throws DatabaseIOException {
|
public void setStopReason(int stopReason) throws DatabaseIOException {
|
||||||
ensureInitialized();
|
ensureInitialized();
|
||||||
|
@ -20,7 +20,6 @@ import android.os.Handler;
|
|||||||
import android.os.HandlerThread;
|
import android.os.HandlerThread;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import android.util.Pair;
|
|
||||||
import android.util.SparseIntArray;
|
import android.util.SparseIntArray;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
@ -32,6 +31,7 @@ import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
|
|||||||
import com.google.android.exoplayer2.source.MediaPeriod;
|
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||||
import com.google.android.exoplayer2.source.MediaSource;
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
|
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
|
||||||
|
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
|
||||||
import com.google.android.exoplayer2.source.TrackGroup;
|
import com.google.android.exoplayer2.source.TrackGroup;
|
||||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||||
import com.google.android.exoplayer2.trackselection.BaseTrackSelection;
|
import com.google.android.exoplayer2.trackselection.BaseTrackSelection;
|
||||||
@ -44,6 +44,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
|
|||||||
import com.google.android.exoplayer2.upstream.Allocator;
|
import com.google.android.exoplayer2.upstream.Allocator;
|
||||||
import com.google.android.exoplayer2.upstream.BandwidthMeter;
|
import com.google.android.exoplayer2.upstream.BandwidthMeter;
|
||||||
import com.google.android.exoplayer2.upstream.DataSource;
|
import com.google.android.exoplayer2.upstream.DataSource;
|
||||||
|
import com.google.android.exoplayer2.upstream.DataSource.Factory;
|
||||||
import com.google.android.exoplayer2.upstream.DefaultAllocator;
|
import com.google.android.exoplayer2.upstream.DefaultAllocator;
|
||||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
import com.google.android.exoplayer2.upstream.TransferListener;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
@ -106,30 +107,13 @@ public final class DownloadHelper {
|
|||||||
void onPrepareError(DownloadHelper helper, IOException e);
|
void onPrepareError(DownloadHelper helper, IOException e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable private static final Constructor<?> DASH_FACTORY_CONSTRUCTOR;
|
private static final MediaSourceFactory DASH_FACTORY =
|
||||||
@Nullable private static final Constructor<?> HLS_FACTORY_CONSTRUCTOR;
|
getMediaSourceFactory("com.google.android.exoplayer2.source.dash.DashMediaSource$Factory");
|
||||||
@Nullable private static final Constructor<?> SS_FACTORY_CONSTRUCTOR;
|
private static final MediaSourceFactory SS_FACTORY =
|
||||||
@Nullable private static final Method DASH_FACTORY_CREATE_METHOD;
|
getMediaSourceFactory(
|
||||||
@Nullable private static final Method HLS_FACTORY_CREATE_METHOD;
|
|
||||||
@Nullable private static final Method SS_FACTORY_CREATE_METHOD;
|
|
||||||
|
|
||||||
static {
|
|
||||||
Pair<@NullableType Constructor<?>, @NullableType Method> dashFactoryMethods =
|
|
||||||
getMediaSourceFactoryMethods(
|
|
||||||
"com.google.android.exoplayer2.source.dash.DashMediaSource$Factory");
|
|
||||||
DASH_FACTORY_CONSTRUCTOR = dashFactoryMethods.first;
|
|
||||||
DASH_FACTORY_CREATE_METHOD = dashFactoryMethods.second;
|
|
||||||
Pair<@NullableType Constructor<?>, @NullableType Method> hlsFactoryMethods =
|
|
||||||
getMediaSourceFactoryMethods(
|
|
||||||
"com.google.android.exoplayer2.source.hls.HlsMediaSource$Factory");
|
|
||||||
HLS_FACTORY_CONSTRUCTOR = hlsFactoryMethods.first;
|
|
||||||
HLS_FACTORY_CREATE_METHOD = hlsFactoryMethods.second;
|
|
||||||
Pair<@NullableType Constructor<?>, @NullableType Method> ssFactoryMethods =
|
|
||||||
getMediaSourceFactoryMethods(
|
|
||||||
"com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource$Factory");
|
"com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource$Factory");
|
||||||
SS_FACTORY_CONSTRUCTOR = ssFactoryMethods.first;
|
private static final MediaSourceFactory HLS_FACTORY =
|
||||||
SS_FACTORY_CREATE_METHOD = ssFactoryMethods.second;
|
getMediaSourceFactory("com.google.android.exoplayer2.source.hls.HlsMediaSource$Factory");
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@link DownloadHelper} for progressive streams.
|
* Creates a {@link DownloadHelper} for progressive streams.
|
||||||
@ -202,8 +186,7 @@ public final class DownloadHelper {
|
|||||||
DownloadRequest.TYPE_DASH,
|
DownloadRequest.TYPE_DASH,
|
||||||
uri,
|
uri,
|
||||||
/* cacheKey= */ null,
|
/* cacheKey= */ null,
|
||||||
createMediaSource(
|
DASH_FACTORY.createMediaSource(uri, dataSourceFactory, /* streamKeys= */ null),
|
||||||
uri, dataSourceFactory, DASH_FACTORY_CONSTRUCTOR, DASH_FACTORY_CREATE_METHOD),
|
|
||||||
trackSelectorParameters,
|
trackSelectorParameters,
|
||||||
Util.getRendererCapabilities(renderersFactory, drmSessionManager));
|
Util.getRendererCapabilities(renderersFactory, drmSessionManager));
|
||||||
}
|
}
|
||||||
@ -252,8 +235,7 @@ public final class DownloadHelper {
|
|||||||
DownloadRequest.TYPE_HLS,
|
DownloadRequest.TYPE_HLS,
|
||||||
uri,
|
uri,
|
||||||
/* cacheKey= */ null,
|
/* cacheKey= */ null,
|
||||||
createMediaSource(
|
HLS_FACTORY.createMediaSource(uri, dataSourceFactory, /* streamKeys= */ null),
|
||||||
uri, dataSourceFactory, HLS_FACTORY_CONSTRUCTOR, HLS_FACTORY_CREATE_METHOD),
|
|
||||||
trackSelectorParameters,
|
trackSelectorParameters,
|
||||||
Util.getRendererCapabilities(renderersFactory, drmSessionManager));
|
Util.getRendererCapabilities(renderersFactory, drmSessionManager));
|
||||||
}
|
}
|
||||||
@ -302,11 +284,42 @@ public final class DownloadHelper {
|
|||||||
DownloadRequest.TYPE_SS,
|
DownloadRequest.TYPE_SS,
|
||||||
uri,
|
uri,
|
||||||
/* cacheKey= */ null,
|
/* cacheKey= */ null,
|
||||||
createMediaSource(uri, dataSourceFactory, SS_FACTORY_CONSTRUCTOR, SS_FACTORY_CREATE_METHOD),
|
SS_FACTORY.createMediaSource(uri, dataSourceFactory, /* streamKeys= */ null),
|
||||||
trackSelectorParameters,
|
trackSelectorParameters,
|
||||||
Util.getRendererCapabilities(renderersFactory, drmSessionManager));
|
Util.getRendererCapabilities(renderersFactory, drmSessionManager));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility method to create a MediaSource which only contains the tracks defined in {@code
|
||||||
|
* downloadRequest}.
|
||||||
|
*
|
||||||
|
* @param downloadRequest A {@link DownloadRequest}.
|
||||||
|
* @param dataSourceFactory A factory for {@link DataSource}s to read the media.
|
||||||
|
* @return A MediaSource which only contains the tracks defined in {@code downloadRequest}.
|
||||||
|
*/
|
||||||
|
public static MediaSource createMediaSource(
|
||||||
|
DownloadRequest downloadRequest, DataSource.Factory dataSourceFactory) {
|
||||||
|
MediaSourceFactory factory;
|
||||||
|
switch (downloadRequest.type) {
|
||||||
|
case DownloadRequest.TYPE_DASH:
|
||||||
|
factory = DASH_FACTORY;
|
||||||
|
break;
|
||||||
|
case DownloadRequest.TYPE_SS:
|
||||||
|
factory = SS_FACTORY;
|
||||||
|
break;
|
||||||
|
case DownloadRequest.TYPE_HLS:
|
||||||
|
factory = HLS_FACTORY;
|
||||||
|
break;
|
||||||
|
case DownloadRequest.TYPE_PROGRESSIVE:
|
||||||
|
return new ProgressiveMediaSource.Factory(dataSourceFactory)
|
||||||
|
.createMediaSource(downloadRequest.uri);
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("Unsupported type: " + downloadRequest.type);
|
||||||
|
}
|
||||||
|
return factory.createMediaSource(
|
||||||
|
downloadRequest.uri, dataSourceFactory, downloadRequest.streamKeys);
|
||||||
|
}
|
||||||
|
|
||||||
private final String downloadType;
|
private final String downloadType;
|
||||||
private final Uri uri;
|
private final Uri uri;
|
||||||
@Nullable private final String cacheKey;
|
@Nullable private final String cacheKey;
|
||||||
@ -739,37 +752,56 @@ public final class DownloadHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Pair<@NullableType Constructor<?>, @NullableType Method>
|
private static MediaSourceFactory getMediaSourceFactory(String className) {
|
||||||
getMediaSourceFactoryMethods(String className) {
|
|
||||||
Constructor<?> constructor = null;
|
Constructor<?> constructor = null;
|
||||||
|
Method setStreamKeysMethod = null;
|
||||||
Method createMethod = null;
|
Method createMethod = null;
|
||||||
try {
|
try {
|
||||||
// LINT.IfChange
|
// LINT.IfChange
|
||||||
Class<?> factoryClazz = Class.forName(className);
|
Class<?> factoryClazz = Class.forName(className);
|
||||||
constructor = factoryClazz.getConstructor(DataSource.Factory.class);
|
constructor = factoryClazz.getConstructor(Factory.class);
|
||||||
|
setStreamKeysMethod = factoryClazz.getMethod("setStreamKeys", List.class);
|
||||||
createMethod = factoryClazz.getMethod("createMediaSource", Uri.class);
|
createMethod = factoryClazz.getMethod("createMediaSource", Uri.class);
|
||||||
// LINT.ThenChange(../../../../../../../../proguard-rules.txt)
|
// LINT.ThenChange(../../../../../../../../proguard-rules.txt)
|
||||||
} catch (Exception e) {
|
} catch (ClassNotFoundException e) {
|
||||||
// Expected if the app was built without the respective module.
|
// Expected if the app was built without the respective module.
|
||||||
|
} catch (NoSuchMethodException | SecurityException e) {
|
||||||
|
// Something is wrong with the library or the proguard configuration.
|
||||||
|
throw new IllegalStateException(e);
|
||||||
}
|
}
|
||||||
return Pair.create(constructor, createMethod);
|
return new MediaSourceFactory(constructor, setStreamKeysMethod, createMethod);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static MediaSource createMediaSource(
|
private static final class MediaSourceFactory {
|
||||||
Uri uri,
|
@Nullable private final Constructor<?> constructor;
|
||||||
DataSource.Factory dataSourceFactory,
|
@Nullable private final Method setStreamKeysMethod;
|
||||||
@Nullable Constructor<?> factoryConstructor,
|
@Nullable private final Method createMethod;
|
||||||
@Nullable Method createMediaSourceMethod) {
|
|
||||||
if (factoryConstructor == null || createMediaSourceMethod == null) {
|
public MediaSourceFactory(
|
||||||
|
@Nullable Constructor<?> constructor,
|
||||||
|
@Nullable Method setStreamKeysMethod,
|
||||||
|
@Nullable Method createMethod) {
|
||||||
|
this.constructor = constructor;
|
||||||
|
this.setStreamKeysMethod = setStreamKeysMethod;
|
||||||
|
this.createMethod = createMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MediaSource createMediaSource(
|
||||||
|
Uri uri, Factory dataSourceFactory, @Nullable List<StreamKey> streamKeys) {
|
||||||
|
if (constructor == null || setStreamKeysMethod == null || createMethod == null) {
|
||||||
throw new IllegalStateException("Module missing to create media source.");
|
throw new IllegalStateException("Module missing to create media source.");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
Object factory = factoryConstructor.newInstance(dataSourceFactory);
|
Object factory = constructor.newInstance(dataSourceFactory);
|
||||||
return (MediaSource) Assertions.checkNotNull(createMediaSourceMethod.invoke(factory, uri));
|
if (streamKeys != null) {
|
||||||
|
setStreamKeysMethod.invoke(factory, streamKeys);
|
||||||
|
}
|
||||||
|
return (MediaSource) Assertions.checkNotNull(createMethod.invoke(factory, uri));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new IllegalStateException("Failed to instantiate media source.", e);
|
throw new IllegalStateException("Failed to instantiate media source.", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static final class MediaPreparer
|
private static final class MediaPreparer
|
||||||
implements MediaSource.SourceInfoRefreshListener, MediaPeriod.Callback, Handler.Callback {
|
implements MediaSource.SourceInfoRefreshListener, MediaPeriod.Callback, Handler.Callback {
|
||||||
|
@ -133,10 +133,11 @@ public final class DownloadManager {
|
|||||||
private static final int MSG_SET_MIN_RETRY_COUNT = 5;
|
private static final int MSG_SET_MIN_RETRY_COUNT = 5;
|
||||||
private static final int MSG_ADD_DOWNLOAD = 6;
|
private static final int MSG_ADD_DOWNLOAD = 6;
|
||||||
private static final int MSG_REMOVE_DOWNLOAD = 7;
|
private static final int MSG_REMOVE_DOWNLOAD = 7;
|
||||||
private static final int MSG_TASK_STOPPED = 8;
|
private static final int MSG_REMOVE_ALL_DOWNLOADS = 8;
|
||||||
private static final int MSG_CONTENT_LENGTH_CHANGED = 9;
|
private static final int MSG_TASK_STOPPED = 9;
|
||||||
private static final int MSG_UPDATE_PROGRESS = 10;
|
private static final int MSG_CONTENT_LENGTH_CHANGED = 10;
|
||||||
private static final int MSG_RELEASE = 11;
|
private static final int MSG_UPDATE_PROGRESS = 11;
|
||||||
|
private static final int MSG_RELEASE = 12;
|
||||||
|
|
||||||
private static final String TAG = "DownloadManager";
|
private static final String TAG = "DownloadManager";
|
||||||
|
|
||||||
@ -446,6 +447,12 @@ public final class DownloadManager {
|
|||||||
internalHandler.obtainMessage(MSG_REMOVE_DOWNLOAD, id).sendToTarget();
|
internalHandler.obtainMessage(MSG_REMOVE_DOWNLOAD, id).sendToTarget();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Cancels all pending downloads and removes all downloaded data. */
|
||||||
|
public void removeAllDownloads() {
|
||||||
|
pendingMessages++;
|
||||||
|
internalHandler.obtainMessage(MSG_REMOVE_ALL_DOWNLOADS).sendToTarget();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stops the downloads and releases resources. Waits until the downloads are persisted to the
|
* Stops the downloads and releases resources. Waits until the downloads are persisted to the
|
||||||
* download index. The manager must not be accessed after this method has been called.
|
* download index. The manager must not be accessed after this method has been called.
|
||||||
@ -652,6 +659,9 @@ public final class DownloadManager {
|
|||||||
id = (String) message.obj;
|
id = (String) message.obj;
|
||||||
removeDownload(id);
|
removeDownload(id);
|
||||||
break;
|
break;
|
||||||
|
case MSG_REMOVE_ALL_DOWNLOADS:
|
||||||
|
removeAllDownloads();
|
||||||
|
break;
|
||||||
case MSG_TASK_STOPPED:
|
case MSG_TASK_STOPPED:
|
||||||
Task task = (Task) message.obj;
|
Task task = (Task) message.obj;
|
||||||
onTaskStopped(task);
|
onTaskStopped(task);
|
||||||
@ -797,6 +807,36 @@ public final class DownloadManager {
|
|||||||
syncTasks();
|
syncTasks();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void removeAllDownloads() {
|
||||||
|
List<Download> terminalDownloads = new ArrayList<>();
|
||||||
|
try (DownloadCursor cursor = downloadIndex.getDownloads(STATE_COMPLETED, STATE_FAILED)) {
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
terminalDownloads.add(cursor.getDownload());
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Failed to load downloads.");
|
||||||
|
}
|
||||||
|
for (int i = 0; i < downloads.size(); i++) {
|
||||||
|
downloads.set(i, copyDownloadWithState(downloads.get(i), STATE_REMOVING));
|
||||||
|
}
|
||||||
|
for (int i = 0; i < terminalDownloads.size(); i++) {
|
||||||
|
downloads.add(copyDownloadWithState(terminalDownloads.get(i), STATE_REMOVING));
|
||||||
|
}
|
||||||
|
Collections.sort(downloads, InternalHandler::compareStartTimes);
|
||||||
|
try {
|
||||||
|
downloadIndex.setStatesToRemoving();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Failed to update index.", e);
|
||||||
|
}
|
||||||
|
ArrayList<Download> updateList = new ArrayList<>(downloads);
|
||||||
|
for (int i = 0; i < downloads.size(); i++) {
|
||||||
|
DownloadUpdate update =
|
||||||
|
new DownloadUpdate(downloads.get(i), /* isRemove= */ false, updateList);
|
||||||
|
mainHandler.obtainMessage(MSG_DOWNLOAD_UPDATE, update).sendToTarget();
|
||||||
|
}
|
||||||
|
syncTasks();
|
||||||
|
}
|
||||||
|
|
||||||
private void release() {
|
private void release() {
|
||||||
for (Task task : activeTasks.values()) {
|
for (Task task : activeTasks.values()) {
|
||||||
task.cancel(/* released= */ true);
|
task.cancel(/* released= */ true);
|
||||||
@ -1057,16 +1097,7 @@ public final class DownloadManager {
|
|||||||
// to set STATE_STOPPED either, because it doesn't have a stopReason argument.
|
// to set STATE_STOPPED either, because it doesn't have a stopReason argument.
|
||||||
Assertions.checkState(
|
Assertions.checkState(
|
||||||
state != STATE_COMPLETED && state != STATE_FAILED && state != STATE_STOPPED);
|
state != STATE_COMPLETED && state != STATE_FAILED && state != STATE_STOPPED);
|
||||||
return putDownload(
|
return putDownload(copyDownloadWithState(download, state));
|
||||||
new Download(
|
|
||||||
download.request,
|
|
||||||
state,
|
|
||||||
download.startTimeMs,
|
|
||||||
/* updateTimeMs= */ System.currentTimeMillis(),
|
|
||||||
download.contentLength,
|
|
||||||
/* stopReason= */ 0,
|
|
||||||
FAILURE_REASON_NONE,
|
|
||||||
download.progress));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Download putDownload(Download download) {
|
private Download putDownload(Download download) {
|
||||||
@ -1120,6 +1151,18 @@ public final class DownloadManager {
|
|||||||
return C.INDEX_UNSET;
|
return C.INDEX_UNSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Download copyDownloadWithState(Download download, @Download.State int state) {
|
||||||
|
return new Download(
|
||||||
|
download.request,
|
||||||
|
state,
|
||||||
|
download.startTimeMs,
|
||||||
|
/* updateTimeMs= */ System.currentTimeMillis(),
|
||||||
|
download.contentLength,
|
||||||
|
/* stopReason= */ 0,
|
||||||
|
FAILURE_REASON_NONE,
|
||||||
|
download.progress);
|
||||||
|
}
|
||||||
|
|
||||||
private static int compareStartTimes(Download first, Download second) {
|
private static int compareStartTimes(Download first, Download second) {
|
||||||
return Util.compareLong(first.startTimeMs, second.startTimeMs);
|
return Util.compareLong(first.startTimeMs, second.startTimeMs);
|
||||||
}
|
}
|
||||||
|
@ -77,6 +77,16 @@ public abstract class DownloadService extends Service {
|
|||||||
public static final String ACTION_REMOVE_DOWNLOAD =
|
public static final String ACTION_REMOVE_DOWNLOAD =
|
||||||
"com.google.android.exoplayer.downloadService.action.REMOVE_DOWNLOAD";
|
"com.google.android.exoplayer.downloadService.action.REMOVE_DOWNLOAD";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all downloads. Extras:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link #KEY_FOREGROUND} - See {@link #KEY_FOREGROUND}.
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public static final String ACTION_REMOVE_ALL_DOWNLOADS =
|
||||||
|
"com.google.android.exoplayer.downloadService.action.REMOVE_ALL_DOWNLOADS";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resumes all downloads except those that have a non-zero {@link Download#stopReason}. Extras:
|
* Resumes all downloads except those that have a non-zero {@link Download#stopReason}. Extras:
|
||||||
*
|
*
|
||||||
@ -296,6 +306,19 @@ public abstract class DownloadService extends Service {
|
|||||||
.putExtra(KEY_CONTENT_ID, id);
|
.putExtra(KEY_CONTENT_ID, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds an {@link Intent} for removing all downloads.
|
||||||
|
*
|
||||||
|
* @param context A {@link Context}.
|
||||||
|
* @param clazz The concrete download service being targeted by the intent.
|
||||||
|
* @param foreground Whether this intent will be used to start the service in the foreground.
|
||||||
|
* @return The created intent.
|
||||||
|
*/
|
||||||
|
public static Intent buildRemoveAllDownloadsIntent(
|
||||||
|
Context context, Class<? extends DownloadService> clazz, boolean foreground) {
|
||||||
|
return getIntent(context, clazz, ACTION_REMOVE_ALL_DOWNLOADS, foreground);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds an {@link Intent} for resuming all downloads.
|
* Builds an {@link Intent} for resuming all downloads.
|
||||||
*
|
*
|
||||||
@ -414,6 +437,19 @@ public abstract class DownloadService extends Service {
|
|||||||
startService(context, intent, foreground);
|
startService(context, intent, foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the service if not started already and removes all downloads.
|
||||||
|
*
|
||||||
|
* @param context A {@link Context}.
|
||||||
|
* @param clazz The concrete download service to be started.
|
||||||
|
* @param foreground Whether the service is started in the foreground.
|
||||||
|
*/
|
||||||
|
public static void sendRemoveAllDownloads(
|
||||||
|
Context context, Class<? extends DownloadService> clazz, boolean foreground) {
|
||||||
|
Intent intent = buildRemoveAllDownloadsIntent(context, clazz, foreground);
|
||||||
|
startService(context, intent, foreground);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts the service if not started already and resumes all downloads.
|
* Starts the service if not started already and resumes all downloads.
|
||||||
*
|
*
|
||||||
@ -560,6 +596,9 @@ public abstract class DownloadService extends Service {
|
|||||||
downloadManager.removeDownload(contentId);
|
downloadManager.removeDownload(contentId);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case ACTION_REMOVE_ALL_DOWNLOADS:
|
||||||
|
downloadManager.removeAllDownloads();
|
||||||
|
break;
|
||||||
case ACTION_RESUME_DOWNLOADS:
|
case ACTION_RESUME_DOWNLOADS:
|
||||||
downloadManager.resumeDownloads();
|
downloadManager.resumeDownloads();
|
||||||
break;
|
break;
|
||||||
|
@ -44,6 +44,13 @@ public interface WritableDownloadIndex extends DownloadIndex {
|
|||||||
*/
|
*/
|
||||||
void setDownloadingStatesToQueued() throws IOException;
|
void setDownloadingStatesToQueued() throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets all states to {@link Download#STATE_REMOVING}.
|
||||||
|
*
|
||||||
|
* @throws IOException If an error occurs updating the state.
|
||||||
|
*/
|
||||||
|
void setStatesToRemoving() throws IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the stop reason of the downloads in a terminal state ({@link Download#STATE_COMPLETED},
|
* Sets the stop reason of the downloads in a terminal state ({@link Download#STATE_COMPLETED},
|
||||||
* {@link Download#STATE_FAILED}).
|
* {@link Download#STATE_FAILED}).
|
||||||
|
@ -36,7 +36,7 @@ import com.google.android.exoplayer2.util.Util;
|
|||||||
* <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
* <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||||
* <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
* <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||||
*
|
*
|
||||||
* <service android:name="com.google.android.exoplayer2.util.scheduler.PlatformScheduler$PlatformSchedulerService"
|
* <service android:name="com.google.android.exoplayer2.scheduler.PlatformScheduler$PlatformSchedulerService"
|
||||||
* android:permission="android.permission.BIND_JOB_SERVICE"
|
* android:permission="android.permission.BIND_JOB_SERVICE"
|
||||||
* android:exported="true"/>
|
* android:exported="true"/>
|
||||||
* }</pre>
|
* }</pre>
|
||||||
|
@ -21,7 +21,6 @@ import android.graphics.Color;
|
|||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuff;
|
||||||
import android.graphics.PorterDuffXfermode;
|
import android.graphics.PorterDuffXfermode;
|
||||||
import android.graphics.Region;
|
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
import com.google.android.exoplayer2.text.Cue;
|
import com.google.android.exoplayer2.text.Cue;
|
||||||
import com.google.android.exoplayer2.util.Log;
|
import com.google.android.exoplayer2.util.Log;
|
||||||
@ -150,6 +149,8 @@ import java.util.List;
|
|||||||
List<Cue> cues = new ArrayList<>();
|
List<Cue> cues = new ArrayList<>();
|
||||||
SparseArray<PageRegion> pageRegions = subtitleService.pageComposition.regions;
|
SparseArray<PageRegion> pageRegions = subtitleService.pageComposition.regions;
|
||||||
for (int i = 0; i < pageRegions.size(); i++) {
|
for (int i = 0; i < pageRegions.size(); i++) {
|
||||||
|
// Save clean clipping state.
|
||||||
|
canvas.save();
|
||||||
PageRegion pageRegion = pageRegions.valueAt(i);
|
PageRegion pageRegion = pageRegions.valueAt(i);
|
||||||
int regionId = pageRegions.keyAt(i);
|
int regionId = pageRegions.keyAt(i);
|
||||||
RegionComposition regionComposition = subtitleService.regions.get(regionId);
|
RegionComposition regionComposition = subtitleService.regions.get(regionId);
|
||||||
@ -163,9 +164,7 @@ import java.util.List;
|
|||||||
displayDefinition.horizontalPositionMaximum);
|
displayDefinition.horizontalPositionMaximum);
|
||||||
int clipBottom = Math.min(baseVerticalAddress + regionComposition.height,
|
int clipBottom = Math.min(baseVerticalAddress + regionComposition.height,
|
||||||
displayDefinition.verticalPositionMaximum);
|
displayDefinition.verticalPositionMaximum);
|
||||||
canvas.clipRect(baseHorizontalAddress, baseVerticalAddress, clipRight, clipBottom,
|
canvas.clipRect(baseHorizontalAddress, baseVerticalAddress, clipRight, clipBottom);
|
||||||
Region.Op.REPLACE);
|
|
||||||
|
|
||||||
ClutDefinition clutDefinition = subtitleService.cluts.get(regionComposition.clutId);
|
ClutDefinition clutDefinition = subtitleService.cluts.get(regionComposition.clutId);
|
||||||
if (clutDefinition == null) {
|
if (clutDefinition == null) {
|
||||||
clutDefinition = subtitleService.ancillaryCluts.get(regionComposition.clutId);
|
clutDefinition = subtitleService.ancillaryCluts.get(regionComposition.clutId);
|
||||||
@ -214,9 +213,11 @@ import java.util.List;
|
|||||||
(float) regionComposition.height / displayDefinition.height));
|
(float) regionComposition.height / displayDefinition.height));
|
||||||
|
|
||||||
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
|
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
|
||||||
|
// Restore clean clipping state.
|
||||||
|
canvas.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
return cues;
|
return Collections.unmodifiableList(cues);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Static parsing.
|
// Static parsing.
|
||||||
|
@ -89,6 +89,11 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
|
|||||||
private long bytesSkipped;
|
private long bytesSkipped;
|
||||||
private long bytesRead;
|
private long bytesRead;
|
||||||
|
|
||||||
|
/** @param userAgent The User-Agent string that should be used. */
|
||||||
|
public DefaultHttpDataSource(String userAgent) {
|
||||||
|
this(userAgent, /* contentTypePredicate= */ null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param userAgent The User-Agent string that should be used.
|
* @param userAgent The User-Agent string that should be used.
|
||||||
* @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the
|
* @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the
|
||||||
|
@ -48,6 +48,17 @@ public final class IcyDecoderTest {
|
|||||||
assertThat(streamInfo.url).isNull();
|
assertThat(streamInfo.url).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void decode_emptyTitle() {
|
||||||
|
IcyDecoder decoder = new IcyDecoder();
|
||||||
|
Metadata metadata = decoder.decode("StreamTitle='';StreamURL='test_url';");
|
||||||
|
|
||||||
|
assertThat(metadata.length()).isEqualTo(1);
|
||||||
|
IcyInfo streamInfo = (IcyInfo) metadata.get(0);
|
||||||
|
assertThat(streamInfo.title).isEmpty();
|
||||||
|
assertThat(streamInfo.url).isEqualTo("test_url");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void decode_semiColonInTitle() {
|
public void decode_semiColonInTitle() {
|
||||||
IcyDecoder decoder = new IcyDecoder();
|
IcyDecoder decoder = new IcyDecoder();
|
||||||
@ -70,6 +81,17 @@ public final class IcyDecoderTest {
|
|||||||
assertThat(streamInfo.url).isEqualTo("test_url");
|
assertThat(streamInfo.url).isEqualTo("test_url");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void decode_lineTerminatorInTitle() {
|
||||||
|
IcyDecoder decoder = new IcyDecoder();
|
||||||
|
Metadata metadata = decoder.decode("StreamTitle='test\r\ntitle';StreamURL='test_url';");
|
||||||
|
|
||||||
|
assertThat(metadata.length()).isEqualTo(1);
|
||||||
|
IcyInfo streamInfo = (IcyInfo) metadata.get(0);
|
||||||
|
assertThat(streamInfo.title).isEqualTo("test\r\ntitle");
|
||||||
|
assertThat(streamInfo.url).isEqualTo("test_url");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void decode_notIcy() {
|
public void decode_notIcy() {
|
||||||
IcyDecoder decoder = new IcyDecoder();
|
IcyDecoder decoder = new IcyDecoder();
|
||||||
|
@ -243,6 +243,27 @@ public class DownloadManagerTest {
|
|||||||
downloadManagerListener.blockUntilTasksCompleteAndThrowAnyDownloadError();
|
downloadManagerListener.blockUntilTasksCompleteAndThrowAnyDownloadError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void removeAllDownloads_removesAllDownloads() throws Throwable {
|
||||||
|
// Finish one download and keep one running.
|
||||||
|
DownloadRunner runner1 = new DownloadRunner(uri1);
|
||||||
|
DownloadRunner runner2 = new DownloadRunner(uri2);
|
||||||
|
runner1.postDownloadRequest();
|
||||||
|
runner1.getDownloader(0).unblock();
|
||||||
|
downloadManagerListener.blockUntilTasksCompleteAndThrowAnyDownloadError();
|
||||||
|
runner2.postDownloadRequest();
|
||||||
|
|
||||||
|
runner1.postRemoveAllRequest();
|
||||||
|
runner1.getDownloader(1).unblock();
|
||||||
|
runner2.getDownloader(1).unblock();
|
||||||
|
downloadManagerListener.blockUntilTasksCompleteAndThrowAnyDownloadError();
|
||||||
|
|
||||||
|
runner1.getTask().assertRemoved();
|
||||||
|
runner2.getTask().assertRemoved();
|
||||||
|
assertThat(downloadManager.getCurrentDownloads()).isEmpty();
|
||||||
|
assertThat(downloadIndex.getDownloads().getCount()).isEqualTo(0);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void differentDownloadRequestsMerged() throws Throwable {
|
public void differentDownloadRequestsMerged() throws Throwable {
|
||||||
DownloadRunner runner = new DownloadRunner(uri1);
|
DownloadRunner runner = new DownloadRunner(uri1);
|
||||||
@ -605,6 +626,11 @@ public class DownloadManagerTest {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private DownloadRunner postRemoveAllRequest() {
|
||||||
|
runOnMainThread(() -> downloadManager.removeAllDownloads());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
private DownloadRunner postDownloadRequest(StreamKey... keys) {
|
private DownloadRunner postDownloadRequest(StreamKey... keys) {
|
||||||
DownloadRequest downloadRequest =
|
DownloadRequest downloadRequest =
|
||||||
new DownloadRequest(
|
new DownloadRequest(
|
||||||
|
@ -6,7 +6,9 @@ play DASH content, instantiate a `DashMediaSource` and pass it to
|
|||||||
|
|
||||||
## Links ##
|
## Links ##
|
||||||
|
|
||||||
|
* [Developer Guide][].
|
||||||
* [Javadoc][]: Classes matching `com.google.android.exoplayer2.source.dash.*`
|
* [Javadoc][]: Classes matching `com.google.android.exoplayer2.source.dash.*`
|
||||||
belong to this module.
|
belong to this module.
|
||||||
|
|
||||||
|
[Developer Guide]: https://exoplayer.dev/dash.html
|
||||||
[Javadoc]: https://exoplayer.dev/doc/reference/index.html
|
[Javadoc]: https://exoplayer.dev/doc/reference/index.html
|
||||||
|
@ -5,7 +5,9 @@ instantiate a `HlsMediaSource` and pass it to `ExoPlayer.prepare`.
|
|||||||
|
|
||||||
## Links ##
|
## Links ##
|
||||||
|
|
||||||
|
* [Developer Guide][].
|
||||||
* [Javadoc][]: Classes matching `com.google.android.exoplayer2.source.hls.*`
|
* [Javadoc][]: Classes matching `com.google.android.exoplayer2.source.hls.*`
|
||||||
belong to this module.
|
belong to this module.
|
||||||
|
|
||||||
|
[Developer Guide]: https://exoplayer.dev/hls.html
|
||||||
[Javadoc]: https://exoplayer.dev/doc/reference/index.html
|
[Javadoc]: https://exoplayer.dev/doc/reference/index.html
|
||||||
|
@ -802,7 +802,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
|
|||||||
if (isPrimaryTrackInVariant) {
|
if (isPrimaryTrackInVariant) {
|
||||||
channelCount = variantFormat.channelCount;
|
channelCount = variantFormat.channelCount;
|
||||||
selectionFlags = variantFormat.selectionFlags;
|
selectionFlags = variantFormat.selectionFlags;
|
||||||
roleFlags = mediaTagFormat.roleFlags;
|
roleFlags = variantFormat.roleFlags;
|
||||||
language = variantFormat.language;
|
language = variantFormat.language;
|
||||||
label = variantFormat.label;
|
label = variantFormat.label;
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,10 @@ instantiate a `SsMediaSource` and pass it to `ExoPlayer.prepare`.
|
|||||||
|
|
||||||
## Links ##
|
## Links ##
|
||||||
|
|
||||||
|
* [Developer Guide][].
|
||||||
* [Javadoc][]: Classes matching
|
* [Javadoc][]: Classes matching
|
||||||
`com.google.android.exoplayer2.source.smoothstreaming.*` belong to this
|
`com.google.android.exoplayer2.source.smoothstreaming.*` belong to this
|
||||||
module.
|
module.
|
||||||
|
|
||||||
|
[Developer Guide]: https://exoplayer.dev/smoothstreaming.html
|
||||||
[Javadoc]: https://exoplayer.dev/doc/reference/index.html
|
[Javadoc]: https://exoplayer.dev/doc/reference/index.html
|
||||||
|
@ -4,7 +4,9 @@ Provides UI components and resources for use with ExoPlayer.
|
|||||||
|
|
||||||
## Links ##
|
## Links ##
|
||||||
|
|
||||||
|
* [Developer Guide][].
|
||||||
* [Javadoc][]: Classes matching `com.google.android.exoplayer2.ui.*`
|
* [Javadoc][]: Classes matching `com.google.android.exoplayer2.ui.*`
|
||||||
belong to this module.
|
belong to this module.
|
||||||
|
|
||||||
|
[Developer Guide]: https://exoplayer.dev/ui-components.html
|
||||||
[Javadoc]: https://exoplayer.dev/doc/reference/index.html
|
[Javadoc]: https://exoplayer.dev/doc/reference/index.html
|
||||||
|
@ -27,7 +27,7 @@ import java.io.IOException;
|
|||||||
/** Fake {@link MediaChunk}. */
|
/** Fake {@link MediaChunk}. */
|
||||||
public final class FakeMediaChunk extends MediaChunk {
|
public final class FakeMediaChunk extends MediaChunk {
|
||||||
|
|
||||||
private static final DataSource DATA_SOURCE = new DefaultHttpDataSource("TEST_AGENT", null);
|
private static final DataSource DATA_SOURCE = new DefaultHttpDataSource("TEST_AGENT");
|
||||||
|
|
||||||
public FakeMediaChunk(Format trackFormat, long startTimeUs, long endTimeUs) {
|
public FakeMediaChunk(Format trackFormat, long startTimeUs, long endTimeUs) {
|
||||||
this(new DataSpec(Uri.EMPTY), trackFormat, startTimeUs, endTimeUs);
|
this(new DataSpec(Uri.EMPTY), trackFormat, startTimeUs, endTimeUs);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user