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