Commit f7ed789f by ojw28 Committed by GitHub

Merge pull request #4434 from google/dev-v2-r2.8.2

r2.8.2
parents 2b55c91a d880fac5
Showing with 893 additions and 440 deletions
# Release notes #
### 2.8.2 ###
* IMA: Don't advertise support for video/mpeg ad media, as we don't have an
extractor for this ([#4297](https://github.com/google/ExoPlayer/issues/4297)).
* DASH: Fix playback getting stuck when playing representations that have both
sidx atoms and non-zero presentationTimeOffset values.
* HLS:
* Allow injection of custom playlist trackers.
* Fix adaptation in live playlists with EXT-X-PROGRAM-DATE-TIME tags.
* Mitigate memory leaks when `MediaSource` loads are slow to cancel
([#4249](https://github.com/google/ExoPlayer/issues/4249)).
* Fix inconsistent `Player.EventListener` invocations for recursive player state
changes ([#4276](https://github.com/google/ExoPlayer/issues/4276)).
* Fix `MediaCodec.native_setSurface` crash on Moto C
([#4315](https://github.com/google/ExoPlayer/issues/4315)).
* Fix missing whitespace in CEA-608
([#3906](https://github.com/google/ExoPlayer/issues/3906)).
* Fix crash downloading HLS media playlists
([#4396](https://github.com/google/ExoPlayer/issues/4396)).
* Fix a bug where download cancellation was ignored
([#4403](https://github.com/google/ExoPlayer/issues/4403)).
* Set `METADATA_KEY_TITLE` on media descriptions
([#4292](https://github.com/google/ExoPlayer/issues/4292)).
* Allow apps to register custom MIME types
([#4264](https://github.com/google/ExoPlayer/issues/4264)).
### 2.8.1 ###
* HLS:
......@@ -59,7 +85,7 @@
periods are created, released and being read from.
* Support live stream clipping with `ClippingMediaSource`.
* Allow setting tags for all media sources in their factories. The tag of the
current window can be retrieved with `ExoPlayer.getCurrentTag`.
current window can be retrieved with `Player.getCurrentTag`.
* UI components:
* Add support for displaying error messages and a buffering spinner in
`PlayerView`.
......
......@@ -13,8 +13,8 @@
// limitations under the License.
project.ext {
// ExoPlayer version and version code.
releaseVersion = '2.8.1'
releaseVersionCode = 2801
releaseVersion = '2.8.2'
releaseVersionCode = 2802
// Important: ExoPlayer specifies a minSdkVersion of 14 because various
// components provided by the library may be of use on older devices.
// However, please note that the core media playback functionality provided
......@@ -25,7 +25,7 @@ project.ext {
buildToolsVersion = '27.0.3'
testSupportLibraryVersion = '0.5'
supportLibraryVersion = '27.0.0'
playServicesLibraryVersion = '12.0.0'
playServicesLibraryVersion = '15.0.1'
dexmakerVersion = '1.2'
mockitoVersion = '1.9.5'
junitVersion = '4.12'
......
......@@ -136,6 +136,7 @@ public class PlayerActivity extends Activity
private DataSource.Factory mediaDataSourceFactory;
private SimpleExoPlayer player;
private FrameworkMediaDrm mediaDrm;
private MediaSource mediaSource;
private DefaultTrackSelector trackSelector;
private DefaultTrackSelector.Parameters trackSelectorParameters;
......@@ -487,8 +488,9 @@ public class PlayerActivity extends Activity
keyRequestPropertiesArray[i + 1]);
}
}
return new DefaultDrmSessionManager<>(
uuid, FrameworkMediaDrm.newInstance(uuid), drmCallback, null, multiSession);
releaseMediaDrm();
mediaDrm = FrameworkMediaDrm.newInstance(uuid);
return new DefaultDrmSessionManager<>(uuid, mediaDrm, drmCallback, null, multiSession);
}
private void releasePlayer() {
......@@ -502,6 +504,23 @@ public class PlayerActivity extends Activity
mediaSource = null;
trackSelector = null;
}
releaseMediaDrm();
}
private void releaseMediaDrm() {
if (mediaDrm != null) {
mediaDrm.release();
mediaDrm = null;
}
}
private void releaseAdsLoader() {
if (adsLoader != null) {
adsLoader.release();
adsLoader = null;
loadedAdTagUri = null;
playerView.getOverlayFrameLayout().removeAllViews();
}
}
private void updateTrackSelectorParameters() {
......@@ -576,15 +595,6 @@ public class PlayerActivity extends Activity
}
}
private void releaseAdsLoader() {
if (adsLoader != null) {
adsLoader.release();
adsLoader = null;
loadedAdTagUri = null;
playerView.getOverlayFrameLayout().removeAllViews();
}
}
// User controls
private void updateButtonVisibilities() {
......
......@@ -94,9 +94,15 @@ public class SampleChooserActivity extends Activity
SampleListLoader loaderTask = new SampleListLoader();
loaderTask.execute(uris);
// Ping the download service in case it's not running (but should be).
startService(
new Intent(this, DemoDownloadService.class).setAction(DownloadService.ACTION_INIT));
// Start the download service if it should be running but it's not currently.
// Starting the service in the foreground causes notification flicker if there is no scheduled
// action. Starting it in the background throws an exception if the app is in the background too
// (e.g. if device screen is locked).
try {
DownloadService.start(this, DemoDownloadService.class);
} catch (IllegalStateException e) {
DownloadService.startForeground(this, DemoDownloadService.class);
}
}
@Override
......
......@@ -26,16 +26,6 @@ android {
}
dependencies {
// These dependencies are necessary to force the supportLibraryVersion of
// com.android.support:support-v4, com.android.support:appcompat-v7 and
// com.android.support:mediarouter-v7 to be used. Else older versions are
// used, for example:
// com.google.android.gms:play-services-cast-framework:12.0.0
// |-- com.google.android.gms:play-services-basement:12.0.0
// |-- com.android.support:support-v4:26.1.0
api 'com.android.support:support-v4:' + supportLibraryVersion
api 'com.android.support:appcompat-v7:' + supportLibraryVersion
api 'com.android.support:mediarouter-v7:' + supportLibraryVersion
api 'com.google.android.gms:play-services-cast-framework:' + playServicesLibraryVersion
implementation project(modulePrefix + 'library-core')
implementation project(modulePrefix + 'library-ui')
......@@ -44,6 +34,15 @@ dependencies {
testImplementation 'org.mockito:mockito-core:' + mockitoVersion
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
testImplementation project(modulePrefix + 'testutils-robolectric')
// These dependencies are necessary to force the supportLibraryVersion of
// com.android.support:support-v4, com.android.support:appcompat-v7 and
// com.android.support:mediarouter-v7 to be used. Else older versions are
// used, for example via:
// com.google.android.gms:play-services-cast-framework:15.0.1
// |-- com.android.support:mediarouter-v7:26.1.0
api 'com.android.support:support-v4:' + supportLibraryVersion
api 'com.android.support:mediarouter-v7:' + supportLibraryVersion
api 'com.android.support:recyclerview-v7:' + supportLibraryVersion
}
ext {
......
......@@ -26,17 +26,16 @@ android {
}
dependencies {
// This dependency is necessary to force the supportLibraryVersion of
// com.android.support:support-v4 to be used. Else an older version (25.2.0)
// is included via:
// com.google.android.gms:play-services-ads:12.0.0
// |-- com.google.android.gms:play-services-ads-lite:12.0.0
// |-- com.google.android.gms:play-services-basement:12.0.0
// |-- com.android.support:support-v4:26.1.0
api 'com.android.support:support-v4:' + supportLibraryVersion
api 'com.google.ads.interactivemedia.v3:interactivemedia:3.8.5'
api 'com.google.ads.interactivemedia.v3:interactivemedia:3.8.7'
implementation project(modulePrefix + 'library-core')
implementation 'com.google.android.gms:play-services-ads:' + playServicesLibraryVersion
// These dependencies are necessary to force the supportLibraryVersion of
// com.android.support:support-v4 and com.android.support:customtabs to be
// used. Else older versions are used, for example via:
// com.google.android.gms:play-services-ads:15.0.1
// |-- com.android.support:customtabs:26.1.0
implementation 'com.android.support:support-v4:' + supportLibraryVersion
implementation 'com.android.support:customtabs:' + supportLibraryVersion
}
ext {
......
......@@ -447,9 +447,13 @@ public final class ImaAdsLoader extends Player.DefaultEventListener implements A
} else if (contentType == C.TYPE_HLS) {
supportedMimeTypes.add(MimeTypes.APPLICATION_M3U8);
} else if (contentType == C.TYPE_OTHER) {
supportedMimeTypes.addAll(Arrays.asList(
MimeTypes.VIDEO_MP4, MimeTypes.VIDEO_WEBM, MimeTypes.VIDEO_H263, MimeTypes.VIDEO_MPEG,
MimeTypes.AUDIO_MP4, MimeTypes.AUDIO_MPEG));
supportedMimeTypes.addAll(
Arrays.asList(
MimeTypes.VIDEO_MP4,
MimeTypes.VIDEO_WEBM,
MimeTypes.VIDEO_H263,
MimeTypes.AUDIO_MP4,
MimeTypes.AUDIO_MPEG));
} else if (contentType == C.TYPE_SS) {
// IMA does not support Smooth Streaming ad media.
}
......
......@@ -600,8 +600,9 @@ public final class MediaSessionConnector {
}
}
if (description.getTitle() != null) {
builder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE,
String.valueOf(description.getTitle()));
String title = String.valueOf(description.getTitle());
builder.putString(MediaMetadataCompat.METADATA_KEY_TITLE, title);
builder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, title);
}
if (description.getSubtitle() != null) {
builder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE,
......
......@@ -89,12 +89,12 @@ import com.google.android.exoplayer2.video.MediaCodecVideoRenderer;
* model">
*
* <ul>
* <li>It is strongly recommended that ExoPlayer instances are created and accessed from a single
* application thread. The application's main thread is ideal. Accessing an instance from
* multiple threads is discouraged as it may cause synchronization problems.
* <li>Registered listeners are called on the thread that created the ExoPlayer instance, unless
* the thread that created the ExoPlayer instance does not have a {@link Looper}. In that
* case, registered listeners will be called on the application's main thread.
* <li>ExoPlayer instances must be accessed from a single application thread. This must be the
* thread the player is created on if that thread has a {@link Looper}, or the application's
* main thread otherwise.
* <li>Registered listeners are called on the thread the player is created on if that thread has a
* {@link Looper}, or the application's main thread otherwise. Note that this means registered
* listeners are called on the same thread which must be used to access the player.
* <li>An internal playback thread is responsible for playback. Injected player components such as
* Renderers, MediaSources, TrackSelectors and LoadControls are called by the player on this
* thread.
......
......@@ -33,8 +33,10 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.Util;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
/**
......@@ -53,6 +55,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
private final CopyOnWriteArraySet<Player.EventListener> listeners;
private final Timeline.Window window;
private final Timeline.Period period;
private final ArrayDeque<PlaybackInfoUpdate> pendingPlaybackInfoUpdates;
private boolean playWhenReady;
private @RepeatMode int repeatMode;
......@@ -112,6 +115,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
/* startPositionUs= */ 0,
TrackGroupArray.EMPTY,
emptyTrackSelectorResult);
pendingPlaybackInfoUpdates = new ArrayDeque<>();
internalPlayer =
new ExoPlayerImplInternal(
renderers,
......@@ -185,7 +189,8 @@ import java.util.concurrent.CopyOnWriteArraySet;
/* positionDiscontinuity= */ false,
/* ignored */ DISCONTINUITY_REASON_INTERNAL,
TIMELINE_CHANGE_REASON_RESET,
/* seekProcessed= */ false);
/* seekProcessed= */ false,
/* playWhenReadyChanged= */ false);
}
@Override
......@@ -193,10 +198,13 @@ import java.util.concurrent.CopyOnWriteArraySet;
if (this.playWhenReady != playWhenReady) {
this.playWhenReady = playWhenReady;
internalPlayer.setPlayWhenReady(playWhenReady);
PlaybackInfo playbackInfo = this.playbackInfo;
for (Player.EventListener listener : listeners) {
listener.onPlayerStateChanged(playWhenReady, playbackInfo.playbackState);
}
updatePlaybackInfo(
playbackInfo,
/* positionDiscontinuity= */ false,
/* ignored */ DISCONTINUITY_REASON_INTERNAL,
/* ignored */ TIMELINE_CHANGE_REASON_RESET,
/* seekProcessed= */ false,
/* playWhenReadyChanged= */ true);
}
}
......@@ -352,7 +360,8 @@ import java.util.concurrent.CopyOnWriteArraySet;
/* positionDiscontinuity= */ false,
/* ignored */ DISCONTINUITY_REASON_INTERNAL,
TIMELINE_CHANGE_REASON_RESET,
/* seekProcessed= */ false);
/* seekProcessed= */ false,
/* playWhenReadyChanged= */ false);
}
@Override
......@@ -615,7 +624,8 @@ import java.util.concurrent.CopyOnWriteArraySet;
positionDiscontinuity,
positionDiscontinuityReason,
timelineChangeReason,
seekProcessed);
seekProcessed,
/* playWhenReadyChanged= */ false);
}
}
......@@ -643,51 +653,33 @@ import java.util.concurrent.CopyOnWriteArraySet;
}
private void updatePlaybackInfo(
PlaybackInfo newPlaybackInfo,
PlaybackInfo playbackInfo,
boolean positionDiscontinuity,
@Player.DiscontinuityReason int positionDiscontinuityReason,
@Player.TimelineChangeReason int timelineChangeReason,
boolean seekProcessed) {
boolean timelineOrManifestChanged =
playbackInfo.timeline != newPlaybackInfo.timeline
|| playbackInfo.manifest != newPlaybackInfo.manifest;
boolean playbackStateChanged = playbackInfo.playbackState != newPlaybackInfo.playbackState;
boolean isLoadingChanged = playbackInfo.isLoading != newPlaybackInfo.isLoading;
boolean trackSelectorResultChanged =
playbackInfo.trackSelectorResult != newPlaybackInfo.trackSelectorResult;
playbackInfo = newPlaybackInfo;
if (timelineOrManifestChanged || timelineChangeReason == TIMELINE_CHANGE_REASON_PREPARED) {
for (Player.EventListener listener : listeners) {
listener.onTimelineChanged(
playbackInfo.timeline, playbackInfo.manifest, timelineChangeReason);
}
}
if (positionDiscontinuity) {
for (Player.EventListener listener : listeners) {
listener.onPositionDiscontinuity(positionDiscontinuityReason);
}
}
if (trackSelectorResultChanged) {
trackSelector.onSelectionActivated(playbackInfo.trackSelectorResult.info);
for (Player.EventListener listener : listeners) {
listener.onTracksChanged(
playbackInfo.trackGroups, playbackInfo.trackSelectorResult.selections);
}
}
if (isLoadingChanged) {
for (Player.EventListener listener : listeners) {
listener.onLoadingChanged(playbackInfo.isLoading);
}
}
if (playbackStateChanged) {
for (Player.EventListener listener : listeners) {
listener.onPlayerStateChanged(playWhenReady, playbackInfo.playbackState);
}
boolean seekProcessed,
boolean playWhenReadyChanged) {
boolean isRunningRecursiveListenerNotification = !pendingPlaybackInfoUpdates.isEmpty();
pendingPlaybackInfoUpdates.addLast(
new PlaybackInfoUpdate(
playbackInfo,
/* previousPlaybackInfo= */ this.playbackInfo,
listeners,
trackSelector,
positionDiscontinuity,
positionDiscontinuityReason,
timelineChangeReason,
seekProcessed,
playWhenReady,
playWhenReadyChanged));
// Assign playback info immediately such that all getters return the right values.
this.playbackInfo = playbackInfo;
if (isRunningRecursiveListenerNotification) {
return;
}
if (seekProcessed) {
for (Player.EventListener listener : listeners) {
listener.onSeekProcessed();
}
while (!pendingPlaybackInfoUpdates.isEmpty()) {
pendingPlaybackInfoUpdates.peekFirst().notifyListeners();
pendingPlaybackInfoUpdates.removeFirst();
}
}
......@@ -703,4 +695,85 @@ import java.util.concurrent.CopyOnWriteArraySet;
private boolean shouldMaskPosition() {
return playbackInfo.timeline.isEmpty() || pendingOperationAcks > 0;
}
private static final class PlaybackInfoUpdate {
private final PlaybackInfo playbackInfo;
private final Set<Player.EventListener> listeners;
private final TrackSelector trackSelector;
private final boolean positionDiscontinuity;
private final @Player.DiscontinuityReason int positionDiscontinuityReason;
private final @Player.TimelineChangeReason int timelineChangeReason;
private final boolean seekProcessed;
private final boolean playWhenReady;
private final boolean playbackStateOrPlayWhenReadyChanged;
private final boolean timelineOrManifestChanged;
private final boolean isLoadingChanged;
private final boolean trackSelectorResultChanged;
public PlaybackInfoUpdate(
PlaybackInfo playbackInfo,
PlaybackInfo previousPlaybackInfo,
Set<Player.EventListener> listeners,
TrackSelector trackSelector,
boolean positionDiscontinuity,
@Player.DiscontinuityReason int positionDiscontinuityReason,
@Player.TimelineChangeReason int timelineChangeReason,
boolean seekProcessed,
boolean playWhenReady,
boolean playWhenReadyChanged) {
this.playbackInfo = playbackInfo;
this.listeners = listeners;
this.trackSelector = trackSelector;
this.positionDiscontinuity = positionDiscontinuity;
this.positionDiscontinuityReason = positionDiscontinuityReason;
this.timelineChangeReason = timelineChangeReason;
this.seekProcessed = seekProcessed;
this.playWhenReady = playWhenReady;
playbackStateOrPlayWhenReadyChanged =
playWhenReadyChanged || previousPlaybackInfo.playbackState != playbackInfo.playbackState;
timelineOrManifestChanged =
previousPlaybackInfo.timeline != playbackInfo.timeline
|| previousPlaybackInfo.manifest != playbackInfo.manifest;
isLoadingChanged = previousPlaybackInfo.isLoading != playbackInfo.isLoading;
trackSelectorResultChanged =
previousPlaybackInfo.trackSelectorResult != playbackInfo.trackSelectorResult;
}
public void notifyListeners() {
if (timelineOrManifestChanged || timelineChangeReason == TIMELINE_CHANGE_REASON_PREPARED) {
for (Player.EventListener listener : listeners) {
listener.onTimelineChanged(
playbackInfo.timeline, playbackInfo.manifest, timelineChangeReason);
}
}
if (positionDiscontinuity) {
for (Player.EventListener listener : listeners) {
listener.onPositionDiscontinuity(positionDiscontinuityReason);
}
}
if (trackSelectorResultChanged) {
trackSelector.onSelectionActivated(playbackInfo.trackSelectorResult.info);
for (Player.EventListener listener : listeners) {
listener.onTracksChanged(
playbackInfo.trackGroups, playbackInfo.trackSelectorResult.selections);
}
}
if (isLoadingChanged) {
for (Player.EventListener listener : listeners) {
listener.onLoadingChanged(playbackInfo.isLoading);
}
}
if (playbackStateOrPlayWhenReadyChanged) {
for (Player.EventListener listener : listeners) {
listener.onPlayerStateChanged(playWhenReady, playbackInfo.playbackState);
}
}
if (seekProcessed) {
for (Player.EventListener listener : listeners) {
listener.onSeekProcessed();
}
}
}
}
}
......@@ -1543,6 +1543,7 @@ import java.util.Collections;
}
}
@SuppressWarnings("ParameterNotNullable")
private void updatePlayingPeriodRenderers(@Nullable MediaPeriodHolder oldPlayingPeriodHolder)
throws ExoPlaybackException {
MediaPeriodHolder newPlayingPeriodHolder = queue.getPlayingPeriod();
......
......@@ -29,11 +29,11 @@ public final class ExoPlayerLibraryInfo {
/** 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.
public static final String VERSION = "2.8.1";
public static final String VERSION = "2.8.2";
/** The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}. */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
public static final String VERSION_SLASHY = "ExoPlayerLib/2.8.1";
public static final String VERSION_SLASHY = "ExoPlayerLib/2.8.2";
/**
* 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).
*/
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
public static final int VERSION_INT = 2008001;
public static final int VERSION_INT = 2008002;
/**
* Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions}
......
......@@ -46,6 +46,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
* Data collector which is able to forward analytics events to {@link AnalyticsListener}s by
......@@ -66,29 +67,34 @@ public class AnalyticsCollector
/**
* Creates an analytics collector for the specified player.
*
* @param player The {@link Player} for which data will be collected.
* @param player The {@link Player} for which data will be collected. Can be null, if the player
* is set by calling {@link AnalyticsCollector#setPlayer(Player)} before using the analytics
* collector.
* @param clock A {@link Clock} used to generate timestamps.
* @return An analytics collector.
*/
public AnalyticsCollector createAnalyticsCollector(Player player, Clock clock) {
public AnalyticsCollector createAnalyticsCollector(@Nullable Player player, Clock clock) {
return new AnalyticsCollector(player, clock);
}
}
private final CopyOnWriteArraySet<AnalyticsListener> listeners;
private final Player player;
private final Clock clock;
private final Window window;
private final MediaPeriodQueueTracker mediaPeriodQueueTracker;
private @MonotonicNonNull Player player;
/**
* Creates an analytics collector for the specified player.
*
* @param player The {@link Player} for which data will be collected.
* @param player The {@link Player} for which data will be collected. Can be null, if the player
* is set by calling {@link AnalyticsCollector#setPlayer(Player)} before using the analytics
* collector.
* @param clock A {@link Clock} used to generate timestamps.
*/
protected AnalyticsCollector(Player player, Clock clock) {
this.player = Assertions.checkNotNull(player);
protected AnalyticsCollector(@Nullable Player player, Clock clock) {
this.player = player;
this.clock = Assertions.checkNotNull(clock);
listeners = new CopyOnWriteArraySet<>();
mediaPeriodQueueTracker = new MediaPeriodQueueTracker();
......@@ -113,6 +119,17 @@ public class AnalyticsCollector
listeners.remove(listener);
}
/**
* Sets the player for which data will be collected. Must only be called if no player has been set
* yet.
*
* @param player The {@link Player} for which data will be collected.
*/
public void setPlayer(Player player) {
Assertions.checkState(this.player == null);
this.player = Assertions.checkNotNull(player);
}
// External events.
/**
......@@ -541,6 +558,7 @@ public class AnalyticsCollector
/** Returns a new {@link EventTime} for the specified window index and media period id. */
protected EventTime generateEventTime(int windowIndex, @Nullable MediaPeriodId mediaPeriodId) {
Assertions.checkNotNull(player);
long realtimeMs = clock.elapsedRealtime();
Timeline timeline = player.getCurrentTimeline();
long eventPositionMs;
......@@ -579,7 +597,7 @@ public class AnalyticsCollector
private EventTime generateEventTime(@Nullable WindowAndMediaPeriodId mediaPeriod) {
if (mediaPeriod == null) {
int windowIndex = player.getCurrentWindowIndex();
int windowIndex = Assertions.checkNotNull(player).getCurrentWindowIndex();
MediaPeriodId mediaPeriodId = mediaPeriodQueueTracker.tryResolveWindowIndex(windowIndex);
return generateEventTime(windowIndex, mediaPeriodId);
}
......
......@@ -17,7 +17,7 @@ package com.google.android.exoplayer2.decoder;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.Assertions;
import java.util.LinkedList;
import java.util.ArrayDeque;
/**
* Base class for {@link Decoder}s that use their own decode thread.
......@@ -28,8 +28,8 @@ public abstract class SimpleDecoder<I extends DecoderInputBuffer, O extends Outp
private final Thread decodeThread;
private final Object lock;
private final LinkedList<I> queuedInputBuffers;
private final LinkedList<O> queuedOutputBuffers;
private final ArrayDeque<I> queuedInputBuffers;
private final ArrayDeque<O> queuedOutputBuffers;
private final I[] availableInputBuffers;
private final O[] availableOutputBuffers;
......@@ -48,8 +48,8 @@ public abstract class SimpleDecoder<I extends DecoderInputBuffer, O extends Outp
*/
protected SimpleDecoder(I[] inputBuffers, O[] outputBuffers) {
lock = new Object();
queuedInputBuffers = new LinkedList<>();
queuedOutputBuffers = new LinkedList<>();
queuedInputBuffers = new ArrayDeque<>();
queuedOutputBuffers = new ArrayDeque<>();
availableInputBuffers = inputBuffers;
availableInputBufferCount = inputBuffers.length;
for (int i = 0; i < availableInputBufferCount; i++) {
......
......@@ -108,7 +108,8 @@ public final class HttpMediaDrmCallback implements MediaDrmCallback {
@Override
public byte[] executeProvisionRequest(UUID uuid, ProvisionRequest request) throws IOException {
String url = request.getDefaultUrl() + "&signedRequest=" + new String(request.getData());
String url =
request.getDefaultUrl() + "&signedRequest=" + Util.fromUtf8Bytes(request.getData());
return executePost(dataSourceFactory, url, new byte[0], null);
}
......
......@@ -24,7 +24,7 @@ import java.io.EOFException;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Stack;
import java.util.ArrayDeque;
/**
* Default implementation of {@link EbmlReader}.
......@@ -46,15 +46,21 @@ import java.util.Stack;
private static final int VALID_FLOAT32_ELEMENT_SIZE_BYTES = 4;
private static final int VALID_FLOAT64_ELEMENT_SIZE_BYTES = 8;
private final byte[] scratch = new byte[8];
private final Stack<MasterElement> masterElementsStack = new Stack<>();
private final VarintReader varintReader = new VarintReader();
private final byte[] scratch;
private final ArrayDeque<MasterElement> masterElementsStack;
private final VarintReader varintReader;
private EbmlReaderOutput output;
private @ElementState int elementState;
private int elementId;
private long elementContentSize;
public DefaultEbmlReader() {
scratch = new byte[8];
masterElementsStack = new ArrayDeque<>();
varintReader = new VarintReader();
}
@Override
public void init(EbmlReaderOutput eventHandler) {
this.output = eventHandler;
......@@ -100,7 +106,7 @@ import java.util.Stack;
case EbmlReaderOutput.TYPE_MASTER:
long elementContentPosition = input.getPosition();
long elementEndPosition = elementContentPosition + elementContentSize;
masterElementsStack.add(new MasterElement(elementId, elementEndPosition));
masterElementsStack.push(new MasterElement(elementId, elementEndPosition));
output.startMasterElement(elementId, elementContentPosition, elementContentSize);
elementState = ELEMENT_STATE_READ_ID;
return true;
......
......@@ -78,8 +78,9 @@ import java.io.IOException;
return false;
}
if (size != 0) {
input.advancePeekPosition((int) size);
peekLength += size;
int sizeInt = (int) size;
input.advancePeekPosition(sizeInt);
peekLength += sizeInt;
}
}
return peekLength == headerStart + headerSize;
......
......@@ -108,4 +108,7 @@ import com.google.android.exoplayer2.util.Util;
return new Results(offsets, sizes, maximumSize, timestamps, flags, duration);
}
private FixedSampleSizeRechunker() {
// Prevent instantiation.
}
}
......@@ -50,7 +50,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Stack;
import java.util.UUID;
/**
......@@ -141,7 +140,7 @@ public final class FragmentedMp4Extractor implements Extractor {
// Parser state.
private final ParsableByteArray atomHeader;
private final byte[] extendedTypeScratch;
private final Stack<ContainerAtom> containerAtoms;
private final ArrayDeque<ContainerAtom> containerAtoms;
private final ArrayDeque<MetadataSampleInfo> pendingMetadataSampleInfos;
private final @Nullable TrackOutput additionalEmsgTrackOutput;
......@@ -257,7 +256,7 @@ public final class FragmentedMp4Extractor implements Extractor {
nalPrefix = new ParsableByteArray(5);
nalBuffer = new ParsableByteArray();
extendedTypeScratch = new byte[16];
containerAtoms = new Stack<>();
containerAtoms = new ArrayDeque<>();
pendingMetadataSampleInfos = new ArrayDeque<>();
trackBundles = new SparseArray<>();
durationUs = C.TIME_UNSET;
......@@ -390,7 +389,7 @@ public final class FragmentedMp4Extractor implements Extractor {
if (shouldParseContainerAtom(atomType)) {
long endPosition = input.getPosition() + atomSize - Atom.HEADER_SIZE;
containerAtoms.add(new ContainerAtom(atomType, endPosition));
containerAtoms.push(new ContainerAtom(atomType, endPosition));
if (atomSize == atomHeaderBytesRead) {
processAtomEnded(endPosition);
} else {
......
......@@ -37,9 +37,9 @@ import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
/**
* Extracts data from the MP4 container format.
......@@ -101,7 +101,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
private final ParsableByteArray nalLength;
private final ParsableByteArray atomHeader;
private final Stack<ContainerAtom> containerAtoms;
private final ArrayDeque<ContainerAtom> containerAtoms;
@State private int parserState;
private int atomType;
......@@ -137,7 +137,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
public Mp4Extractor(@Flags int flags) {
this.flags = flags;
atomHeader = new ParsableByteArray(Atom.LONG_HEADER_SIZE);
containerAtoms = new Stack<>();
containerAtoms = new ArrayDeque<>();
nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE);
nalLength = new ParsableByteArray(4);
sampleTrackIndex = C.INDEX_UNSET;
......@@ -303,7 +303,7 @@ public final class Mp4Extractor implements Extractor, SeekMap {
if (shouldParseContainerAtom(atomType)) {
long endPosition = input.getPosition() + atomSize - atomHeaderBytesRead;
containerAtoms.add(new ContainerAtom(atomType, endPosition));
containerAtoms.push(new ContainerAtom(atomType, endPosition));
if (atomSize == atomHeaderBytesRead) {
processAtomEnded(endPosition);
} else {
......
......@@ -49,6 +49,7 @@ public final class PsshAtomUtil {
* @param data The scheme specific data.
* @return The PSSH atom.
*/
@SuppressWarnings("ParameterNotNullable")
public static byte[] buildPsshAtom(
UUID systemId, @Nullable UUID[] keyIds, @Nullable byte[] data) {
boolean buildV1Atom = keyIds != null;
......
......@@ -130,6 +130,6 @@ import java.util.List;
} else {
length = 10000 << length;
}
return frames * length;
return (long) frames * length;
}
}
......@@ -357,12 +357,12 @@ import java.util.Arrays;
for (int i = 0; i < lengthMap.length; i++) {
if (isSparse) {
if (bitArray.readBit()) {
lengthMap[i] = bitArray.readBits(5) + 1;
lengthMap[i] = (long) (bitArray.readBits(5) + 1);
} else { // entry unused
lengthMap[i] = 0;
}
} else { // not sparse
lengthMap[i] = bitArray.readBits(5) + 1;
lengthMap[i] = (long) (bitArray.readBits(5) + 1);
}
}
} else {
......@@ -392,7 +392,7 @@ import java.util.Arrays;
lookupValuesCount = 0;
}
} else {
lookupValuesCount = entries * dimensions;
lookupValuesCount = (long) entries * dimensions;
}
// discard (no decoding required yet)
bitArray.skipBits((int) (lookupValuesCount * valueBits));
......@@ -407,6 +407,10 @@ import java.util.Arrays;
return (long) Math.floor(Math.pow(entries, 1.d / dimension));
}
private VorbisUtil() {
// Prevent instantiation.
}
public static final class CodeBook {
public final int dimensions;
......
......@@ -25,7 +25,7 @@ import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
/** Reads a {@code WavHeader} from an input stream; supports resuming from input failures. */
/*package*/ final class WavHeaderReader {
/* package */ final class WavHeaderReader {
private static final String TAG = "WavHeaderReader";
......@@ -158,6 +158,10 @@ import java.io.IOException;
wavHeader.setDataBounds(input.getPosition(), chunkHeader.size);
}
private WavHeaderReader() {
// Prevent instantiation.
}
/** Container for a WAV chunk header. */
private static final class ChunkHeader {
......
......@@ -262,12 +262,23 @@ public final class DownloadManager {
return task.id;
}
/** Returns the current number of tasks. */
/** Returns the number of tasks. */
public int getTaskCount() {
Assertions.checkState(!released);
return tasks.size();
}
/** Returns the number of download tasks. */
public int getDownloadCount() {
int count = 0;
for (int i = 0; i < tasks.size(); i++) {
if (!tasks.get(i).action.isRemoveAction) {
count++;
}
}
return count;
}
/** Returns the state of a task, or null if no such task exists */
public @Nullable TaskState getTaskState(int taskId) {
Assertions.checkState(!released);
......
......@@ -160,9 +160,9 @@ public abstract class DownloadService extends Service {
* Starts the service, adding an action to be executed.
*
* @param context A {@link Context}.
* @param clazz The concrete download service being targeted by the intent.
* @param clazz The concrete download service to be started.
* @param downloadAction The action to be executed.
* @param foreground Whether this intent will be used to start the service in the foreground.
* @param foreground Whether the service is started in the foreground.
*/
public static void startWithAction(
Context context,
......@@ -177,6 +177,33 @@ public abstract class DownloadService extends Service {
}
}
/**
* Starts the service without adding a new action. If there are any not finished actions and the
* requirements are met, the service resumes executing actions. Otherwise it stops immediately.
*
* @param context A {@link Context}.
* @param clazz The concrete download service to be started.
* @see #startForeground(Context, Class)
*/
public static void start(Context context, Class<? extends DownloadService> clazz) {
context.startService(new Intent(context, clazz).setAction(ACTION_INIT));
}
/**
* Starts the service in the foreground without adding a new action. If there are any not finished
* actions and the requirements are met, the service resumes executing actions. Otherwise it stops
* immediately.
*
* @param context A {@link Context}.
* @param clazz The concrete download service to be started.
* @see #start(Context, Class)
*/
public static void startForeground(Context context, Class<? extends DownloadService> clazz) {
Intent intent =
new Intent(context, clazz).setAction(ACTION_INIT).putExtra(KEY_FOREGROUND, true);
Util.startForegroundService(context, intent);
}
@Override
public void onCreate() {
logd("onCreate");
......@@ -187,17 +214,6 @@ public abstract class DownloadService extends Service {
downloadManager = getDownloadManager();
downloadManagerListener = new DownloadManagerListener();
downloadManager.addListener(downloadManagerListener);
RequirementsHelper requirementsHelper;
synchronized (requirementsHelpers) {
Class<? extends DownloadService> clazz = getClass();
requirementsHelper = requirementsHelpers.get(clazz);
if (requirementsHelper == null) {
requirementsHelper = new RequirementsHelper(this, getRequirements(), getScheduler(), clazz);
requirementsHelpers.put(clazz, requirementsHelper);
}
}
requirementsHelper.start();
}
@Override
......@@ -237,6 +253,7 @@ public abstract class DownloadService extends Service {
Log.e(TAG, "Ignoring unrecognized action: " + intentAction);
break;
}
maybeStartWatchingRequirements();
if (downloadManager.isIdle()) {
stop();
}
......@@ -248,14 +265,7 @@ public abstract class DownloadService extends Service {
logd("onDestroy");
foregroundNotificationUpdater.stopPeriodicUpdates();
downloadManager.removeListener(downloadManagerListener);
if (downloadManager.getTaskCount() == 0) {
synchronized (requirementsHelpers) {
RequirementsHelper requirementsHelper = requirementsHelpers.remove(getClass());
if (requirementsHelper != null) {
requirementsHelper.stop();
}
}
}
maybeStopWatchingRequirements();
}
@Nullable
......@@ -312,6 +322,31 @@ public abstract class DownloadService extends Service {
// Do nothing.
}
private void maybeStartWatchingRequirements() {
if (downloadManager.getDownloadCount() == 0) {
return;
}
Class<? extends DownloadService> clazz = getClass();
RequirementsHelper requirementsHelper = requirementsHelpers.get(clazz);
if (requirementsHelper == null) {
requirementsHelper = new RequirementsHelper(this, getRequirements(), getScheduler(), clazz);
requirementsHelpers.put(clazz, requirementsHelper);
requirementsHelper.start();
logd("started watching requirements");
}
}
private void maybeStopWatchingRequirements() {
if (downloadManager.getDownloadCount() > 0) {
return;
}
RequirementsHelper requirementsHelper = requirementsHelpers.remove(getClass());
if (requirementsHelper != null) {
requirementsHelper.stop();
logd("stopped watching requirements");
}
}
private void stop() {
foregroundNotificationUpdater.stopPeriodicUpdates();
// Make sure startForeground is called before stopping. Workaround for [Internal: b/69424260].
......@@ -331,7 +366,7 @@ public abstract class DownloadService extends Service {
private final class DownloadManagerListener implements DownloadManager.Listener {
@Override
public void onInitialized(DownloadManager downloadManager) {
// Do nothing.
maybeStartWatchingRequirements();
}
@Override
......
......@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.offline;
package com.google.android.exoplayer2.offline;
import android.net.Uri;
import com.google.android.exoplayer2.C;
......
......@@ -201,6 +201,8 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M, K>, K>
throws InterruptedException, IOException;
/** Initializes the download, returning a list of {@link Segment}s that need to be downloaded. */
// Writes to downloadedSegments and downloadedBytes are safe. See the comment on download().
@SuppressWarnings("NonAtomicVolatileUpdate")
private List<Segment> initDownload() throws IOException, InterruptedException {
M manifest = getManifest(dataSource, manifestUri);
if (!streamKeys.isEmpty()) {
......
......@@ -19,7 +19,6 @@ import android.os.Handler;
import android.os.Looper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.SparseIntArray;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer;
......@@ -34,6 +33,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
......@@ -656,7 +656,7 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
/* package */ static final class MediaSourceHolder implements Comparable<MediaSourceHolder> {
public final MediaSource mediaSource;
public final int uid;
public final Object uid;
public DeferredTimeline timeline;
public int childIndex;
......@@ -668,9 +668,9 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
public MediaSourceHolder(MediaSource mediaSource) {
this.mediaSource = mediaSource;
this.uid = System.identityHashCode(this);
this.timeline = new DeferredTimeline();
this.activeMediaPeriods = new ArrayList<>();
this.uid = new Object();
}
public void reset(int childIndex, int firstWindowIndexInChild, int firstPeriodIndexInChild) {
......@@ -728,8 +728,8 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
private final int[] firstPeriodInChildIndices;
private final int[] firstWindowInChildIndices;
private final Timeline[] timelines;
private final int[] uids;
private final SparseIntArray childIndexByUid;
private final Object[] uids;
private final HashMap<Object, Integer> childIndexByUid;
public ConcatenatedTimeline(
Collection<MediaSourceHolder> mediaSourceHolders,
......@@ -744,8 +744,8 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
firstPeriodInChildIndices = new int[childCount];
firstWindowInChildIndices = new int[childCount];
timelines = new Timeline[childCount];
uids = new int[childCount];
childIndexByUid = new SparseIntArray();
uids = new Object[childCount];
childIndexByUid = new HashMap<>();
int index = 0;
for (MediaSourceHolder mediaSourceHolder : mediaSourceHolders) {
timelines[index] = mediaSourceHolder.timeline;
......@@ -768,11 +768,8 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
@Override
protected int getChildIndexByChildUid(Object childUid) {
if (!(childUid instanceof Integer)) {
return C.INDEX_UNSET;
}
int index = childIndexByUid.get((int) childUid, -1);
return index == -1 ? C.INDEX_UNSET : index;
Integer index = childIndexByUid.get(childUid);
return index == null ? C.INDEX_UNSET : index;
}
@Override
......@@ -804,7 +801,6 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
public int getPeriodCount() {
return periodCount;
}
}
/**
......
......@@ -19,19 +19,25 @@ package com.google.android.exoplayer2.source;
@Deprecated
public final class DynamicConcatenatingMediaSource extends ConcatenatingMediaSource {
/** @deprecated Use {@link ConcatenatingMediaSource#ConcatenatingMediaSource()} instead. */
/**
* @deprecated Use {@link ConcatenatingMediaSource#ConcatenatingMediaSource(MediaSource...)}
* instead.
*/
@Deprecated
public DynamicConcatenatingMediaSource() {}
/** @deprecated Use {@link ConcatenatingMediaSource#ConcatenatingMediaSource(boolean)} instead. */
/**
* @deprecated Use {@link ConcatenatingMediaSource#ConcatenatingMediaSource(boolean,
* MediaSource...)} instead.
*/
@Deprecated
public DynamicConcatenatingMediaSource(boolean isAtomic) {
super(isAtomic);
}
/**
* @deprecated Use {@link ConcatenatingMediaSource#ConcatenatingMediaSource(boolean,
* ShuffleOrder)} instead.
* @deprecated Use {@link ConcatenatingMediaSource#ConcatenatingMediaSource(boolean, ShuffleOrder,
* MediaSource...)} instead.
*/
@Deprecated
public DynamicConcatenatingMediaSource(boolean isAtomic, ShuffleOrder shuffleOrder) {
......
......@@ -90,7 +90,7 @@ import java.util.Arrays;
private final Runnable onContinueLoadingRequestedRunnable;
private final Handler handler;
private Callback callback;
private @Nullable Callback callback;
private SeekMap seekMap;
private SampleQueue[] sampleQueues;
private int[] sampleQueueTrackIds;
......@@ -190,6 +190,7 @@ import java.util.Arrays;
}
loader.release(this);
handler.removeCallbacksAndMessages(null);
callback = null;
released = true;
eventDispatcher.mediaPeriodReleased();
}
......@@ -833,11 +834,6 @@ import java.util.Arrays;
}
@Override
public boolean isLoadCanceled() {
return loadCanceled;
}
@Override
public void load() throws IOException, InterruptedException {
int result = Extractor.RESULT_CONTINUE;
while (result == Extractor.RESULT_CONTINUE && !loadCanceled) {
......
......@@ -349,11 +349,6 @@ import java.util.Arrays;
}
@Override
public boolean isLoadCanceled() {
return false;
}
@Override
public void load() throws IOException, InterruptedException {
// We always load from the beginning, so reset the sampleSize to 0.
sampleSize = 0;
......
......@@ -72,11 +72,14 @@ public final class TrackGroup implements Parcelable {
}
/**
* Returns the index of the track with the given format in the group.
* Returns the index of the track with the given format in the group. The format is located by
* identity so, for example, {@code group.indexOf(group.getFormat(index)) == index} even if
* multiple tracks have formats that contain the same values.
*
* @param format The format.
* @return The index of the track, or {@link C#INDEX_UNSET} if no such track exists.
*/
@SuppressWarnings("ReferenceEquality")
public int indexOf(Format format) {
for (int i = 0; i < formats.length; i++) {
if (format == formats[i]) {
......
......@@ -44,7 +44,7 @@ public abstract class BaseMediaChunk extends MediaChunk {
* @param endTimeUs The end time of the media contained by the chunk, in microseconds.
* @param seekTimeUs The media time from which output will begin, or {@link C#TIME_UNSET} if the
* whole chunk should be output.
* @param chunkIndex The index of the chunk.
* @param chunkIndex The index of the chunk, or {@link C#INDEX_UNSET} if it is not known.
*/
public BaseMediaChunk(
DataSource dataSource,
......
......@@ -21,10 +21,8 @@ import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.source.SampleQueue;
import com.google.android.exoplayer2.source.chunk.ChunkExtractorWrapper.TrackOutputProvider;
/**
* An output for {@link BaseMediaChunk}s.
*/
/* package */ final class BaseMediaChunkOutput implements TrackOutputProvider {
/** An output for {@link BaseMediaChunk}s. */
public final class BaseMediaChunkOutput implements TrackOutputProvider {
private static final String TAG = "BaseMediaChunkOutput";
......
......@@ -49,7 +49,7 @@ public class ContainerMediaChunk extends BaseMediaChunk {
* @param endTimeUs The end time of the media contained by the chunk, in microseconds.
* @param seekTimeUs The media time from which output will begin, or {@link C#TIME_UNSET} if the
* whole chunk should be output.
* @param chunkIndex The index of the chunk.
* @param chunkIndex The index of the chunk, or {@link C#INDEX_UNSET} if it is not known.
* @param chunkCount The number of chunks in the underlying media that are spanned by this
* instance. Normally equal to one, but may be larger if multiple chunks as defined by the
* underlying media are being merged into a single load.
......@@ -106,11 +106,6 @@ public class ContainerMediaChunk extends BaseMediaChunk {
loadCanceled = true;
}
@Override
public final boolean isLoadCanceled() {
return loadCanceled;
}
@SuppressWarnings("NonAtomicVolatileUpdate")
@Override
public final void load() throws IOException, InterruptedException {
......
......@@ -76,11 +76,6 @@ public abstract class DataChunk extends Chunk {
}
@Override
public final boolean isLoadCanceled() {
return loadCanceled;
}
@Override
public final void load() throws IOException, InterruptedException {
try {
dataSource.open(dataSpec);
......
......@@ -69,11 +69,6 @@ public final class InitializationChunk extends Chunk {
loadCanceled = true;
}
@Override
public boolean isLoadCanceled() {
return loadCanceled;
}
@SuppressWarnings("NonAtomicVolatileUpdate")
@Override
public void load() throws IOException, InterruptedException {
......
......@@ -26,7 +26,7 @@ import com.google.android.exoplayer2.util.Assertions;
*/
public abstract class MediaChunk extends Chunk {
/** The chunk index. */
/** The chunk index, or {@link C#INDEX_UNSET} if it is not known. */
public final long chunkIndex;
/**
......@@ -37,7 +37,7 @@ public abstract class MediaChunk extends Chunk {
* @param trackSelectionData See {@link #trackSelectionData}.
* @param startTimeUs The start time of the media contained by the chunk, in microseconds.
* @param endTimeUs The end time of the media contained by the chunk, in microseconds.
* @param chunkIndex The index of the chunk.
* @param chunkIndex The index of the chunk, or {@link C#INDEX_UNSET} if it is not known.
*/
public MediaChunk(
DataSource dataSource,
......@@ -54,9 +54,9 @@ public abstract class MediaChunk extends Chunk {
this.chunkIndex = chunkIndex;
}
/** Returns the next chunk index. */
/** Returns the next chunk index or {@link C#INDEX_UNSET} if it is not known. */
public long getNextChunkIndex() {
return chunkIndex + 1;
return chunkIndex != C.INDEX_UNSET ? chunkIndex + 1 : C.INDEX_UNSET;
}
/**
......
......@@ -34,7 +34,6 @@ public final class SingleSampleMediaChunk extends BaseMediaChunk {
private final Format sampleFormat;
private volatile int bytesLoaded;
private volatile boolean loadCanceled;
private volatile boolean loadCompleted;
/**
......@@ -45,7 +44,7 @@ public final class SingleSampleMediaChunk extends BaseMediaChunk {
* @param trackSelectionData See {@link #trackSelectionData}.
* @param startTimeUs The start time of the media contained by the chunk, in microseconds.
* @param endTimeUs The end time of the media contained by the chunk, in microseconds.
* @param chunkIndex The index of the chunk.
* @param chunkIndex The index of the chunk, or {@link C#INDEX_UNSET} if it is not known.
* @param trackType The type of the chunk. Typically one of the {@link C} {@code TRACK_TYPE_*}
* constants.
* @param sampleFormat The {@link Format} of the sample in the chunk.
......@@ -90,12 +89,7 @@ public final class SingleSampleMediaChunk extends BaseMediaChunk {
@Override
public void cancelLoad() {
loadCanceled = true;
}
@Override
public boolean isLoadCanceled() {
return loadCanceled;
// Do nothing.
}
@SuppressWarnings("NonAtomicVolatileUpdate")
......
......@@ -374,6 +374,9 @@ public final class Cea608Decoder extends CeaDecoder {
private void handleMidrowCtrl(byte cc2) {
// TODO: support the extended styles (i.e. backgrounds and transparencies)
// A midrow control code advances the cursor.
currentCueBuilder.append(' ');
// cc2 - 0|0|1|0|ATRBT|U
// ATRBT is the 3-byte encoded attribute, and U is the underline toggle
boolean isUnderlined = (cc2 & 0x01) == 0x01;
......
......@@ -38,7 +38,6 @@ import com.google.android.exoplayer2.util.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
/**
......@@ -196,7 +195,10 @@ public final class Cea708Decoder extends CeaDecoder {
@Override
protected void decode(SubtitleInputBuffer inputBuffer) {
ccData.reset(inputBuffer.data.array(), inputBuffer.data.limit());
// Subtitle input buffers are non-direct and the position is zero, so calling array() is safe.
@SuppressWarnings("ByteBufferBackingArray")
byte[] inputBufferData = inputBuffer.data.array();
ccData.reset(inputBufferData, inputBuffer.data.limit());
while (ccData.bytesLeft() >= 3) {
int ccTypeAndValid = (ccData.readUnsignedByte() & 0x07);
......@@ -879,7 +881,7 @@ public final class Cea708Decoder extends CeaDecoder {
private int row;
public CueBuilder() {
rolledUpCaptions = new LinkedList<>();
rolledUpCaptions = new ArrayList<>();
captionStringBuilder = new SpannableStringBuilder();
reset();
}
......
......@@ -24,7 +24,7 @@ import com.google.android.exoplayer2.text.SubtitleDecoderException;
import com.google.android.exoplayer2.text.SubtitleInputBuffer;
import com.google.android.exoplayer2.text.SubtitleOutputBuffer;
import com.google.android.exoplayer2.util.Assertions;
import java.util.LinkedList;
import java.util.ArrayDeque;
import java.util.PriorityQueue;
/**
......@@ -35,8 +35,8 @@ import java.util.PriorityQueue;
private static final int NUM_INPUT_BUFFERS = 10;
private static final int NUM_OUTPUT_BUFFERS = 2;
private final LinkedList<CeaInputBuffer> availableInputBuffers;
private final LinkedList<SubtitleOutputBuffer> availableOutputBuffers;
private final ArrayDeque<CeaInputBuffer> availableInputBuffers;
private final ArrayDeque<SubtitleOutputBuffer> availableOutputBuffers;
private final PriorityQueue<CeaInputBuffer> queuedInputBuffers;
private CeaInputBuffer dequeuedInputBuffer;
......@@ -44,11 +44,11 @@ import java.util.PriorityQueue;
private long queuedInputBufferCount;
public CeaDecoder() {
availableInputBuffers = new LinkedList<>();
availableInputBuffers = new ArrayDeque<>();
for (int i = 0; i < NUM_INPUT_BUFFERS; i++) {
availableInputBuffers.add(new CeaInputBuffer());
}
availableOutputBuffers = new LinkedList<>();
availableOutputBuffers = new ArrayDeque<>();
for (int i = 0; i < NUM_OUTPUT_BUFFERS; i++) {
availableOutputBuffers.add(new CeaOutputBuffer());
}
......
......@@ -62,7 +62,7 @@ public final class SsaDecoder extends SimpleSubtitleDecoder {
super("SsaDecoder");
if (initializationData != null && !initializationData.isEmpty()) {
haveInitializationData = true;
String formatLine = new String(initializationData.get(0));
String formatLine = Util.fromUtf8Bytes(initializationData.get(0));
Assertions.checkArgument(formatLine.startsWith(FORMAT_LINE_PREFIX));
parseFormatLine(formatLine);
parseHeader(new ParsableByteArray(initializationData.get(1)));
......
......@@ -26,8 +26,8 @@ import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.util.XmlPullParserUtil;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
......@@ -109,13 +109,13 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes, 0, length);
xmlParser.setInput(inputStream, null);
TtmlSubtitle ttmlSubtitle = null;
LinkedList<TtmlNode> nodeStack = new LinkedList<>();
ArrayDeque<TtmlNode> nodeStack = new ArrayDeque<>();
int unsupportedNodeDepth = 0;
int eventType = xmlParser.getEventType();
FrameAndTickRate frameAndTickRate = DEFAULT_FRAME_AND_TICK_RATE;
CellResolution cellResolution = DEFAULT_CELL_RESOLUTION;
while (eventType != XmlPullParser.END_DOCUMENT) {
TtmlNode parent = nodeStack.peekLast();
TtmlNode parent = nodeStack.peek();
if (unsupportedNodeDepth == 0) {
String name = xmlParser.getName();
if (eventType == XmlPullParser.START_TAG) {
......@@ -131,7 +131,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
} else {
try {
TtmlNode node = parseNode(xmlParser, parent, regionMap, frameAndTickRate);
nodeStack.addLast(node);
nodeStack.push(node);
if (parent != null) {
parent.addChild(node);
}
......@@ -145,9 +145,9 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
parent.addChild(TtmlNode.buildTextNode(xmlParser.getText()));
} else if (eventType == XmlPullParser.END_TAG) {
if (xmlParser.getName().equals(TtmlNode.TAG_TT)) {
ttmlSubtitle = new TtmlSubtitle(nodeStack.getLast(), globalStyles, regionMap);
ttmlSubtitle = new TtmlSubtitle(nodeStack.peek(), globalStyles, regionMap);
}
nodeStack.removeLast();
nodeStack.pop();
}
} else {
if (eventType == XmlPullParser.START_TAG) {
......@@ -178,7 +178,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
float frameRateMultiplier = 1;
String frameRateMultiplierString = xmlParser.getAttributeValue(TTP, "frameRateMultiplier");
if (frameRateMultiplierString != null) {
String[] parts = frameRateMultiplierString.split(" ");
String[] parts = Util.split(frameRateMultiplierString, " ");
if (parts.length != 2) {
throw new SubtitleDecoderException("frameRateMultiplier doesn't have 2 parts");
}
......@@ -354,7 +354,8 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
}
private String[] parseStyleIds(String parentStyleIds) {
return parentStyleIds.split("\\s+");
parentStyleIds = parentStyleIds.trim();
return parentStyleIds.isEmpty() ? new String[0] : Util.split(parentStyleIds, "\\s+");
}
private TtmlStyle parseStyleAttributes(XmlPullParser parser, TtmlStyle style) {
......@@ -531,7 +532,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
private static void parseFontSize(String expression, TtmlStyle out) throws
SubtitleDecoderException {
String[] expressions = expression.split("\\s+");
String[] expressions = Util.split(expression, "\\s+");
Matcher matcher;
if (expressions.length == 1) {
matcher = FONT_SIZE.matcher(expression);
......
......@@ -92,7 +92,8 @@ public final class Tx3gDecoder extends SimpleSubtitleDecoder {
| ((initializationBytes[27] & 0xFF) << 16)
| ((initializationBytes[28] & 0xFF) << 8)
| (initializationBytes[29] & 0xFF);
String fontFamily = new String(initializationBytes, 43, initializationBytes.length - 43);
String fontFamily =
Util.fromUtf8Bytes(initializationBytes, 43, initializationBytes.length - 43);
defaultFontFamily = TX3G_SERIF.equals(fontFamily) ? C.SERIF_NAME : C.SANS_SERIF_NAME;
//font size (initializationBytes[25]) is 5% of video height
calculatedVideoTrackHeight = 20 * initializationBytes[25];
......
......@@ -18,6 +18,7 @@ package com.google.android.exoplayer2.text.webvtt;
import android.text.TextUtils;
import com.google.android.exoplayer2.util.ColorParser;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
......@@ -314,7 +315,7 @@ import java.util.regex.Pattern;
}
selector = selector.substring(0, voiceStartIndex);
}
String[] classDivision = selector.split("\\.");
String[] classDivision = Util.split(selector, "\\.");
String tagAndIdDivision = classDivision[0];
int idPrefixIndex = tagAndIdDivision.indexOf('#');
if (idPrefixIndex != -1) {
......
......@@ -78,7 +78,8 @@ public final class Mp4WebvttDecoder extends SimpleSubtitleDecoder {
int boxType = sampleData.readInt();
remainingCueBoxBytes -= BOX_HEADER_SIZE;
int payloadLength = boxSize - BOX_HEADER_SIZE;
String boxPayload = new String(sampleData.data, sampleData.getPosition(), payloadLength);
String boxPayload =
Util.fromUtf8Bytes(sampleData.data, sampleData.getPosition(), payloadLength);
sampleData.skipBytes(payloadLength);
remainingCueBoxBytes -= payloadLength;
if (boxType == TYPE_sttg) {
......
......@@ -34,11 +34,12 @@ import android.text.style.UnderlineSpan;
import android.util.Log;
import com.google.android.exoplayer2.text.Cue;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
......@@ -157,7 +158,7 @@ public final class WebvttCueParser {
/* package */ static void parseCueText(String id, String markup, WebvttCue.Builder builder,
List<WebvttCssStyle> styles) {
SpannableStringBuilder spannedText = new SpannableStringBuilder();
Stack<StartTag> startTagStack = new Stack<>();
ArrayDeque<StartTag> startTagStack = new ArrayDeque<>();
List<StyleMatch> scratchStyleMatches = new ArrayList<>();
int pos = 0;
while (pos < markup.length()) {
......@@ -456,7 +457,7 @@ public final class WebvttCueParser {
if (tagExpression.isEmpty()) {
return null;
}
return tagExpression.split("[ \\.]")[0];
return Util.splitAtFirst(tagExpression, "[ \\.]")[0];
}
private static void getApplicableStyles(List<WebvttCssStyle> declaredStyles, String id,
......@@ -518,7 +519,7 @@ public final class WebvttCueParser {
voice = fullTagExpression.substring(voiceStartIndex).trim();
fullTagExpression = fullTagExpression.substring(0, voiceStartIndex);
}
String[] nameAndClasses = fullTagExpression.split("\\.");
String[] nameAndClasses = Util.split(fullTagExpression, "\\.");
String name = nameAndClasses[0];
String[] classes;
if (nameAndClasses.length > 1) {
......
......@@ -17,6 +17,7 @@ package com.google.android.exoplayer2.text.webvtt;
import com.google.android.exoplayer2.text.SubtitleDecoderException;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
......@@ -53,8 +54,8 @@ public final class WebvttParserUtil {
*/
public static long parseTimestampUs(String timestamp) throws NumberFormatException {
long value = 0;
String[] parts = timestamp.split("\\.", 2);
String[] subparts = parts[0].split(":");
String[] parts = Util.splitAtFirst(timestamp, "\\.");
String[] subparts = Util.split(parts[0], ":");
for (String subpart : subparts) {
value = (value * 60) + Long.parseLong(subpart);
}
......
......@@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.trackselection;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.source.TrackGroup;
......@@ -242,9 +243,11 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
this.minTimeBetweenBufferReevaluationMs = minTimeBetweenBufferReevaluationMs;
this.clock = clock;
playbackSpeed = 1f;
selectedIndex = determineIdealSelectedIndex(Long.MIN_VALUE);
reason = C.SELECTION_REASON_INITIAL;
lastBufferEvaluationMs = C.TIME_UNSET;
@SuppressWarnings("nullness:method.invocation.invalid")
int selectedIndex = determineIdealSelectedIndex(Long.MIN_VALUE);
this.selectedIndex = selectedIndex;
}
@Override
......@@ -301,7 +304,7 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
}
@Override
public Object getSelectionData() {
public @Nullable Object getSelectionData() {
return null;
}
......
......@@ -110,6 +110,7 @@ public abstract class BaseTrackSelection implements TrackSelection {
}
@Override
@SuppressWarnings("ReferenceEquality")
public final int indexOf(Format format) {
for (int i = 0; i < length; i++) {
if (formats[i] == format) {
......@@ -183,7 +184,9 @@ public abstract class BaseTrackSelection implements TrackSelection {
return hashCode;
}
// Track groups are compared by identity not value, as distinct groups may have the same value.
@Override
@SuppressWarnings("ReferenceEquality")
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
......
......@@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.trackselection;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.util.Assertions;
......@@ -30,7 +31,7 @@ public final class FixedTrackSelection extends BaseTrackSelection {
public static final class Factory implements TrackSelection.Factory {
private final int reason;
private final Object data;
private final @Nullable Object data;
public Factory() {
this.reason = C.SELECTION_REASON_UNKNOWN;
......@@ -41,7 +42,7 @@ public final class FixedTrackSelection extends BaseTrackSelection {
* @param reason A reason for the track selection.
* @param data Optional data associated with the track selection.
*/
public Factory(int reason, Object data) {
public Factory(int reason, @Nullable Object data) {
this.reason = reason;
this.data = data;
}
......@@ -51,11 +52,10 @@ public final class FixedTrackSelection extends BaseTrackSelection {
Assertions.checkArgument(tracks.length == 1);
return new FixedTrackSelection(group, tracks[0], reason, data);
}
}
private final int reason;
private final Object data;
private final @Nullable Object data;
/**
* @param group The {@link TrackGroup}. Must not be null.
......@@ -71,7 +71,7 @@ public final class FixedTrackSelection extends BaseTrackSelection {
* @param reason A reason for the track selection.
* @param data Optional data associated with the track selection.
*/
public FixedTrackSelection(TrackGroup group, int track, int reason, Object data) {
public FixedTrackSelection(TrackGroup group, int track, int reason, @Nullable Object data) {
super(group, track);
this.reason = reason;
this.data = data;
......@@ -94,7 +94,7 @@ public final class FixedTrackSelection extends BaseTrackSelection {
}
@Override
public Object getSelectionData() {
public @Nullable Object getSelectionData() {
return data;
}
......
......@@ -16,6 +16,7 @@
package com.google.android.exoplayer2.trackselection;
import android.support.annotation.IntDef;
import android.support.annotation.Nullable;
import android.util.Pair;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
......@@ -301,13 +302,13 @@ public abstract class MappingTrackSelector extends TrackSelector {
}
private MappedTrackInfo currentMappedTrackInfo;
private @Nullable MappedTrackInfo currentMappedTrackInfo;
/**
* Returns the mapping information for the currently active track selection, or null if no
* selection is currently active.
*/
public final MappedTrackInfo getCurrentMappedTrackInfo() {
public final @Nullable MappedTrackInfo getCurrentMappedTrackInfo() {
return currentMappedTrackInfo;
}
......@@ -357,9 +358,11 @@ public abstract class MappingTrackSelector extends TrackSelector {
int[] rendererTrackTypes = new int[rendererCapabilities.length];
for (int i = 0; i < rendererCapabilities.length; i++) {
int rendererTrackGroupCount = rendererTrackGroupCounts[i];
rendererTrackGroupArrays[i] = new TrackGroupArray(
Arrays.copyOf(rendererTrackGroups[i], rendererTrackGroupCount));
rendererFormatSupports[i] = Arrays.copyOf(rendererFormatSupports[i], rendererTrackGroupCount);
rendererTrackGroupArrays[i] =
new TrackGroupArray(
Util.nullSafeArrayCopy(rendererTrackGroups[i], rendererTrackGroupCount));
rendererFormatSupports[i] =
Util.nullSafeArrayCopy(rendererFormatSupports[i], rendererTrackGroupCount);
rendererTrackTypes[i] = rendererCapabilities[i].getTrackType();
}
......@@ -367,7 +370,7 @@ public abstract class MappingTrackSelector extends TrackSelector {
int unmappedTrackGroupCount = rendererTrackGroupCounts[rendererCapabilities.length];
TrackGroupArray unmappedTrackGroupArray =
new TrackGroupArray(
Arrays.copyOf(
Util.nullSafeArrayCopy(
rendererTrackGroups[rendererCapabilities.length], unmappedTrackGroupCount));
// Package up the track information and selections.
......@@ -399,11 +402,12 @@ public abstract class MappingTrackSelector extends TrackSelector {
* RendererCapabilities#getTrackType()} is {@link C#TRACK_TYPE_NONE}.
* @throws ExoPlaybackException If an error occurs while selecting the tracks.
*/
protected abstract Pair<RendererConfiguration[], TrackSelection[]> selectTracks(
MappedTrackInfo mappedTrackInfo,
int[][][] rendererFormatSupports,
int[] rendererMixedMimeTypeAdaptationSupport)
throws ExoPlaybackException;
protected abstract Pair<RendererConfiguration[], TrackSelection[]>
selectTracks(
MappedTrackInfo mappedTrackInfo,
int[][][] rendererFormatSupports,
int[] rendererMixedMimeTypeAdaptationSupport)
throws ExoPlaybackException;
/**
* Finds the renderer to which the provided {@link TrackGroup} should be mapped.
......
......@@ -16,6 +16,7 @@
package com.google.android.exoplayer2.trackselection;
import android.os.SystemClock;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.source.TrackGroup;
import java.util.Random;
......@@ -47,7 +48,6 @@ public final class RandomTrackSelection extends BaseTrackSelection {
public RandomTrackSelection createTrackSelection(TrackGroup group, int... tracks) {
return new RandomTrackSelection(group, tracks, random);
}
}
private final Random random;
......@@ -123,7 +123,7 @@ public final class RandomTrackSelection extends BaseTrackSelection {
}
@Override
public Object getSelectionData() {
public @Nullable Object getSelectionData() {
return null;
}
......
......@@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.trackselection;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.source.TrackGroup;
......@@ -90,7 +91,9 @@ public interface TrackSelection {
int getIndexInTrackGroup(int index);
/**
* Returns the index in the selection of the track with the specified format.
* Returns the index in the selection of the track with the specified format. The format is
* located by identity so, for example, {@code selection.indexOf(selection.getFormat(index)) ==
* index} even if multiple selected tracks have formats that contain the same values.
*
* @param format The format.
* @return The index in the selection, or {@link C#INDEX_UNSET} if the track with the specified
......@@ -129,10 +132,8 @@ public interface TrackSelection {
*/
int getSelectionReason();
/**
* Returns optional data associated with the current track selection.
*/
Object getSelectionData();
/** Returns optional data associated with the current track selection. */
@Nullable Object getSelectionData();
// Adaptation.
......
......@@ -29,9 +29,7 @@ public final class TrackSelectionArray {
// Lazily initialized hashcode.
private int hashCode;
/**
* @param trackSelections The selections. Must not be null, but may contain null elements.
*/
/** @param trackSelections The selections. Must not be null, but may contain null elements. */
public TrackSelectionArray(TrackSelection... trackSelections) {
this.trackSelections = trackSelections;
this.length = trackSelections.length;
......@@ -43,13 +41,11 @@ public final class TrackSelectionArray {
* @param index The index of the selection.
* @return The selection.
*/
public TrackSelection get(int index) {
public @Nullable TrackSelection get(int index) {
return trackSelections[index];
}
/**
* Returns the selections in a newly allocated array.
*/
/** Returns the selections in a newly allocated array. */
public TrackSelection[] getAll() {
return trackSelections.clone();
}
......
......@@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.trackselection;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.Renderer;
......@@ -89,7 +90,7 @@ public abstract class TrackSelector {
}
private InvalidationListener listener;
private @Nullable InvalidationListener listener;
/**
* Called by the player to initialize the selector.
......
......@@ -48,7 +48,9 @@ public final class TrackSelectorResult {
* TrackSelector#onSelectionActivated(Object)} should the selection be activated.
*/
public TrackSelectorResult(
RendererConfiguration[] rendererConfigurations, TrackSelection[] selections, Object info) {
RendererConfiguration[] rendererConfigurations,
TrackSelection[] selections,
Object info) {
this.rendererConfigurations = rendererConfigurations;
this.selections = new TrackSelectionArray(selections);
this.info = info;
......
......@@ -19,6 +19,7 @@ import android.net.Uri;
import android.util.Base64;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
import java.net.URLDecoder;
......@@ -41,8 +42,8 @@ public final class DataSchemeDataSource implements DataSource {
if (!SCHEME_DATA.equals(scheme)) {
throw new ParserException("Unsupported scheme: " + scheme);
}
String[] uriParts = uri.getSchemeSpecificPart().split(",");
if (uriParts.length > 2) {
String[] uriParts = Util.split(uri.getSchemeSpecificPart(), ",");
if (uriParts.length != 2) {
throw new ParserException("Unexpected URI format: " + uri);
}
String dataString = uriParts[1];
......
......@@ -58,11 +58,6 @@ public final class Loader implements LoaderErrorThrower {
void cancelLoad();
/**
* Returns whether the load has been canceled.
*/
boolean isLoadCanceled();
/**
* Performs the load, returning on completion or cancellation.
*
* @throws IOException If the input could not be loaded.
......@@ -250,15 +245,17 @@ public final class Loader implements LoaderErrorThrower {
private static final int MSG_IO_EXCEPTION = 3;
private static final int MSG_FATAL_ERROR = 4;
private final T loadable;
private final Loader.Callback<T> callback;
public final int defaultMinRetryCount;
private final T loadable;
private final long startTimeMs;
private @Nullable Loader.Callback<T> callback;
private IOException currentError;
private int errorCount;
private volatile Thread executorThread;
private volatile boolean canceled;
private volatile boolean released;
public LoadTask(Looper looper, T loadable, Loader.Callback<T> callback,
......@@ -295,6 +292,7 @@ public final class Loader implements LoaderErrorThrower {
sendEmptyMessage(MSG_CANCEL);
}
} else {
canceled = true;
loadable.cancelLoad();
if (executorThread != null) {
executorThread.interrupt();
......@@ -304,6 +302,11 @@ public final class Loader implements LoaderErrorThrower {
finish();
long nowMs = SystemClock.elapsedRealtime();
callback.onLoadCanceled(loadable, nowMs, nowMs - startTimeMs, true);
// If loading, this task will be referenced from a GC root (the loading thread) until
// cancellation completes. The time taken for cancellation to complete depends on the
// implementation of the Loadable that the task is loading. We null the callback reference
// here so that it doesn't prevent garbage collection whilst cancellation is ongoing.
callback = null;
}
}
......@@ -311,7 +314,7 @@ public final class Loader implements LoaderErrorThrower {
public void run() {
try {
executorThread = Thread.currentThread();
if (!loadable.isLoadCanceled()) {
if (!canceled) {
TraceUtil.beginSection("load:" + loadable.getClass().getSimpleName());
try {
loadable.load();
......@@ -328,7 +331,7 @@ public final class Loader implements LoaderErrorThrower {
}
} catch (InterruptedException e) {
// The load was canceled.
Assertions.checkState(loadable.isLoadCanceled());
Assertions.checkState(canceled);
if (!released) {
sendEmptyMessage(MSG_END_OF_SOURCE);
}
......@@ -373,7 +376,7 @@ public final class Loader implements LoaderErrorThrower {
finish();
long nowMs = SystemClock.elapsedRealtime();
long durationMs = nowMs - startTimeMs;
if (loadable.isLoadCanceled()) {
if (canceled) {
callback.onLoadCanceled(loadable, nowMs, durationMs, false);
return;
}
......
......@@ -78,7 +78,6 @@ public final class ParsingLoadable<T> implements Loadable {
private final Parser<? extends T> parser;
private volatile T result;
private volatile boolean isCanceled;
private volatile long bytesLoaded;
/**
......@@ -128,14 +127,7 @@ public final class ParsingLoadable<T> implements Loadable {
@Override
public final void cancelLoad() {
// We don't actually cancel anything, but we need to record the cancellation so that
// isLoadCanceled can return the correct value.
isCanceled = true;
}
@Override
public final boolean isLoadCanceled() {
return isCanceled;
// Do nothing.
}
@Override
......
......@@ -18,7 +18,6 @@ package com.google.android.exoplayer2.upstream.cache;
import android.net.Uri;
import android.support.annotation.IntDef;
import android.support.annotation.Nullable;
import android.util.Log;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.upstream.DataSink;
import com.google.android.exoplayer2.upstream.DataSource;
......@@ -52,8 +51,6 @@ public final class CacheDataSource implements DataSource {
*/
public static final long DEFAULT_MAX_CACHE_FILE_SIZE = 2 * 1024 * 1024;
private static final String TAG = "CacheDataSource";
/**
* Flags controlling the cache's behavior.
*/
......@@ -221,7 +218,7 @@ public final class CacheDataSource implements DataSource {
try {
key = CacheUtil.getKey(dataSpec);
uri = dataSpec.uri;
actualUri = loadRedirectedUriOrReturnGivenUri(cache, key, uri);
actualUri = getRedirectedUriOrDefault(cache, key, /* defaultUri= */ uri);
flags = dataSpec.flags;
readPosition = dataSpec.position;
......@@ -272,7 +269,7 @@ public final class CacheDataSource implements DataSource {
bytesRemaining -= bytesRead;
}
} else if (currentDataSpecLengthUnset) {
setBytesRemainingAndMaybeStoreLength(0);
setNoBytesRemainingAndMaybeStoreLength();
} else if (bytesRemaining > 0 || bytesRemaining == C.LENGTH_UNSET) {
closeCurrentSource();
openNextSource(false);
......@@ -281,7 +278,7 @@ public final class CacheDataSource implements DataSource {
return bytesRead;
} catch (IOException e) {
if (currentDataSpecLengthUnset && isCausedByPositionOutOfRange(e)) {
setBytesRemainingAndMaybeStoreLength(0);
setNoBytesRemainingAndMaybeStoreLength();
return C.RESULT_END_OF_INPUT;
}
handleBeforeThrow(e);
......@@ -402,46 +399,38 @@ public final class CacheDataSource implements DataSource {
currentDataSource = nextDataSource;
currentDataSpecLengthUnset = nextDataSpec.length == C.LENGTH_UNSET;
long resolvedLength = nextDataSource.open(nextDataSpec);
// Update bytesRemaining, actualUri and (if writing to cache) the cache metadata.
ContentMetadataMutations mutations = new ContentMetadataMutations();
if (currentDataSpecLengthUnset && resolvedLength != C.LENGTH_UNSET) {
setBytesRemainingAndMaybeStoreLength(resolvedLength);
bytesRemaining = resolvedLength;
ContentMetadataInternal.setContentLength(mutations, readPosition + bytesRemaining);
}
// TODO find a way to store length and redirected uri in one metadata mutation.
maybeUpdateActualUriFieldAndRedirectedUriMetadata();
}
private void maybeUpdateActualUriFieldAndRedirectedUriMetadata() {
if (!isReadingFromUpstream()) {
return;
if (isReadingFromUpstream()) {
actualUri = currentDataSource.getUri();
boolean isRedirected = !uri.equals(actualUri);
if (isRedirected) {
ContentMetadataInternal.setRedirectedUri(mutations, actualUri);
} else {
ContentMetadataInternal.removeRedirectedUri(mutations);
}
}
if (isWritingToCache()) {
cache.applyContentMetadataMutations(key, mutations);
}
actualUri = currentDataSource.getUri();
maybeUpdateRedirectedUriMetadata();
}
private void maybeUpdateRedirectedUriMetadata() {
if (!isWritingToCache()) {
return;
}
ContentMetadataMutations mutations = new ContentMetadataMutations();
boolean isRedirected = !uri.equals(actualUri);
if (isRedirected) {
ContentMetadataInternal.setRedirectedUri(mutations, actualUri);
} else {
ContentMetadataInternal.removeRedirectedUri(mutations);
}
try {
cache.applyContentMetadataMutations(key, mutations);
} catch (CacheException e) {
String message =
"Couldn't update redirected URI. "
+ "This might cause relative URIs get resolved incorrectly.";
Log.w(TAG, message, e);
private void setNoBytesRemainingAndMaybeStoreLength() throws IOException {
bytesRemaining = 0;
if (isWritingToCache()) {
cache.setContentLength(key, readPosition);
}
}
private static Uri loadRedirectedUriOrReturnGivenUri(Cache cache, String key, Uri uri) {
private static Uri getRedirectedUriOrDefault(Cache cache, String key, Uri defaultUri) {
ContentMetadata contentMetadata = cache.getContentMetadata(key);
Uri redirectedUri = ContentMetadataInternal.getRedirectedUri(contentMetadata);
return redirectedUri == null ? uri : redirectedUri;
return redirectedUri == null ? defaultUri : redirectedUri;
}
private static boolean isCausedByPositionOutOfRange(IOException e) {
......@@ -458,13 +447,6 @@ public final class CacheDataSource implements DataSource {
return false;
}
private void setBytesRemainingAndMaybeStoreLength(long bytesRemaining) throws IOException {
this.bytesRemaining = bytesRemaining;
if (isWritingToCache()) {
cache.setContentLength(key, readPosition + bytesRemaining);
}
}
private boolean isReadingFromUpstream() {
return !isReadingFromCache();
}
......
......@@ -129,11 +129,11 @@ public final class CacheUtil {
cache,
new CacheDataSource(cache, upstream),
new byte[DEFAULT_BUFFER_SIZE_BYTES],
null,
0,
/* priorityTaskManager= */ null,
/* priority= */ 0,
counters,
null,
false);
isCanceled,
/* enableEOFException= */ false);
}
/**
......
......@@ -20,7 +20,7 @@ import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
/** Helper classes to easily access and modify internal metadata values. */
/*package*/ final class ContentMetadataInternal {
/* package */ final class ContentMetadataInternal {
private static final String PREFIX = ContentMetadata.INTERNAL_METADATA_NAME_PREFIX;
private static final String METADATA_NAME_REDIRECTED_URI = PREFIX + "redir";
......@@ -59,4 +59,8 @@ import com.google.android.exoplayer2.C;
public static void removeRedirectedUri(ContentMetadataMutations mutations) {
mutations.remove(METADATA_NAME_REDIRECTED_URI);
}
private ContentMetadataInternal() {
// Prevent instantiation.
}
}
......@@ -16,6 +16,7 @@
package com.google.android.exoplayer2.upstream.crypto;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
import java.nio.ByteBuffer;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
......@@ -49,7 +50,9 @@ public final class AesFlushingCipher {
flushedBlock = new byte[blockSize];
long counter = offset / blockSize;
int startPadding = (int) (offset % blockSize);
cipher.init(mode, new SecretKeySpec(secretKey, cipher.getAlgorithm().split("/")[0]),
cipher.init(
mode,
new SecretKeySpec(secretKey, Util.splitAtFirst(cipher.getAlgorithm(), "/")[0]),
new IvParameterSpec(getInitializationVector(nonce, counter)));
if (startPadding != 0) {
updateInPlace(new byte[startPadding], 0, startPadding);
......
......@@ -26,7 +26,7 @@ import java.util.regex.Pattern;
*
* @see <a href="https://w3c.github.io/webvtt/#styling">WebVTT CSS Styling</a>
* @see <a href="https://www.w3.org/TR/ttml2/">Timed Text Markup Language 2 (TTML2) - 10.3.5</a>
**/
*/
public final class ColorParser {
private static final String RGB = "rgb";
......@@ -271,4 +271,7 @@ public final class ColorParser {
COLOR_MAP.put("yellowgreen", 0xFF9ACD32);
}
private ColorParser() {
// Prevent instantiation.
}
}
......@@ -111,12 +111,20 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL
GLES20.glDeleteTextures(1, textureIdHolder, 0);
}
} finally {
if (display != null && !display.equals(EGL14.EGL_NO_DISPLAY)) {
EGL14.eglMakeCurrent(
display, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);
}
if (surface != null && !surface.equals(EGL14.EGL_NO_SURFACE)) {
EGL14.eglDestroySurface(display, surface);
}
if (context != null) {
EGL14.eglDestroyContext(display, context);
}
// EGL14.eglReleaseThread could crash before Android K (see [internal: b/11327779]).
if (Util.SDK_INT >= 19) {
EGL14.eglReleaseThread();
}
display = null;
context = null;
surface = null;
......
......@@ -18,6 +18,7 @@ package com.google.android.exoplayer2.util;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import com.google.android.exoplayer2.C;
import java.util.ArrayList;
/**
* Defines common MIME types and helper methods.
......@@ -92,7 +93,29 @@ public final class MimeTypes {
public static final String APPLICATION_DVBSUBS = BASE_TYPE_APPLICATION + "/dvbsubs";
public static final String APPLICATION_EXIF = BASE_TYPE_APPLICATION + "/x-exif";
private MimeTypes() {}
private static final ArrayList<CustomMimeType> customMimeTypes = new ArrayList<>();
/**
* Registers a custom MIME type. Most applications do not need to call this method, as handling of
* standard MIME types is built in. These built-in MIME types take precedence over any registered
* via this method. If this method is used, it must be called before creating any player(s).
*
* @param mimeType The custom MIME type to register.
* @param codecPrefix The RFC 6381-style codec string prefix associated with the MIME type.
* @param trackType The {@link C}{@code .TRACK_TYPE_*} constant associated with the MIME type.
* This value is ignored if the top-level type of {@code mimeType} is audio, video or text.
*/
public static void registerCustomMimeType(String mimeType, String codecPrefix, int trackType) {
CustomMimeType customMimeType = new CustomMimeType(mimeType, codecPrefix, trackType);
int customMimeTypeCount = customMimeTypes.size();
for (int i = 0; i < customMimeTypeCount; i++) {
if (mimeType.equals(customMimeTypes.get(i).mimeType)) {
customMimeTypes.remove(i);
break;
}
}
customMimeTypes.add(customMimeType);
}
/**
* Whether the top-level type of {@code mimeType} is audio.
......@@ -144,7 +167,7 @@ public final class MimeTypes {
if (codecs == null) {
return null;
}
String[] codecList = codecs.split(",");
String[] codecList = Util.split(codecs, ",");
for (String codec : codecList) {
String mimeType = getMediaMimeType(codec);
if (mimeType != null && isVideo(mimeType)) {
......@@ -164,7 +187,7 @@ public final class MimeTypes {
if (codecs == null) {
return null;
}
String[] codecList = codecs.split(",");
String[] codecList = Util.split(codecs, ",");
for (String codec : codecList) {
String mimeType = getMediaMimeType(codec);
if (mimeType != null && isAudio(mimeType)) {
......@@ -222,8 +245,9 @@ public final class MimeTypes {
return MimeTypes.AUDIO_OPUS;
} else if (codec.startsWith("vorbis")) {
return MimeTypes.AUDIO_VORBIS;
} else {
return getCustomMimeTypeForCodec(codec);
}
return null;
}
/**
......@@ -236,18 +260,28 @@ public final class MimeTypes {
@Nullable
public static String getMimeTypeFromMp4ObjectType(int objectType) {
switch (objectType) {
case 0x60:
case 0x61:
return MimeTypes.VIDEO_MPEG2;
case 0x20:
return MimeTypes.VIDEO_MP4V;
case 0x21:
return MimeTypes.VIDEO_H264;
case 0x23:
return MimeTypes.VIDEO_H265;
case 0x60:
case 0x61:
case 0x62:
case 0x63:
case 0x64:
case 0x65:
return MimeTypes.VIDEO_MPEG2;
case 0x6A:
return MimeTypes.VIDEO_MPEG;
case 0x69:
case 0x6B:
return MimeTypes.AUDIO_MPEG;
case 0xA3:
return MimeTypes.VIDEO_VC1;
case 0xB1:
return MimeTypes.VIDEO_VP9;
case 0x40:
case 0x66:
case 0x67:
......@@ -298,7 +332,7 @@ public final class MimeTypes {
|| APPLICATION_CAMERA_MOTION.equals(mimeType)) {
return C.TRACK_TYPE_METADATA;
} else {
return C.TRACK_TYPE_UNKNOWN;
return getTrackTypeForCustomMimeType(mimeType);
}
}
......@@ -355,4 +389,41 @@ public final class MimeTypes {
return mimeType.substring(0, indexOfSlash);
}
private static @Nullable String getCustomMimeTypeForCodec(String codec) {
int customMimeTypeCount = customMimeTypes.size();
for (int i = 0; i < customMimeTypeCount; i++) {
CustomMimeType customMimeType = customMimeTypes.get(i);
if (codec.startsWith(customMimeType.codecPrefix)) {
return customMimeType.mimeType;
}
}
return null;
}
private static int getTrackTypeForCustomMimeType(String mimeType) {
int customMimeTypeCount = customMimeTypes.size();
for (int i = 0; i < customMimeTypeCount; i++) {
CustomMimeType customMimeType = customMimeTypes.get(i);
if (mimeType.equals(customMimeType.mimeType)) {
return customMimeType.trackType;
}
}
return C.TRACK_TYPE_UNKNOWN;
}
private MimeTypes() {
// Prevent instantiation.
}
private static final class CustomMimeType {
public final String mimeType;
public final String codecPrefix;
public final int trackType;
public CustomMimeType(String mimeType, String codecPrefix, int trackType) {
this.mimeType = mimeType;
this.codecPrefix = codecPrefix;
this.trackType = trackType;
}
}
}
......@@ -175,7 +175,7 @@ public final class ParsableBitArray {
bitOffset -= 8;
returnValue |= (data[byteOffset++] & 0xFF) << bitOffset;
}
returnValue |= (data[byteOffset] & 0xFF) >> 8 - bitOffset;
returnValue |= (data[byteOffset] & 0xFF) >> (8 - bitOffset);
returnValue &= 0xFFFFFFFF >>> (32 - numBits);
if (bitOffset == 8) {
bitOffset = 0;
......@@ -199,17 +199,18 @@ public final class ParsableBitArray {
int to = offset + (numBits >> 3) /* numBits / 8 */;
for (int i = offset; i < to; i++) {
buffer[i] = (byte) (data[byteOffset++] << bitOffset);
buffer[i] |= (data[byteOffset] & 0xFF) >> (8 - bitOffset);
buffer[i] = (byte) (buffer[i] | ((data[byteOffset] & 0xFF) >> (8 - bitOffset)));
}
// Trailing bits.
int bitsLeft = numBits & 7 /* numBits % 8 */;
if (bitsLeft == 0) {
return;
}
buffer[to] &= 0xFF >> bitsLeft; // Set to 0 the bits that are going to be overwritten.
// Set bits that are going to be overwritten to 0.
buffer[to] = (byte) (buffer[to] & (0xFF >> bitsLeft));
if (bitOffset + bitsLeft > 8) {
// We read the rest of data[byteOffset] and increase byteOffset.
buffer[to] |= (byte) ((data[byteOffset++] & 0xFF) << bitOffset);
buffer[to] = (byte) (buffer[to] | ((data[byteOffset++] & 0xFF) << bitOffset));
bitOffset -= 8;
}
bitOffset += bitsLeft;
......@@ -280,9 +281,10 @@ public final class ParsableBitArray {
int firstByteReadSize = Math.min(8 - bitOffset, numBits);
int firstByteRightPaddingSize = 8 - bitOffset - firstByteReadSize;
int firstByteBitmask = (0xFF00 >> bitOffset) | ((1 << firstByteRightPaddingSize) - 1);
data[byteOffset] &= firstByteBitmask;
data[byteOffset] = (byte) (data[byteOffset] & firstByteBitmask);
int firstByteInputBits = value >>> (numBits - firstByteReadSize);
data[byteOffset] |= firstByteInputBits << firstByteRightPaddingSize;
data[byteOffset] =
(byte) (data[byteOffset] | (firstByteInputBits << firstByteRightPaddingSize));
remainingBitsToRead -= firstByteReadSize;
int currentByteIndex = byteOffset + 1;
while (remainingBitsToRead > 8) {
......@@ -290,9 +292,11 @@ public final class ParsableBitArray {
remainingBitsToRead -= 8;
}
int lastByteRightPaddingSize = 8 - remainingBitsToRead;
data[currentByteIndex] &= (1 << lastByteRightPaddingSize) - 1;
data[currentByteIndex] =
(byte) (data[currentByteIndex] & ((1 << lastByteRightPaddingSize) - 1));
int lastByteInput = value & ((1 << remainingBitsToRead) - 1);
data[currentByteIndex] |= lastByteInput << lastByteRightPaddingSize;
data[currentByteIndex] =
(byte) (data[currentByteIndex] | (lastByteInput << lastByteRightPaddingSize));
skipBits(numBits);
assertValidOffset();
}
......
......@@ -470,7 +470,7 @@ public final class ParsableByteArray {
if (lastIndex < limit && data[lastIndex] == 0) {
stringLength--;
}
String result = new String(data, position, stringLength);
String result = Util.fromUtf8Bytes(data, position, stringLength);
position += length;
return result;
}
......@@ -489,7 +489,7 @@ public final class ParsableByteArray {
while (stringLimit < limit && data[stringLimit] != 0) {
stringLimit++;
}
String string = new String(data, position, stringLimit - position);
String string = Util.fromUtf8Bytes(data, position, stringLimit - position);
position = stringLimit;
if (position < limit) {
position++;
......@@ -520,7 +520,7 @@ public final class ParsableByteArray {
// There's a byte order mark at the start of the line. Discard it.
position += 3;
}
String line = new String(data, position, lineLimit - position);
String line = Util.fromUtf8Bytes(data, position, lineLimit - position);
position = lineLimit;
if (position == limit) {
return line;
......
......@@ -140,7 +140,7 @@ public final class ParsableNalUnitBitArray {
returnValue |= (data[byteOffset] & 0xFF) << bitOffset;
byteOffset += shouldSkipByte(byteOffset + 1) ? 2 : 1;
}
returnValue |= (data[byteOffset] & 0xFF) >> 8 - bitOffset;
returnValue |= (data[byteOffset] & 0xFF) >> (8 - bitOffset);
returnValue &= 0xFFFFFFFF >>> (32 - numBits);
if (bitOffset == 8) {
bitOffset = 0;
......
......@@ -311,10 +311,10 @@ public final class Util {
* Returns a normalized RFC 639-2/T code for {@code language}.
*
* @param language A case-insensitive ISO 639 alpha-2 or alpha-3 language code.
* @return The all-lowercase normalized code, or null if the input was null, or
* {@code language.toLowerCase()} if the language could not be normalized.
* @return The all-lowercase normalized code, or null if the input was null, or {@code
* language.toLowerCase()} if the language could not be normalized.
*/
public static String normalizeLanguageCode(String language) {
public static @Nullable String normalizeLanguageCode(@Nullable String language) {
try {
return language == null ? null : new Locale(language).getISO3Language();
} catch (MissingResourceException e) {
......@@ -333,6 +333,18 @@ public final class Util {
}
/**
* Returns a new {@link String} constructed by decoding UTF-8 encoded bytes in a subarray.
*
* @param bytes The UTF-8 encoded bytes to decode.
* @param offset The index of the first byte to decode.
* @param length The number of bytes to decode.
* @return The string.
*/
public static String fromUtf8Bytes(byte[] bytes, int offset, int length) {
return new String(bytes, offset, length, Charset.forName(C.UTF8_NAME));
}
/**
* Returns a new byte array containing the code points of a {@link String} encoded using UTF-8.
*
* @param value The {@link String} whose bytes should be obtained.
......@@ -343,6 +355,33 @@ public final class Util {
}
/**
* Splits a string using {@code value.split(regex, -1}). Note: this is is similar to {@link
* String#split(String)} but empty matches at the end of the string will not be omitted from the
* returned array.
*
* @param value The string to split.
* @param regex A delimiting regular expression.
* @return The array of strings resulting from splitting the string.
*/
public static String[] split(String value, String regex) {
return value.split(regex, /* limit= */ -1);
}
/**
* Splits the string at the first occurrence of the delimiter {@code regex}. If the delimiter does
* not match, returns an array with one element which is the input string. If the delimiter does
* match, returns an array with the portion of the string before the delimiter and the rest of the
* string.
*
* @param value The string.
* @param regex A delimiting regular expression.
* @return The string split by the first occurrence of the delimiter.
*/
public static String[] splitAtFirst(String value, String regex) {
return value.split(regex, /* limit= */ 2);
}
/**
* Returns whether the given character is a carriage return ('\r') or a line feed ('\n').
*
* @param c The character.
......@@ -978,7 +1017,7 @@ public final class Util {
if (TextUtils.isEmpty(codecs)) {
return null;
}
String[] codecArray = codecs.trim().split("(\\s*,\\s*)");
String[] codecArray = split(codecs.trim(), "(\\s*,\\s*)");
StringBuilder builder = new StringBuilder();
for (String codec : codecArray) {
if (trackType == MimeTypes.getTrackTypeOfCodec(codec)) {
......@@ -1454,7 +1493,7 @@ public final class Util {
// If we managed to read sys.display-size, attempt to parse it.
if (!TextUtils.isEmpty(sysDisplaySize)) {
try {
String[] sysDisplaySizeParts = sysDisplaySize.trim().split("x");
String[] sysDisplaySizeParts = split(sysDisplaySize.trim(), "x");
if (sysDisplaySizeParts.length == 2) {
int width = Integer.parseInt(sysDisplaySizeParts[0]);
int height = Integer.parseInt(sysDisplaySizeParts[1]);
......
......@@ -156,7 +156,7 @@ public final class DummySurface extends Surface {
private static final int MSG_INIT = 1;
private static final int MSG_RELEASE = 2;
private @MonotonicNonNull EGLSurfaceTexture eglSurfaceTexure;
private @MonotonicNonNull EGLSurfaceTexture eglSurfaceTexture;
private @MonotonicNonNull Handler handler;
private @Nullable Error initError;
private @Nullable RuntimeException initException;
......@@ -169,7 +169,7 @@ public final class DummySurface extends Surface {
public DummySurface init(@SecureMode int secureMode) {
start();
handler = new Handler(getLooper(), /* callback= */ this);
eglSurfaceTexure = new EGLSurfaceTexture(handler);
eglSurfaceTexture = new EGLSurfaceTexture(handler);
boolean wasInterrupted = false;
synchronized (this) {
handler.obtainMessage(MSG_INIT, secureMode, 0).sendToTarget();
......@@ -232,16 +232,16 @@ public final class DummySurface extends Surface {
}
private void initInternal(@SecureMode int secureMode) {
Assertions.checkNotNull(eglSurfaceTexure);
eglSurfaceTexure.init(secureMode);
Assertions.checkNotNull(eglSurfaceTexture);
eglSurfaceTexture.init(secureMode);
this.surface =
new DummySurface(
this, eglSurfaceTexure.getSurfaceTexture(), secureMode != SECURE_MODE_NONE);
this, eglSurfaceTexture.getSurfaceTexture(), secureMode != SECURE_MODE_NONE);
}
private void releaseInternal() {
Assertions.checkNotNull(eglSurfaceTexure);
eglSurfaceTexure.release();
Assertions.checkNotNull(eglSurfaceTexture);
eglSurfaceTexture.release();
}
}
......
......@@ -205,7 +205,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
this.allowedJoiningTimeMs = allowedJoiningTimeMs;
this.maxDroppedFramesToNotify = maxDroppedFramesToNotify;
this.context = context.getApplicationContext();
frameReleaseTimeHelper = new VideoFrameReleaseTimeHelper(context);
frameReleaseTimeHelper = new VideoFrameReleaseTimeHelper(this.context);
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
deviceNeedsAutoFrcWorkaround = deviceNeedsAutoFrcWorkaround();
pendingOutputStreamOffsetsUs = new long[MAX_PENDING_OUTPUT_STREAM_OFFSET_COUNT];
......@@ -1177,8 +1177,9 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
// https://github.com/google/ExoPlayer/issues/3835,
// https://github.com/google/ExoPlayer/issues/4006,
// https://github.com/google/ExoPlayer/issues/4084,
// https://github.com/google/ExoPlayer/issues/4104.
// https://github.com/google/ExoPlayer/issues/4134.
// https://github.com/google/ExoPlayer/issues/4104,
// https://github.com/google/ExoPlayer/issues/4134,
// https://github.com/google/ExoPlayer/issues/4315.
return (("deb".equals(Util.DEVICE) // Nexus 7 (2013)
|| "flo".equals(Util.DEVICE) // Nexus 7 (2013)
|| "mido".equals(Util.DEVICE) // Redmi Note 4
......@@ -1192,7 +1193,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|| "M5c".equals(Util.DEVICE) // Meizu M5C
|| "QM16XE_U".equals(Util.DEVICE) // Philips QM163E
|| "A7010a48".equals(Util.DEVICE) // Lenovo K4 Note
|| "woods_f".equals(Util.MODEL)) // Moto E (4)
|| "woods_f".equals(Util.MODEL) // Moto E (4)
|| "watson".equals(Util.DEVICE)) // Moto C
&& "OMX.MTK.VIDEO.DECODER.AVC".equals(name))
|| (("ALE-L21".equals(Util.MODEL) // Huawei P8 Lite
|| "CAM-L21".equals(Util.MODEL)) // Huawei Y6II
......
......@@ -72,8 +72,12 @@ public final class VideoFrameReleaseTimeHelper {
* @param context A context from which information about the default display can be retrieved.
*/
public VideoFrameReleaseTimeHelper(@Nullable Context context) {
windowManager = context == null ? null
: (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
if (context != null) {
context = context.getApplicationContext();
windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
} else {
windowManager = null;
}
if (windowManager != null) {
displayListener = Util.SDK_INT >= 17 ? maybeBuildDefaultDisplayListenerV17(context) : null;
vsyncSampler = VSyncSampler.getInstance();
......
......@@ -1980,6 +1980,105 @@ public final class ExoPlayerTest {
.inOrder();
}
@Test
public void testRecursivePlayerChangesReportConsistentValuesForAllListeners() throws Exception {
// We add two listeners to the player. The first stops the player as soon as it's ready and both
// record the state change events they receive.
final AtomicReference<Player> playerReference = new AtomicReference<>();
final List<Integer> eventListener1States = new ArrayList<>();
final List<Integer> eventListener2States = new ArrayList<>();
final EventListener eventListener1 =
new DefaultEventListener() {
@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
eventListener1States.add(playbackState);
if (playbackState == Player.STATE_READY) {
playerReference.get().stop(/* reset= */ true);
}
}
};
final EventListener eventListener2 =
new DefaultEventListener() {
@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
eventListener2States.add(playbackState);
}
};
ActionSchedule actionSchedule =
new ActionSchedule.Builder("testRecursivePlayerChanges")
.executeRunnable(
new PlayerRunnable() {
@Override
public void run(SimpleExoPlayer player) {
playerReference.set(player);
player.addListener(eventListener1);
player.addListener(eventListener2);
}
})
.build();
new ExoPlayerTestRunner.Builder()
.setActionSchedule(actionSchedule)
.build()
.start()
.blockUntilEnded(TIMEOUT_MS);
assertThat(eventListener1States)
.containsExactly(Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_IDLE)
.inOrder();
assertThat(eventListener2States)
.containsExactly(Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_IDLE)
.inOrder();
}
@Test
public void testRecursivePlayerChangesAreReportedInCorrectOrder() throws Exception {
// The listener stops the player as soon as it's ready (which should report a timeline and state
// change) and sets playWhenReady to false when the timeline callback is received.
final AtomicReference<Player> playerReference = new AtomicReference<>();
final List<Boolean> eventListenerPlayWhenReady = new ArrayList<>();
final List<Integer> eventListenerStates = new ArrayList<>();
final EventListener eventListener =
new DefaultEventListener() {
@Override
public void onTimelineChanged(Timeline timeline, Object manifest, int reason) {
if (timeline.isEmpty()) {
playerReference.get().setPlayWhenReady(/* playWhenReady= */ false);
}
}
@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
eventListenerPlayWhenReady.add(playWhenReady);
eventListenerStates.add(playbackState);
if (playbackState == Player.STATE_READY) {
playerReference.get().stop(/* reset= */ true);
}
}
};
ActionSchedule actionSchedule =
new ActionSchedule.Builder("testRecursivePlayerChanges")
.executeRunnable(
new PlayerRunnable() {
@Override
public void run(SimpleExoPlayer player) {
playerReference.set(player);
player.addListener(eventListener);
}
})
.build();
new ExoPlayerTestRunner.Builder()
.setActionSchedule(actionSchedule)
.build()
.start()
.blockUntilEnded(TIMEOUT_MS);
assertThat(eventListenerStates)
.containsExactly(
Player.STATE_BUFFERING, Player.STATE_READY, Player.STATE_IDLE, Player.STATE_IDLE)
.inOrder();
assertThat(eventListenerPlayWhenReady).containsExactly(true, true, true, false).inOrder();
}
// Internal methods.
private static ActionSchedule.Builder addSurfaceSwitch(ActionSchedule.Builder builder) {
......
......@@ -392,11 +392,6 @@ public final class AdaptiveTrackSelectionTest {
}
@Override
public boolean isLoadCanceled() {
return false;
}
@Override
public void load() throws IOException, InterruptedException {
// Do nothing.
}
......
......@@ -16,6 +16,7 @@
package com.google.android.exoplayer2.source.dash;
import android.support.annotation.IntDef;
import android.support.annotation.Nullable;
import android.util.Pair;
import android.util.SparseArray;
import android.util.SparseIntArray;
......@@ -72,7 +73,7 @@ import java.util.List;
private final IdentityHashMap<ChunkSampleStream<DashChunkSource>, PlayerTrackEmsgHandler>
trackEmsgHandlerBySampleStream;
private Callback callback;
private @Nullable Callback callback;
private ChunkSampleStream<DashChunkSource>[] sampleStreams;
private EventSampleStream[] eventSampleStreams;
private SequenceableLoader compositeSequenceableLoader;
......@@ -150,6 +151,7 @@ import java.util.List;
for (ChunkSampleStream<DashChunkSource> sampleStream : sampleStreams) {
sampleStream.release(this);
}
callback = null;
eventDispatcher.mediaPeriodReleased();
}
......
......@@ -25,12 +25,15 @@ import com.google.android.exoplayer2.source.dash.manifest.RangedUri;
public final class DashWrappingSegmentIndex implements DashSegmentIndex {
private final ChunkIndex chunkIndex;
private final long timeOffsetUs;
/**
* @param chunkIndex The {@link ChunkIndex} to wrap.
* @param timeOffsetUs An offset to subtract from the times in the wrapped index, in microseconds.
*/
public DashWrappingSegmentIndex(ChunkIndex chunkIndex) {
public DashWrappingSegmentIndex(ChunkIndex chunkIndex, long timeOffsetUs) {
this.chunkIndex = chunkIndex;
this.timeOffsetUs = timeOffsetUs;
}
@Override
......@@ -45,7 +48,7 @@ public final class DashWrappingSegmentIndex implements DashSegmentIndex {
@Override
public long getTimeUs(long segmentNum) {
return chunkIndex.timesUs[(int) segmentNum];
return chunkIndex.timesUs[(int) segmentNum] - timeOffsetUs;
}
@Override
......@@ -61,7 +64,7 @@ public final class DashWrappingSegmentIndex implements DashSegmentIndex {
@Override
public long getSegmentNum(long timeUs, long periodDurationUs) {
return chunkIndex.getChunkIndex(timeUs);
return chunkIndex.getChunkIndex(timeUs + timeOffsetUs);
}
@Override
......
......@@ -354,7 +354,10 @@ public class DefaultDashChunkSource implements DashChunkSource {
if (representationHolder.segmentIndex == null) {
SeekMap seekMap = representationHolder.extractorWrapper.getSeekMap();
if (seekMap != null) {
representationHolder.segmentIndex = new DashWrappingSegmentIndex((ChunkIndex) seekMap);
representationHolder.segmentIndex =
new DashWrappingSegmentIndex(
(ChunkIndex) seekMap,
representationHolder.representation.presentationTimeOffsetUs);
}
}
}
......
......@@ -167,7 +167,9 @@ public final class DashDownloader extends SegmentDownloader<DashManifest, Repres
return index;
}
ChunkIndex seekMap = DashUtil.loadChunkIndex(dataSource, trackType, representation);
return seekMap == null ? null : new DashWrappingSegmentIndex(seekMap);
return seekMap == null
? null
: new DashWrappingSegmentIndex(seekMap, representation.presentationTimeOffsetUs);
}
}
......@@ -104,7 +104,7 @@ import java.util.List;
// the way in which HlsSampleStreamWrapper generates track groups. Use only index based methods
// in TrackSelection to avoid unexpected behavior.
private TrackSelection trackSelection;
private long liveEdgeTimeUs;
private long liveEdgeInPeriodTimeUs;
private boolean seenExpectedPlaylistError;
/**
......@@ -128,7 +128,7 @@ import java.util.List;
this.variants = variants;
this.timestampAdjusterProvider = timestampAdjusterProvider;
this.muxedCaptionFormats = muxedCaptionFormats;
liveEdgeTimeUs = C.TIME_UNSET;
liveEdgeInPeriodTimeUs = C.TIME_UNSET;
Format[] variantFormats = new Format[variants.length];
int[] initialTrackSelection = new int[variants.length];
for (int i = 0; i < variants.length; i++) {
......@@ -254,16 +254,17 @@ import java.util.List;
// Select the chunk.
long chunkMediaSequence;
long startOfPlaylistInPeriodUs =
mediaPlaylist.startTimeUs - playlistTracker.getInitialStartTimeUs();
if (previous == null || switchingVariant) {
long targetPositionUs = (previous == null || independentSegments) ? loadPositionUs
: previous.startTimeUs;
if (!mediaPlaylist.hasEndTag && targetPositionUs >= mediaPlaylist.getEndTimeUs()) {
long endOfPlaylistInPeriodUs = startOfPlaylistInPeriodUs + mediaPlaylist.durationUs;
long targetPositionInPeriodUs =
(previous == null || independentSegments) ? loadPositionUs : previous.startTimeUs;
if (!mediaPlaylist.hasEndTag && targetPositionInPeriodUs >= endOfPlaylistInPeriodUs) {
// If the playlist is too old to contain the chunk, we need to refresh it.
chunkMediaSequence = mediaPlaylist.mediaSequence + mediaPlaylist.segments.size();
} else {
long positionOfPlaylistInPeriodUs =
mediaPlaylist.startTimeUs - playlistTracker.getInitialStartTimeUs();
long targetPositionInPlaylistUs = targetPositionUs - positionOfPlaylistInPeriodUs;
long targetPositionInPlaylistUs = targetPositionInPeriodUs - startOfPlaylistInPeriodUs;
chunkMediaSequence =
Util.binarySearchFloor(
mediaPlaylist.segments,
......@@ -277,6 +278,8 @@ import java.util.List;
selectedVariantIndex = oldVariantIndex;
selectedUrl = variants[selectedVariantIndex];
mediaPlaylist = playlistTracker.getPlaylistSnapshot(selectedUrl);
startOfPlaylistInPeriodUs =
mediaPlaylist.startTimeUs - playlistTracker.getInitialStartTimeUs();
chunkMediaSequence = previous.getNextChunkIndex();
}
}
......@@ -331,9 +334,7 @@ import java.util.List;
}
// Compute start time of the next chunk.
long positionOfPlaylistInPeriodUs =
mediaPlaylist.startTimeUs - playlistTracker.getInitialStartTimeUs();
long segmentStartTimeInPeriodUs = positionOfPlaylistInPeriodUs + segment.relativeStartTimeUs;
long segmentStartTimeInPeriodUs = startOfPlaylistInPeriodUs + segment.relativeStartTimeUs;
int discontinuitySequence = mediaPlaylist.discontinuitySequence
+ segment.relativeDiscontinuitySequence;
TimestampAdjuster timestampAdjuster = timestampAdjusterProvider.getAdjuster(
......@@ -420,12 +421,17 @@ import java.util.List;
// Private methods.
private long resolveTimeToLiveEdgeUs(long playbackPositionUs) {
final boolean resolveTimeToLiveEdgePossible = liveEdgeTimeUs != C.TIME_UNSET;
return resolveTimeToLiveEdgePossible ? liveEdgeTimeUs - playbackPositionUs : C.TIME_UNSET;
final boolean resolveTimeToLiveEdgePossible = liveEdgeInPeriodTimeUs != C.TIME_UNSET;
return resolveTimeToLiveEdgePossible
? liveEdgeInPeriodTimeUs - playbackPositionUs
: C.TIME_UNSET;
}
private void updateLiveEdgeTimeUs(HlsMediaPlaylist mediaPlaylist) {
liveEdgeTimeUs = mediaPlaylist.hasEndTag ? C.TIME_UNSET : mediaPlaylist.getEndTimeUs();
liveEdgeInPeriodTimeUs =
mediaPlaylist.hasEndTag
? C.TIME_UNSET
: (mediaPlaylist.getEndTimeUs() - playlistTracker.getInitialStartTimeUs());
}
private EncryptionKeyChunk newEncryptionKeyChunk(Uri keyUri, String iv, int variantIndex,
......
......@@ -207,11 +207,6 @@ import java.util.concurrent.atomic.AtomicInteger;
}
@Override
public boolean isLoadCanceled() {
return loadCanceled;
}
@Override
public void load() throws IOException, InterruptedException {
maybeLoadInitData();
if (!loadCanceled) {
......@@ -242,7 +237,7 @@ import java.util.concurrent.atomic.AtomicInteger;
initSegmentBytesLoaded = (int) (input.getPosition() - initDataSpec.absoluteStreamPosition);
}
} finally {
Util.closeQuietly(dataSource);
Util.closeQuietly(initDataSource);
}
initLoadCompleted = true;
}
......
......@@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.source.hls;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.SeekParameters;
......@@ -57,7 +58,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
private final boolean allowChunklessPreparation;
private Callback callback;
private @Nullable Callback callback;
private int pendingPrepareCount;
private TrackGroupArray trackGroups;
private HlsSampleStreamWrapper[] sampleStreamWrappers;
......@@ -96,6 +97,7 @@ public final class HlsMediaPeriod implements MediaPeriod, HlsSampleStreamWrapper
for (HlsSampleStreamWrapper sampleStreamWrapper : sampleStreamWrappers) {
sampleStreamWrapper.release();
}
callback = null;
eventDispatcher.mediaPeriodReleased();
}
......
......@@ -32,6 +32,7 @@ import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispat
import com.google.android.exoplayer2.source.SequenceableLoader;
import com.google.android.exoplayer2.source.SinglePeriodTimeline;
import com.google.android.exoplayer2.source.ads.AdsMediaSource;
import com.google.android.exoplayer2.source.hls.playlist.DefaultHlsPlaylistTracker;
import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist;
import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylist;
import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistParser;
......@@ -58,6 +59,7 @@ public final class HlsMediaSource extends BaseMediaSource
private HlsExtractorFactory extractorFactory;
private @Nullable ParsingLoadable.Parser<HlsPlaylist> playlistParser;
private @Nullable HlsPlaylistTracker playlistTracker;
private CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
private int minLoadableRetryCount;
private boolean allowChunklessPreparation;
......@@ -136,17 +138,38 @@ public final class HlsMediaSource extends BaseMediaSource
* Sets the parser to parse HLS playlists. The default is an instance of {@link
* HlsPlaylistParser}.
*
* <p>Must not be called after calling {@link #setPlaylistTracker} on the same builder.
*
* @param playlistParser A {@link ParsingLoadable.Parser} for HLS playlists.
* @return This factory, for convenience.
* @throws IllegalStateException If one of the {@code create} methods has already been called.
*/
public Factory setPlaylistParser(ParsingLoadable.Parser<HlsPlaylist> playlistParser) {
Assertions.checkState(!isCreateCalled);
Assertions.checkState(playlistTracker == null, "A playlist tracker has already been set.");
this.playlistParser = Assertions.checkNotNull(playlistParser);
return this;
}
/**
* Sets the HLS playlist tracker. The default is an instance of {@link
* DefaultHlsPlaylistTracker}. Playlist trackers must not be shared by {@link HlsMediaSource}
* instances.
*
* <p>Must not be called after calling {@link #setPlaylistParser} on the same builder.
*
* @param playlistTracker A tracker for HLS playlists.
* @return This factory, for convenience.
* @throws IllegalStateException If one of the {@code create} methods has already been called.
*/
public Factory setPlaylistTracker(HlsPlaylistTracker playlistTracker) {
Assertions.checkState(!isCreateCalled);
Assertions.checkState(playlistParser == null, "A playlist parser has already been set.");
this.playlistTracker = Assertions.checkNotNull(playlistTracker);
return this;
}
/**
* Sets the factory to create composite {@link SequenceableLoader}s for when this media source
* loads data from multiple streams (video, audio etc...). The default is an instance of {@link
* DefaultCompositeSequenceableLoaderFactory}.
......@@ -187,8 +210,12 @@ public final class HlsMediaSource extends BaseMediaSource
@Override
public HlsMediaSource createMediaSource(Uri playlistUri) {
isCreateCalled = true;
if (playlistParser == null) {
playlistParser = new HlsPlaylistParser();
if (playlistTracker == null) {
playlistTracker =
new DefaultHlsPlaylistTracker(
hlsDataSourceFactory,
minLoadableRetryCount,
playlistParser != null ? playlistParser : new HlsPlaylistParser());
}
return new HlsMediaSource(
playlistUri,
......@@ -196,7 +223,7 @@ public final class HlsMediaSource extends BaseMediaSource
extractorFactory,
compositeSequenceableLoaderFactory,
minLoadableRetryCount,
playlistParser,
playlistTracker,
allowChunklessPreparation,
tag);
}
......@@ -233,12 +260,10 @@ public final class HlsMediaSource extends BaseMediaSource
private final HlsDataSourceFactory dataSourceFactory;
private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
private final int minLoadableRetryCount;
private final ParsingLoadable.Parser<HlsPlaylist> playlistParser;
private final boolean allowChunklessPreparation;
private final HlsPlaylistTracker playlistTracker;
private final @Nullable Object tag;
private HlsPlaylistTracker playlistTracker;
/**
* @param manifestUri The {@link Uri} of the HLS manifest.
* @param dataSourceFactory An {@link HlsDataSourceFactory} for {@link DataSource}s for manifests,
......@@ -276,8 +301,13 @@ public final class HlsMediaSource extends BaseMediaSource
int minLoadableRetryCount,
Handler eventHandler,
MediaSourceEventListener eventListener) {
this(manifestUri, new DefaultHlsDataSourceFactory(dataSourceFactory),
HlsExtractorFactory.DEFAULT, minLoadableRetryCount, eventHandler, eventListener,
this(
manifestUri,
new DefaultHlsDataSourceFactory(dataSourceFactory),
HlsExtractorFactory.DEFAULT,
minLoadableRetryCount,
eventHandler,
eventListener,
new HlsPlaylistParser());
}
......@@ -309,7 +339,8 @@ public final class HlsMediaSource extends BaseMediaSource
extractorFactory,
new DefaultCompositeSequenceableLoaderFactory(),
minLoadableRetryCount,
playlistParser,
new DefaultHlsPlaylistTracker(
dataSourceFactory, minLoadableRetryCount, new HlsPlaylistParser()),
/* allowChunklessPreparation= */ false,
/* tag= */ null);
if (eventHandler != null && eventListener != null) {
......@@ -323,7 +354,7 @@ public final class HlsMediaSource extends BaseMediaSource
HlsExtractorFactory extractorFactory,
CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory,
int minLoadableRetryCount,
ParsingLoadable.Parser<HlsPlaylist> playlistParser,
HlsPlaylistTracker playlistTracker,
boolean allowChunklessPreparation,
@Nullable Object tag) {
this.manifestUri = manifestUri;
......@@ -331,7 +362,7 @@ public final class HlsMediaSource extends BaseMediaSource
this.extractorFactory = extractorFactory;
this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory;
this.minLoadableRetryCount = minLoadableRetryCount;
this.playlistParser = playlistParser;
this.playlistTracker = playlistTracker;
this.allowChunklessPreparation = allowChunklessPreparation;
this.tag = tag;
}
......@@ -339,9 +370,7 @@ public final class HlsMediaSource extends BaseMediaSource
@Override
public void prepareSourceInternal(ExoPlayer player, boolean isTopLevelSource) {
EventDispatcher eventDispatcher = createEventDispatcher(/* mediaPeriodId= */ null);
playlistTracker = new HlsPlaylistTracker(manifestUri, dataSourceFactory, eventDispatcher,
minLoadableRetryCount, this, playlistParser);
playlistTracker.start();
playlistTracker.start(manifestUri, eventDispatcher, /* listener= */ this);
}
@Override
......@@ -373,7 +402,6 @@ public final class HlsMediaSource extends BaseMediaSource
public void releaseSourceInternal() {
if (playlistTracker != null) {
playlistTracker.release();
playlistTracker = null;
}
}
......
......@@ -73,6 +73,7 @@ public final class HlsDownloadHelper extends DownloadHelper {
public TrackGroupArray getTrackGroups(int periodIndex) {
Assertions.checkNotNull(playlist);
if (playlist instanceof HlsMediaPlaylist) {
renditionTypes = new int[0];
return TrackGroupArray.EMPTY;
}
// TODO: Generate track groups as in playback. Reverse the mapping in getDownloadAction.
......
......@@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.source.smoothstreaming;
import android.support.annotation.Nullable;
import android.util.Base64;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.SeekParameters;
......@@ -52,7 +53,7 @@ import java.util.ArrayList;
private final TrackEncryptionBox[] trackEncryptionBoxes;
private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
private Callback callback;
private @Nullable Callback callback;
private SsManifest manifest;
private ChunkSampleStream<SsChunkSource>[] sampleStreams;
private SequenceableLoader compositeSequenceableLoader;
......@@ -98,6 +99,7 @@ import java.util.ArrayList;
for (ChunkSampleStream<SsChunkSource> sampleStream : sampleStreams) {
sampleStream.release();
}
callback = null;
eventDispatcher.mediaPeriodReleased();
}
......
......@@ -251,7 +251,7 @@ public final class SubtitleView extends View implements TextOutput {
// Calculate the bounds after padding is taken into account.
int left = getLeft() + getPaddingLeft();
int top = rawTop + getPaddingTop();
int right = getRight() + getPaddingRight();
int right = getRight() - getPaddingRight();
int bottom = rawBottom - getPaddingBottom();
if (bottom <= top || right <= left) {
// No space to draw subtitles.
......
......@@ -203,7 +203,9 @@ public class TrackSelectionView extends LinearLayout {
removeViewAt(i);
}
if (trackSelector == null) {
MappingTrackSelector.MappedTrackInfo trackInfo =
trackSelector == null ? null : trackSelector.getCurrentMappedTrackInfo();
if (trackSelector == null || trackInfo == null) {
// The view is not initialized.
disableView.setEnabled(false);
defaultView.setEnabled(false);
......@@ -212,7 +214,6 @@ public class TrackSelectionView extends LinearLayout {
disableView.setEnabled(true);
defaultView.setEnabled(true);
MappingTrackSelector.MappedTrackInfo trackInfo = trackSelector.getCurrentMappedTrackInfo();
trackGroups = trackInfo.getTrackGroups(rendererIndex);
DefaultTrackSelector.Parameters parameters = trackSelector.getParameters();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment